godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.12k stars 69 forks source link

Add a `@final` method annotation to GDScript #10500

Open ObaniGemini opened 1 month ago

ObaniGemini commented 1 month ago

Describe the project you are working on

I'm working on a project that heavily relies on modding, and making a GDScript API usable for making mods. In that context, I make base classes that are used for creating characters, levels, background elements, etc...

Because of Godot's way of working, I can hide these base classes's API and create some kind of "header" GDScript files that are used in a project made for modders.

Modders export the pck and their mod is then imported as a resource pack that can't override the game's base files, so it is properly linked with the game's internal API.

Describe the problem or limitation you are having in your project

Because my base classes's internal behaviors are hidden, and also because I don't want people to break the base classes (intentionally or not), I want to prevent the users from removing certain behaviors.

But GDScript permits to easily remove a function by just overriding it and not calling super, because every script function is virtual.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

An annotation with a behavior similar to C++/Java's final keyword would permit to add a new constraint to avoid this kind of issues. I don't want most functions to not be overrideable, but even _physics_process and _process functions would need that treatment.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

As godot already has annotations, I think a @final (or any other fitting keyword) annotation would greatly help. It would also permit making safer APIs in some complex inheritance cases.

If this enhancement will not be used often, can it be worked around with a few lines of script?

I haven't found a way to do this in godot yet.

Is there a reason why this should be core and not an add-on in the asset library?

This is a scripting feature, and I don't think it can be added as an add-on.

dalexeev commented 1 month ago

Personally, I would prefer virtual/override over final. Usually you don't have many methods that are meant to be overridden, you don't want to allow arbitrary methods to be overridden. final by default makes more sense to me. Otherwise you would be forced to use final with almost every method, and silently not use it when you want to make a method overridable.

Of course, we can't make this an error for compatibility reasons. But we could make it a warning, and users would have a choice whether to treat the warning as error or disable it.

class A:
    func f1(): pass
    @virtual func f2(): pass
    @virtual func f3(): pass
    # No func f4().
    # No func f5().

class B extends A:
    func f1(): pass # Warning: Overriding a non-virtual method.
    func f2(): pass # Warning: Overriding a method without @override.
    @override func f3(): pass # OK.
    @override func f4(): pass # Error: Overriding a non-existent method.
    func f5(): pass # OK.
ObaniGemini commented 1 month ago

Of course, we can't make this an error for compatibility reasons. But we could make it a warning, and users would have a choice whether to treat the warning as error or disable it.

If this doesn't trigger an error but only a warning then it loses the point of the thing, since everything would be virtual anyway for compatibility reasons. It wouldn't permit to prevent overriding since it would just trigger a warning, and as I said, while this would be good QoL, the point is mostly for the case where you want to provide a GDScript API that shouldn't be redefined.

I think you're right that this @virtual/@override annotation would make more sense but as you said it, it contradicts the language's base principle.

Also, since GDScript is heavily object-oriented and requires function overriding most of the time (for _ready, _process, etc... functions), I don't know if deviating from that would be a good thing (from a UX perspective, not from a perf perspective).

The @final annotation would have the advantage of not contradicting any of the language's current design principles (I think?)

dalexeev commented 1 month ago

No, I don't think it contradicts the language principles. We already have a group of warnings that are disabled by default (UNTYPED_DECLARATION, INFERRED_DECLARATION, UNSAFE_*). Each warning has three levels: Ignore, Warn, Error. Accordingly, users who want to get a stricter version of the language have the opportunity to do so through the Project Settings. This does not break compatibility.