KEMBAR78
Add abstract classes to GDScript by aaronfranke · Pull Request #67777 · godotengine/godot · GitHub
Skip to content

Conversation

aaronfranke
Copy link
Member

@aaronfranke aaronfranke commented Oct 23, 2022

Implements and closes godotengine/godot-proposals#5641

This PR adds a keyword for marking a script class as abstract in GDScript.

I tested multiple combinations of abstract and non-abstract inheritance and they all work. As an example, in this image MyAbstract is @abstract, while the other is not. Note that if ExtendsMyAbstract was made abstract then both would be hidden.

Screen Shot 2022-10-23 at 1 41 22 PM

@abstract class_name MyAbstract extends Node
class_name ExtendsMyAbstract extends MyAbstract

And here's what happens if you try to instance with .new():

Screen Shot 2022-10-23 at 3 12 40 PM

A previous version of the PR used @virtual, but the feedback has been overwhelming that abstract is better. After that, another previous version of this PR used an annotation @abstract, but it was discussed that a keyword is preferred, then discussed again in #107717 that an annotation is preferred.

Production edit: closes godotengine/godot-roadmap#66

@aaronfranke aaronfranke added this to the 4.0 milestone Oct 23, 2022
@aaronfranke aaronfranke requested review from a team as code owners October 23, 2022 02:27
@aaronfranke aaronfranke changed the title Add an annotation for virtual classes in GDScript Add an annotation for abstract classes in GDScript Oct 23, 2022
@aaronfranke aaronfranke force-pushed the virtually-annotated branch 3 times, most recently from 8daace1 to 36a62f0 Compare October 24, 2022 01:02
@aaronfranke aaronfranke modified the milestones: 4.0, 4.x Nov 6, 2022
@aaronfranke aaronfranke force-pushed the virtually-annotated branch from 74670c0 to 955143a Compare April 16, 2023 03:35
@ryanabx
Copy link
Contributor

ryanabx commented Sep 4, 2023

Are abstract methods supported with this PR?

@aaronfranke
Copy link
Member Author

@ryanabx No, only abstract classes are supported with this PR.

@dalexeev
Copy link
Member

dalexeev commented Sep 4, 2023

  1. I think the @abstract annotation should be possible to apply not only to the top-level script, but also to inner classes. Applying the annotation to a script should not make inner classes abstract.
  2. The check should be performed in release builds too.
  3. Perhaps this should be keyword abstract (like static and potential public/private) rather than annotation @abstract, especially if we add the ability to declare methods abstract (must be implemented in a child class so that it can be non-abstract).

@adamscott adamscott requested a review from vnen September 4, 2023 13:40
@adamscott
Copy link
Member

Talked about in the current GDScript meeting. The PR should implement the elements 1 and 2 stated by @dalexeev. For number 3, we're still discussing this in the meeting, so no decision has been made.

@anvilfolk
Copy link
Contributor

anvilfolk commented Sep 4, 2023

Yeah, my thoughts on 3. are whether it makes sense to (for this PR or eventually) have the "granularity" of abstractness exist at the class or at the method level. For example, with it at the method level:

class AbstractBecauseOfAbstractMethods:

func this_method_is_implemented():
    pass

@abstract
func this_method_is_virtual_slash_abstract()

func this_is_also_implemented():
    pass

# ----------------------

class StillAbstract extends AbstractBecauseOfAbstractMethods:

func some_new_implemented_func():
    pass

# ----------------------

class FinallyNotAbstractClass extends StillAbstract:

func this_method_is_virtual_slash_abstract():
    pass

We could force the users to annotate a class to be marked as abstract if any of its functions are still abstract.

This would give people more granularity in what precisely makes the class abstract. It doesn't make GDScript any more expressive, since you can always 1) tag a class as abstract, then 2) have methods implemented with assert(false) or pass, which would be functionally the same.

Folks agreed during the meeting that this wouldn't be a blocking thing for this PR, and that it could be implemented later :)

@aaronfranke
Copy link
Member Author

I will get to work on 1. For 2, this is trivial because it's just removing #ifdef DEBUG_ENABLED. To be clear I don't mind it in release builds, I just wanted to exclude it for improved performance, but if it's desired then we can do that. For 3, I think an annotation makes more sense than a keyword.

