-
-
Notifications
You must be signed in to change notification settings - Fork 97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a Trait system for GDScript #6416
Comments
I am very happy with the idea how it works, exactly what I would Need and use a lot! Though one things looks a bit „wrong“ to me. Classes fulfill, Implement or provide a trait, not using it, like a derived class extends a Base class and thats why the keyword is extends, maybe we can stick Here Java like with extends for base classes and implements for traits. |
The link for godotengine/godot#70956 is broken. I mean, you should have pasted the entire url for the pull request for the link to be clickable. |
Would abstract functions be exclusive to traits? I don't see why they would, but it's worth clarifying. If they can be declared in normal classes, then that's worth a dedicated proposal, perhaps. |
I don't think Of course every name/keyword can be discussed. I believe |
@YuriSizov yes, they will be exclusive to traits. This proposal only adds them to traits, so the classes still remain without abstract methods. Adding abstract methods would require abstract classes as well, which are not available and beyond the scope of traits. So this idea is more fit to the proposal for abstract classes: #5641 (I'm not saying it won't happen, just that it is a different discussion). It works for traits because those already can't be instantiated. Classes using traits that have abstract methods are required to provided an implementation. |
So this is similar to an interface in other languages? I love this idea and I think you'll see massive usage of it. |
Can you expand a bit on what are the alternatives (like multiple inheritance) and the tradeoffs? To me, a trait seems almost identical to a class, except it can be used in multiple inheritance, can define interfaces, and can't be instanced. Wouldn't it be easier just to have multiple inheritance and just reuse classes in the exact same way? |
For the problem with extra prefixes when using composition, I quite like the Odin approach of importing a member's members into the namespace using the https://odin-lang.org/docs/overview/#using-statement-with-structs This basically lets you do |
Great, just today I thought about the lack of such feature in GDScript) Not sure that a new file extension is a good idea since it always complicates everything about the language: third-party tools support (IDEs, linters, build systems etc), even explaining the difference to novices. Maybe simply using a I'm also not sure about the Or even more, if there will be a new kind of scripts in addition to classes, maybe declaration of both of them should be changed to something like BTW in Swift this feature called protocols and they use the term "conforms to" for classes that implement them. |
My vote goes to |
I've read the proposal and from what I can understand, this is like preloading/loading another script(https://godotengine.org/qa/53560/can-you-split-a-class-across-multiple-files-in-gdscript) but with added benefit that they'll node-type-checked when adding them to a script so that you can't accidentally add a trait meant for another class. Based on this logic, rather than Note: I'm using up class/script/file interchangeably but I mean the same. |
I really like the proposal. I can see myself using this to define "interface style" traits. I would also use this to extend the logic of some scripts. A few questions:
|
And that's why IMHO it doesn't need different extension. Extension hints how to read/parse a file, not how to interpret its content after parsing. For example, Working with a large C project on a daily basis, it's obvious how its separation into |
would be replaced by:
is it a class or trait? Well, you cannot tell unless you hint with |
I think traits like in this proposal can access the scene tree, as long as they extend a Node derived class. As traits could use all methods from classes they extend, this should be valid (or rather, I would wish this to be a valid trait, and I don't think anything is preventing this): func start_idle():
get_node("AnimationPlayer").play("idle") I would also love if |
@WolfgangSenff it is like interfaces with some extra features.
@jabcross Multiple inheritance has 2 issues that traits avoid:
That could be done in parallel, since traits are not only to replace composition (it probably doesn't in many cases anyway). But that's another proposal.
@okla IMO it makes easier for IDEs because the files allow and forbid different things. The IDE would have to be aware of the preamble of the file before deciding what rules to apply. Also, Godot sees resources by extension, so if traits also use
Perhaps. I guess it would be more familiar to GDScript users.
It has been discussed before, it is difficult to use If anything, I would add a
@Anutrix I considered |
I mentioned this in the proposal: the same method name in multiple included traits will give an error. This also happens if they share the signature.
You can use
The problem is that the trait is not an object, at least not how GDScript sees it, and it probably won't even be a resource. The trait won't be available at runtime, so it can't be stored into a constant. It could be done as a "syntax sugar" for the familiarity, but I feel that would be more confusing since you cannot access the trait in any way and there's nothing you can do with its "value". TBH I would prefer adding the syntax Also, there's no way to control when a trait is loaded because it can only be done at compile time. They will always be loaded together with the script.
I do think this will work, unless I find some problem during implementation. |
I would prefer trait to be a resource:
If I understood this sentence correctly, then it seems to me that it would be better if the traits and their inclusions check if the trait's method shadows the native method: # trait_a.gdt
# ===========
global_name TraitA
extends Object
# OK. `Object` does not have `add_child()` method, although its descendants may have it.
func add_child(node):
pass
# trait_b.gdt
# ===========
global_name TraitB
extends Node
# The method "add_child" overrides a method from native class "Node".
# This won't be called by the engine and may not work as expected.
# (Warning treated as error.)
func add_child(node):
pass
# script_1.gd
# ===========
extends Object
# OK. None of the `TraitA` methods shadow native methods.
uses TraitA
# script_2.gd
# ===========
extends Node
# The method "add_child" from trait "TraitA" overrides a method from native class "Node".
# This won't be called by the engine and may not work as expected.
# (Warning treated as error.)
uses TraitA |
I would even go one step further, and discuss if "trait" is the right keyword.
This is also how e.g. the Rust language uses traits. But that doesn't align with this proposal -- the idea here seems to be primarily code reuse and not polymorphism/interfacing. Instead, what do you think about "mixin"?
|
@Bromeon my brain actually used the term mixin in a prior comment and I had to edit my comment to replace it with "trait" - mixin definitely makes more sense here as a name. |
Good question. What is described in this proposal is very similar to traits in PHP ("language assisted copy and paste"), only with additional static checks. |
I had a similar reaction. I immediately thought of C++ type traits. I'd love a feature like type traits, but I agree that it doesn't seem to align with what is being proposed here. I'm drawing attention to C++ specifically because Godot itself is implemented in C++, and there seems to be quite a lot of potential for confusion here. |
Good job, @vnen. All the static typing systems, when implemented post a language launch (e.g. python, ruby) come with some interface concept, given that's the only way we can actually depend on abstractions instead of concrete types. I think this is REALLY important.
I want to discuss this further ☝️. I personally like the
It DOES, even if you provide a default implementation from the trait, it still implements that trait and thus, from a typing system perspective it can act as an object of that type. I dislike You are right that your proposal can be used in two ways:
IMO, usage number 1 is by far the most important point of this, so maybe we should build this feature around that concept. |
Mixins are usually conceptually different than protocols or interfaces. Mixins can be implemented anywhere and do not require a type system (look at Ruby, PHP...). On the other hand, interfaces do not make any sense in a language without static typing (see duck typing). Please, let's not do just Mixins here, or name it like that, that's not what we need (IMO, of course). We need a more refined concept, like the one proposed by the author here, IMO we might need to change some keywords though, like the |
I am not sure how I feel about this. I think it should only be a warning, and have a clearly defined order of lookups. So a function call would have a precedence like this:
To illustrate this more, let's take this example extends Base
uses Foo
uses Bar Calling a function would first look in this script for the function, then in |
Wow i had a similar proposal that for some reason i have never submitted, but i had no idea of the existence of trait/mixin! I have a doubt though, imo a trait should be basically a shortcut to copy-pasting a piece of code to another piece of code plus the addition of some functions like has_trait(), get_trait_list() and similar. Simple and clean and can be beneficail to a vast amount of situations. Admittely i'm ignorant about trait/mixin, but is it really a good idea to treat traits as types? It seems to me that creating trait variables would make things a lot more complex, confusing and error prone only to get some advantages in some situations. Am i wrong? |
That kinda is required to make static typing work. If you can't use traits as types, then there would be no way to get static typing for arbitrary objects of potentially different classes that all implement the same trait. Creating trait variables wouldn't be required, it would just be something that people can do if they want to. And as with all typing in Godot, it's optional. So you don't need to use any of these features, you can just use traits to re-use some method implementations without worrying about using trait types. |
How will this work with user documentation? There are no interfaces in Godot API ( |
In this case it's just a func draw_colors(drawable: Resource) -> void:
if drawable is Curve or drawable is Gradient:
var draw_data : DrawData = drawable.get_color_line()
# ... draw the line ... That would work (with types), right? edit1: to add extra info |
I feel like you're missing the point- This means that you don't have to do as many checks. It also means that you get autocomplete, which you don't have with just checking the type manually, and you get errors before runtime if you attempt to pass a resource that doesn't implement Drawable into the function. It makes things more readable and more type safe. func draw_colors(drawable: Drawable) -> void:
var draw_data := drawable.get_color_line()
# ... draw the line ... is much easier to read, edit, and understand at a glance what's happening than func draw_colors(drawable: Resource) -> void:
if drawable is Curve or drawable is Gradient:
var draw_data : DrawData = drawable.get_color_line()
# ... draw the line ... Not to mention the first is fully type-safe. While the contrast might not be incredibly clear here, it really adds up with larger codebases. Say that instead of just Curve and Gradient having the function, we had to write this: func draw_colors(drawable: Resource) -> void:
if drawable is Curve or drawable is Gradient or drawable is Curve2D or drawable is Curve3D or drawable is CurveXYZ or drawable is CurveTexture or drawable is GradientTexture1D or drawable is GradientTexture2D or drawable is GradientTexture3D or drawable is Texture2D or drawable is Sky or ...:
var draw_data : DrawData = drawable.get_color_line()
# ... draw the line ... (I know that some of these extend the same class so a few of the checks are redundant, but nonetheless) Interfaces would let you save on checks, save on runtime errors, save on loosely typed code, and give you autocomplete. They have plenty of use cases. |
It's not polluting, you don't see a benefit and you are likely not the only one. This means explaining it to you can help other people following this discussion that have the same point of view. So because of that, you are an additional point of view we can talk about. It's beneficial
Using signals for this is an interesting workaround, and it's a bit better than full dynamic typing. But the approach you suggests is in my opinion overly relying on the physics engine and add complexity due to (in my opinion) needless signals. One drawback I also see is that for every new contract, you need a new Area. Now, let's see the same problem solved with interfaces (I will use C#, tho my Godot API know-how might be a bit rusty). I will use a Raycast as a way to hit var spaceState = GetWorld3D().DirectSpaceState;
var node = spaceState.IntersectRay(...)["collider"] as Node3D;
var actor = node.Owner;
if (actor is IHittable hittable)
hittable.Hit(); Here, we do not care what Hit does. It could run some code to do a backflip or it could delegate the call to a child node like var actor = gridManager.GetAt(...);
if (actor is IHittable hittable)
hittable.Hit(); This allows us to use the same "hitbox" for shooting, but also interaction, inserting, picking up and so on. Interfaces allow us to define contracts, which is great for encapsulation. So while it's handy in Unity and Unreal, I would consider it crucial in Godot as the way nodes works encourages encapsulation. |
One huge benefit that is really worth mentioning is what this type of thing means for documentation, as well. When we're dealing with trees of nodes that expect certain things of their children, it's difficult to communicate that a particular node is expected to provide a particular function. Sure, you can throw an error at runtime, but it's much better if we could be like " |
You beat me to it! Very well argued all around, both in the quoted comment and the one you posted later.
This works particularly nicely if we have the ability, as someone proposed waaaay upthread, to delegate trait implementation to a property (such as a child node, or an assigned resource). So for instance, even if a class didn't implement the trait itself, it could say that property so-and-so would implement the trait on its behalf.
Yes! I hadn't even really thought about that, since as a solo dev (... well, solo coder at any rate...) I have markedly less use for documentation and mostly only worry about ease of implementation/expansion. But it's a very good point for those who do properly document their code. |
Thank you for your interest in this proposal! This is a very interesting discussion and it is certainly relevant to the subject. However, I think this is not a good place to compare composition vs inheritance/traits/interfaces. These are different approaches, each approach has its own pros and cons, its supporters and opponents. We are convinced that traits/interfaces would be a useful addition to GDScript. This proposal is highly supported by the community, based on previous discussions (and also reactions on the first post). Of course, we are still assessing the complexity and feasibility of this feature and there are more important problems at the moment. However, the fact that the community is interested in traits/interfaces does not require further confirmation. We do not consider composition as a replacement for traits/interfaces, they could coexist together. Please refrain from further comparison of composition vs traits/interfaces within this proposal, as it already has 200+ comments, which is quite a lot. We understand and appreciate that there is an alternative approach (composition) and this was a good addition to this proposal. However it looks like enough has been said on this topic, let's move on to discussing other issues with traits/interfaces. Thanks! |
There was a misunderstanding of traits in some comments, so I believe discussion benefited from clarification. Nice to know that this proposal is highly supported. This and also typed dictionaries are very wanted by many (according to the Godot forum). |
Does this seem like a decision has been made on this at this point and that some work can begin in implementing this? |
vnen:
Calinou:
Yes, in theory anyone can try to implement this. If you would like to get involved or discuss implementation details, please use the Godot Contributors Chat. However, note that this is a fairly large and complex feature that requires extensive experience in the internals. George has already made some progress, even if he has stopped working on it for now. Please be patient: we are interested in the feature, but it is not the highest priority at the moment. |
Suggest: |
re abstract functions, I'd clarify syntax a bit - gdscript beginners might do all kinds of silly stuff in their first scripts, e.g. skip function body and then they'll receive an error with something about traits/abstract stuff and they'll be confused instead of just getting "function is missing a body" error. Perhaps this could be solved by adding something instead of colon which would show that user have deliberately omitted specifying the body. Perhaps an exclamation mark? Pro: in most human languages it's semantically associated with abstract action of request. func some_func_with_implementation() -> void:
print("hello")
func some_malformed_function() # Emits easy to understand error
func some_abstract_function()! # Doesn't emit error |
That would completely break the proposed trait feature. The inspector works on a per object base. The proposed traits work on a per script base. So when you would allow the inspector to add a trait to an object, it would only change that object not the script. So other instances of the same script may not have the same trait. In contrast the proposed trait system guarantees you that every instance of a script implements the traits it defines. So what you suggested seems more like a component system for the editor/engine, not a trait system for the GDScript language. |
@tehKaiN
or instead of [IDEA] check if a type/instance implements a trait:
|
Not sure about semicolons - they're kinda present in gdscript, as you can optionally end lines with them, like in python - it might be a bit confusing that sometimes they're required and sometimes are not. Comma is an interesting suggestion, I can see the rationale as a field separator, but I'm not sure I'm fan of it. I guess @vnen or whoever implements it would have to say a bit more about it. Re checking for implementing - is separate operation from |
@tehKaiN you're right about function signatures, could just end with |
I can see a scenario where My suggestion would be to give them a shared namespace, so this kind of collision can't happen in the first place. |
Beyond that, I would also suggest adding a naming convention in the docs for traits, similar to interfaces. Some possibilities could be |
Definitely, but don't skimp on collision prevention thinking everyone will use sane naming conventions! 😂 |
Why do they need to be named traits? I agree we need a naming convention for them and if they are called traits then they would collide with generics if they are implemented. I think you guys should call them interfaces and treat them as traits. besides traits are basically interfaces, except they can be attached to any object and be attached after class deceleration. |
Traits and interfaces are different.
(So, traits have more declarative power than interfaces.)
|
I think that a Trait could be done with interfaces and sub-nodes like this:
Benefits:
Example:Interface (with whatever syntax it would be implemented)interface_name ISomeTrait
@required
var sub_node: SubNode
func some_operation() -> void
func some_other_operation() -> void Parent nodeclass_name ParentNode
implements ISomeTrait
@onready var sub_node: SubNode = $SomeSubNode
func some_operation() -> void
pass
func some_other_operation() -> void
sub_node.do_something() Sub nodeclass_name SubNode
func _ready() -> void:
parent.some_operation()
func do_something() -> void
pass |
It's a little bit disheartening to see these discussions still going on about whether traits are useful etc. after all this time. |
I don't think there's any reason to worry. From what i understand, this feature is already planned and vnen stated a while back that he had already started implementing it. Not sure what the status of it is now. I would like to see it come to fruition in 4.4 if that's possible. It would be nice to finally have a proper interface-like system in GDScript instead of relying on the many flimsy work-arounds that I've seen many people suggest and that I have personally tried. I've finally accepted that no work-around will ever quite match having a proper language-supported feature like this, so I'd say that this is my most anticipated feature right now. It's just a matter of when. :( |
interfaces and traits are literally the same thing. For example C# devs are thinking about possibly having interface extensions in the C# language which would literally be traits. I am just saying they should be called interfaces so that generics and interfaces would not be both prefixed with T |
It's the user that gives the names. Nothing is stopping you from prefixing traits with I or prefixing generics with G. |
I'm going to pause this discussion as it's now just bikeshedding. To clarify, the GDScript team recognizes that traits can be a useful addition to the language, and so it might be implemented in the future. Right now, though, our focus is to fix known bugs (e.g. make Once the team considers that it's time to work on trait support, they'll reopen this discussion, if more needs to be discussed. Or possibly they will open a new proposal with the then-consensus on what to implement, so interested users don't have to read through 250 comments to know what's what. |
Describe the project you are working on
The GDScript implementation.
Describe the problem or limitation you are having in your project
The main to reuse code in GDScript is via inheritance. One can always use composition, either via instances stored in class variables or as nodes in the scene tree, but there's a lot of resistance in adding a new prefix to accessing some functionality in the class, so many go to the inheritance route.
That is completely fine if the inheritance tree includes related scripts. However, it's not uncommon for users to create a library of helper functions they want to have available in every script, then proceed to make this the common parent of everything. To be able to attach the sub-classes to any node, they make this common parent extend
Node
(or evenObject
) and rely on the quirk of the system that allows attaching scripts to a sub-class of the type it extends (cf. godotengine/godot#70956).The problem with this usage is that a script gets a bit of "multiple inheritance" which is not really supported by GDScript. One script can now extend another that ultimately extends
Node
but it can also use the methods of, say, aSprite2D
when attached to a node of such type, even when not directly on the inheritance line.Here I put a more concrete example to understand the issue:1
All of this works against the user since they lose optimizations and compile-time checks for potential errors. It's a bigger loss if they use static types since not knowing the actual base class will make the compiler not know the types of properties and methods, making the checks less reliable.
Another problem for users of static typing is checking if a particular method implements a given interface. You can use
has_method()
but that's often not enough since you can't tell what the signature of the method is. Even with introspection, it would require a lot of boilerplate for each check and you still lose the benefits of static type check. You can only check for inheritance but since that is linear you cannot use for a class that needs to implement multiple interfaces.Previous requests for a feature like this, or problems that could be solved by this feature:
Describe the feature / enhancement and how it helps to overcome the problem or limitation
For solving this, I propose the creation of trait in GDScript.
What is a trait?
It is a reusable piece of code that can be included in other scripts, parallel to inheritance. The included code behave pretty much as copy and paste, it adds to the existing code of the script, not unlike the
#include
directive in C/C++. Note that the compiler is aware of traits and can provided better error messages than an actual#include
would be able to.Its contents is the same thing that can be included in a script: variables, constants, methods, inner classes.
Besides that, it can include method signatures, which are methods without an implementation defined, making the trait acting like an interface.
How is a trait defined?
In its own file, like a GDScript class. To avoid confusion, I propose using a new file extension for this, like
.gdt
(for GDScript Trait), so it won't be usable for things that requires a script. For instance, you won't be able to attach a trait file to aNode
directly. I'm still considering whether trait files should be resources. They will only be used from the GDScript implementation, so the resource loader does not need to be aware of them. OTOH, it might make the filesystem scan more complex. This will be investigated further during implementation.Traits can also be nested in trait files or in GDScript files, using the
trait
keyword:That would be accessible with
TraitCollection.NestedTrait
.Example of a trait file:
I consider also using a
trait_name
keyword that would work similar to theclass_name
keyword, making the trait available by name in the global scope. I prefer not to reuseclass_name
for this as I believe it might be confusing, since the trait file is not a class.Trait files cannot contain inner classes.
Traits can use the
extends
keyword to select a base class. That will mean that any script that does not derive from this class cannot use the trait:The
extends
keyword can also be used with a custom GDScript type. Traits can also override methods of the base class. This means they need to match in signature and will give an error otherwise. Note that for native classes the overriding of methods can be unpredictable as the overrides won't be called internally in the engine, so this will give a warning (treated as error by default) as it does for when this happen with a GDScript class.How to use a trait?
I propose adding the
implements
keyword2. This should be added at the beginning of the class, together withextends
andclass_name
.Using multiple traits will include them all in the current class. If there are conflicts, such as the same method name declared on multiple used traits, the compiler will give an error, also shown in-editor for the user. If the current class has a method with the same name as one used trait, it will be treated as an override and will be required to match signature. For variables, constants, and enums, duplication won't be allowed at all, as there are no way to override those. If multiple traits has the same variable, then it will be considered a conflict only if they have different types.
Conflicts between trait methods can be resolved by implementing an override method in the script and calling the appropriate trait method:
Traits can also use other traits, allowing you to extend on an existing trait or create "trait packages" so you only need to use once in the classes and it includes many traits. It is not a conflict if multiple used traits happen to use the same trait. The nested inclusion won't make it being included twice (which differs from how
#include
works in C/C++).Once a trait is used, its members can be accessed as if belonging to the same class:
Are traits considered types?
Yes. Traits can be used as type specifications in variables:
They can also be used in type checks and casts:
To avoid polluting the global scope, the traits can also be preloaded like classes:
Warning: One thing to consider when using traits as types is that all are considered to be a basic
Object
with a script attached. That is, even if the instance you have extends, say,Node2D
, you won't be able to use the static type checks forNode2D
members. That also means that optimizations done by GDScript to call native methods faster won't be applied, since it can't guarantee at compile time that those will be available without knowing the actual super class. Usingextends
in the trait helps when it's meant to be applied to a particular type, as it will hint the compiler to what is available.Warning: Consider too that when checking for traits with
is
andas
, the check will be for the use of the exact trait. It won't check if the object implements the same interface as the trait. As an example:While the class
A
implements a methodfoo
with the same signature, it does not use"trait.gdt"
so it won't be considered to be this trait when checking withis
nor when casting withas
. This is a technical limitation since performing an interface validation at runtime would be costly, and not validating at runtime would be unpredictable for users.Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams
Internally, a trait file will be treated a bit differently from a script file, to allow the little differences in syntax (e.g. methods without body). They will use the same parser since the differences are small, but will be treated differently when relevant. This does add a burden for the parser, though I believe it is better than using a different parser that would do most of the same.
The analyzer, which is responsible for type-checking, will also run on traits. They will be required to be internally consistent on their own, even before being used in a class. This allows the errors to be detected when writing the trait, rather than when using it.
Fully defined functions will be compiled and stored in a
GDScriptTrait
object. This object won't be exposed to scripting and only used internally. It will be used for caching purposes to avoid compiling the same trait multiple times. The defined functions will be copied to the GDScript object that implements the trait, so it won't have any reference to the originalGDScriptTrait
object.Used traits will be stored in the GDScript object as a set of strings. The stored string is the FQN (fully qualified name) that uniquely identifies the trait (essentially the file path plus identifiers for nested traits). This will be used for runtime trait checks and casts, created by the
is
andas
keywords.During parsing, the used traits will be collected and referenced in the resulting parse tree. The parse trees won't be directly mixed in order to allow the parser to detect the origin of code and thus provide more specific error messages.
The analyzer phase will further check if there are no conflicts and if types are correct, including checks for trait types. It will give specific errors and warnings where appropriate.
The
GDScriptCache
will be responsible for keeping the theGDScriptTrait
objects and intermediary compilation steps when necessary. After a GDScript is compiled, the trait cache will be purged. This cache is only used for the compilation of a single file and re-used for multiple uses of the same trait by the script or by its dependencies (either by theimplements
keyword or using them as types), so after the compilation is complete it needs to be cleared as we can't know when or if other scripts will be compiled or whether they will use the same traits.For variables/constants/enums, used traits will behave like copies and recompiled in the current class. This is so the constructor is done per class without having to rely on calling other functions compiled into traits. Usually the default values are not complex expression and it shouldn't be a problem compiling them multiple times (this case will be rare too).
If this enhancement will not be used often, can it be worked around with a few lines of script?
Considering all the requests, it will likely be used often. The work around for this can be quite clunky, especially if you are trying to validate interfaces and not only reuse code.
Is there a reason why this should be core and not an add-on in the asset library?
It is an integral part of GDScript, it cannot be implemented externally.
Footnotes
As an extra note, I would like to make
self
be transparent and work the same as not using it (unless used for scope resolution), since some people like to use it just for clarity and end up losing performance optimizations without realizing. But this is not related to this proposal in particular. ↩Note that
implements
won't be a reserved word to avoid breaking compatibility. The context will be enough to not confuse it with and identifier. ↩The text was updated successfully, but these errors were encountered: