### 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 even `Object`) 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, a `Sprite2D` 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:[^self]
```gdscript
# base.gd
extends Node
class_name Base
# -------
# sprite_funcs.gd
# Attached to a Sprite2D.
extends Base
func _ready() -> void:
# Here using `self` as a hack to use dynamic dispatch.
# Because GDScript won't know what `centered` is, since it's not on the inheritance tree
# and it does not know the attaced node.
self.centered = true
# -------
# body_funcs.gd
# Attached to a RigidBody2D
extends Base
# This method can't be validated in signature to match the super class since RigidBody2D is not being inherited.
# So it won't catch a potential user error.
func _integrate_forces(state: PhysicsDirectBodyState2D) -> void:
print(state)
```
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:**
- https://github.com/godotengine/godot/issues/23101
- https://github.com/godotengine/godot-proposals/issues/758
- https://github.com/godotengine/godot-proposals/issues/4872 (interfaces)
- https://github.com/godotengine/godot-proposals/issues/1346
- https://github.com/godotengine/godot-proposals/issues/1093 (multiple inheritance)
[^self]: 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.
### 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 a `Node` 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:
```gdscript
# script.gd
class_name TraitCollection
trait NestedTrait:
pass
```
That would be accessible with `TraitCollection.NestedTrait`.
Example of a trait file:
```gdscript
# trait.gdt
const some_constant = 0
var some_variable := "hello"
enum Values {
Value1,
Value2,
}
func some_func_with_implementation() -> void:
print("hello")
func some_abstract_function() -> void # No colon at end.
# This is independent of the outer trait, it won't be automatically used.
trait InnerTrait:
pass
```
I consider also using a `trait_name` keyword that would work similar to the `class_name` keyword, making the trait available by name in the global scope. I prefer not to reuse `class_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:
```gdscript
# trait.gdt
trait_name MyTrait
extends Node2D
func flip():
rotate(PI) # Works because `rotate()` is defined in Node2D.
# node3d.gd
extends Node3D
implements MyTrait # Error: MyTrait can only be used with scripts that extends Node2D.
# sprite2d.gd
extends Sprite2D
implements MyTrait # Valid: Sprite2D inherits from Node2D
```
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` keyword[^implements]. This should be added at the beginning of the class, together with `extends` and `class_name`.
```gdscript
# file.gd
extends Node.gd
implements "trait.gdt"
implements GlobalTrait
implements TraitCollection.NestedTrait
```
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:
```gdscript
# trait_a.gdt
trait_name TraitA
func method(a: int) -> int:
return 0
# trait_b.gdt
trait_name TraitB
func method(a: int) -> int:
return 1
# script.gd
implements TraitA, TraitB
func method(a: int) -> int:
return TraitA.method(a) # Or:
# return TraitB.method(a)
```
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:
```gdscript
# trait.gdt
var foo = "Hello"
func bar():
print("World")
# script.gd
implements "trait.gdt"
func _init():
print(foo) # Uses variable from trait.
bar() # Uses method from trait.
```
#### Are traits considered types?
Yes. Traits can be used as type specifications in variables:
```gdscript
var foo: SomeTrait
```
They can also be used in type checks and casts:
```gdscript
var foo := value as SomeTrait
if bar is OtherTrait:
print("do stuff")
```
To avoid polluting the global scope, the traits can also be preloaded like classes:
```gdscript
const SomeTrait = preload("traits/some_trait.gdt")
var foo: SomeTrait
if foo is SomeTrait:
print("yes")
```
**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 for `Node2D` 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. Using `extends` 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` and `as`, 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:
```gdscript
# trait.gdt
func foo(bar: int) -> void
# a.gd
class_name A
func foo(bar: int) -> void:
print(bar)
# b.gd
const MyTrait = preload("trait.gdt")
func _init():
var a := A.new()
print(a is MyTrait) # Prints 'false'.
```
While the class `A` implements a method `foo` with the same signature, it does not use `"trait.gdt"` so it won't be considered to be this trait when checking with `is` nor when casting with `as`. 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.
[^implements]: 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.
### 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 original `GDScriptTrait` 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` and `as` 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 the `GDScriptTrait` 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 the `implements` 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.