@Repiteo
Copy link
Contributor

Repiteo commented May 13, 2025

Thanks!

@LeBogoo
Copy link

LeBogoo commented May 13, 2025

Been waiting for abstract classes, thanks!

@Lazy-Rabbit-2001
Copy link
Contributor

The age has seen this moment, and this should be one of the most milestone in gd 4.5.

@aaronfranke aaronfranke deleted the virtually-annotated branch May 14, 2025 11:41
@nikitalita
Copy link
Contributor

Changing the token enum values requires bumping the bytecode version.

@Arsen-Lovecraft
Copy link

BIG W! Thank you!!!

@hmturnbull
Copy link

hmturnbull commented Jun 3, 2025

I have a question: so obviously you can't instantiate an abstract class using <ClassName>.new(), but is there a way to make a constructor in the abstract class itself?

For example,

func constructor() -> <ClassName>:
  var new_instance := new()
  … do stuff …
  return new_instance

Because it seems like that would be an important thing to be able to do.

Although, now I think of it, I wonder if some new keyword or other would be needed to make the constructor useful, since you'd need the function to return an object of the child class in the first place.

Would calling new() on its own (as in my example) accomplish this already, since it's not calling it specifically on the abstract class (as in AbstractClass.new())?

I don't know; just thinking out loud.

@Lazy-Rabbit-2001
Copy link
Contributor

I have a question: so obviously you can't instantiate an abstract class using <ClassName>.new(), but is there a way to make a constructor in the abstract class itself?

You can try it yourself to see if func _init() -> void: is allowed in an abstract class. If no then you can propose it in godot-proposal

But since a constructor in an abstract class is usually operated to provide a basic and default initialization for its derived classes, I don't think there will be other use cases for the constructor of an abstract class

@IceflowRE
Copy link
Contributor

IceflowRE commented Jun 3, 2025

Is there a way to define a abstract class without defining a global class_name (which iam not a fan of)?

@Snowdrama
Copy link

Snowdrama commented Jun 3, 2025

@IceflowRE
The original proposal mentions not using class_name a bit in the workaround section. If you're doing your inheritance manually without class_name than the workflow I don't believe has been changed by this, you would just explicitly extend the script file itself

So as an example you'd still mark the class as abstract in my_abstract_class.gd

@abstract
extends Node

func some_func():
    pass

then in my_class.gd you'd just manually import the abstract class file and extend it:

extends "my_abstract_class.gd"

And then the one catch being that you need to preload the classes in order to use language features like the is comparison

const MyAbstractClass = preload("my_abstract_class.gd")
const MyClass = preload("my_class.gd")

func test():
    var some_object = MyScript.new()
    if some_object is MyAbstractClass:
        print("some_object is a MyAbstractClass")
        some_object.some_func()

Something like that, I haven't actually checked or tested it but that's what I gather would be the workflow for not using class_name just based on reading the stuff in the proposal

Quick Edit: using the @abstract annotation itself may not be possible if it requires a class_name which may be the case I'm not actually 100% on that. If that's the case, then the only real downside currently is that you wouldn't be able to prevent instantiating a copy of my_abstract_class.gd which to my understanding is all this PR and the @abstract annotation does.

@azur-wolve
Copy link

there wont be virtual and override?

@aaronfranke
Copy link
Member Author

@azur-wolve That's not a part of this pull request. This pull request only implements abstract classes. Other features like those you mention can be done in another PR.

@Lazy-Rabbit-2001
Copy link
Contributor

@WolfgangSenff
Copy link
Contributor

I don't think the name abstract is good, because it creates a confusion with native abstract classes which behave differently (e.g. can't be inherited), this will create confusion when talking about it or documenting stuff about it.

Could someone link me to the previous version were virtual was discarded?

This is all fairly critically worded, but by no means is it intended to criticize anyone who has been involved in this PR or the proposal or the discussion here or anything of that nature: it is purely added in regards to the exact keyword used to achieve the results, and is hopefully more helpful than not to future work/discussions. So on with it...

I know this is already merged and closed, but wasn't sure where to put this: I completely agree with @HolonProduction (quoted) on this. There is another discussion going on in the gdscript chat, and I wanted to reiterate that abstract is not a good keyword here due to the fact that there can be Godot classes that are already abstract in C++ and hence cannot be inherited at all in GDScript, and which are functionally very different from GDScript-abstract. This came up when I believe user @Lazy-Rabbit-2001 brought up the fact that, in GDScript, there seems to be a bug where you can inherit PhysicsBody2D right now, which is Godot-C++-abstract, and should not be allowed, due to an explanation Holon provided which was very clear. While this certainly seems very confusing to me and a few others, I also have other concerns.

It worries me that GDScript seems to be migrating into a C++-like syntax, with all the complexities which that entails (such as virtual, override, abstract, etc.). Are we going to add pure_virtual, too (pls no)?

At the same time, I don't think interface as a keyword would work well either, since that can get confusing in C-sharp, even though (afaicr) it is not an actual keyword in C++, even if the concept does sort of exist in C++. For the class-specific keyword, I would probably use one of [undefined, incomplete, unimplemented, no_def], or something along those lines.

In addition to that, I saw someone ask if there would be an abstract variable potential? I don't really understand how that would be possible. A variable should always be concrete, even if its type is abstract: the actual value contained in the variable must always be a concrete value or null, else Godot could not construct it, after all. Variables should not be allowed to be abstract especially since the concept of abstraction only really applies to functions, in C++ and in most other languages I can think of. In GDScript, you can already achieve something similar using functions, since functions can be overridden.

@aaronfranke
Copy link
Member Author

aaronfranke commented Jun 11, 2025

@WolfgangSenff See this proposal: godotengine/godot-proposals#11791

I agree that the name mismatch is unfortunate, so I propose to swap the C++ names, or pick new names to avoid confusion.

GDScript isn't really "migrating into a C++-like syntax", more towards C# if anything. In C#, you can inherit abstract classes.

@OhiraKyou
Copy link
Contributor

If I were writing a GDScript class that I didn't want to be instantiated, I would expect the keyword I'd use to be abstract. As a user, I don't particularly care how it relates to C++ abstract.

@azur-wolve
Copy link

If I were writing a GDScript class that I didn't want to be instantiated, I would expect the keyword I'd use to be abstract. As a user, I don't particularly care how it relates to C++ abstract.

that reminds to C# sealed or C++/Java final

@OhiraKyou
Copy link
Contributor

OhiraKyou commented Jun 12, 2025

that reminds to C# sealed or C++/Java final

C# sealed prevents inheritance. abstract effectively forces it. So, the intent is roughly opposite. For sealed, see the new proposal: godotengine/godot-proposals#12591

@aaronfranke
Copy link
Member Author

@OhiraKyou The question is what to call the thing the engine already does where those classes cannot be inherited or instantiated.

@geekley
Copy link

geekley commented Jun 12, 2025

where those classes cannot be inherited or instantiated

@aaronfranke I know nothing about this or how "abstract" differs in C/C++ but...
Doesn't that sound like a C# static class? Or its implied sealed abstract class?
Static class (in C#) = cannot have instances, and all members must be static -> implies sealed and abstract
Sealed = no inheritance allowed
Abstract = no direct instantiation allowed

@akien-mga
Copy link
Member

akien-mga commented Jun 12, 2025

I think the current names are fine.

Abstract classes in C++ (in Godot) cannot be instantiated, but they can be inherited -- in C++. That's their purpose - CanvasItem can't be instantiated, but it's inherited by Node2D and Control, which can be instantiated.
What's missing (IIRC) is that this property is lost in ClassDB/GDScript so a custom GDScript classes extending CanvasItem still can't be instantiated. But that's just a C++ <> Scripting API issue, it doesn't invalidate the concept of abstract classes in C++.

Abstract classes in GDScript are similar, they cannot be instantiated but they can be inherited, and their derived classes can be instantiated, so everything is fine.

@aaronfranke
Copy link
Member Author

See #107717 for a follow-up discussion about changing this back to an annotation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for abstract classes in GDScript