Skip to content

Commit 1e2e52c

Browse files
aaronfrankedalexeev
andcommitted
Add a keyword for abstract classes in GDScript
Co-authored-by: Danil Alexeev <danil@alexeev.xyz>
1 parent b5bdb88 commit 1e2e52c

17 files changed

+135
-42
lines changed

doc/classes/ScriptExtension.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@
162162
<method name="_is_abstract" qualifiers="virtual const">
163163
<return type="bool" />
164164
<description>
165-
Returns [code]true[/code] if the script is an abstract script. An abstract script does not have a constructor and cannot be instantiated.
165+
Returns [code]true[/code] if the script is an abstract script. Abstract scripts cannot be instantiated directly, instead other scripts should inherit them. Abstract scripts will be either unselectable or hidden in the Create New Node dialog (unselectable if there are non-abstract classes inheriting it, otherwise hidden).
166166
</description>
167167
</method>
168168
<method name="_is_placeholder_fallback_enabled" qualifiers="virtual const">

editor/create_dialog.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ bool CreateDialog::_should_hide_type(const StringName &p_type) const {
195195
i = script_path.find_char('/', i + 1);
196196
}
197197
}
198+
// Abstract scripts cannot be instantiated.
199+
String path = ScriptServer::get_global_class_path(p_type);
200+
Ref<Script> scr = ResourceLoader::load(path, "Script");
201+
return scr->is_abstract();
198202
}
199203

200204
return false;

modules/gdscript/gdscript.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -2743,6 +2743,7 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
27432743
"when",
27442744
"while",
27452745
// Declarations.
2746+
"abstract",
27462747
"class",
27472748
"class_name",
27482749
"const",

modules/gdscript/gdscript.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class GDScript : public Script {
6262
bool tool = false;
6363
bool valid = false;
6464
bool reloading = false;
65+
bool _is_abstract = false;
6566

6667
struct MemberInfo {
6768
int index = 0;
@@ -244,7 +245,6 @@ class GDScript : public Script {
244245
void clear(GDScript::ClearData *p_clear_data = nullptr);
245246

246247
virtual bool is_valid() const override { return valid; }
247-
virtual bool is_abstract() const override { return false; } // GDScript does not support abstract classes.
248248

249249
bool inherits_script(const Ref<Script> &p_script) const override;
250250

@@ -277,6 +277,7 @@ class GDScript : public Script {
277277
virtual void get_script_signal_list(List<MethodInfo> *r_signals) const override;
278278

279279
bool is_tool() const override { return tool; }
280+
bool is_abstract() const override { return _is_abstract; }
280281
Ref<GDScript> get_base() const;
281282

282283
const HashMap<StringName, MemberInfo> &debug_get_member_indices() const { return member_indices; }

modules/gdscript/gdscript_analyzer.cpp

+9-4
Original file line numberDiff line numberDiff line change
@@ -3567,10 +3567,15 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
35673567

35683568
bool is_constructor = (base_type.is_meta_type || (p_call->callee && p_call->callee->type == GDScriptParser::Node::IDENTIFIER)) && p_call->function_name == SNAME("new");
35693569

3570-
if (is_constructor && Engine::get_singleton()->has_singleton(base_type.native_type)) {
3571-
push_error(vformat(R"(Cannot construct native class "%s" because it is an engine singleton.)", base_type.native_type), p_call);
3572-
p_call->set_datatype(call_type);
3573-
return;
3570+
if (is_constructor) {
3571+
if (Engine::get_singleton()->has_singleton(base_type.native_type)) {
3572+
push_error(vformat(R"(Cannot construct native class "%s" because it is an engine singleton.)", base_type.native_type), p_call);
3573+
p_call->set_datatype(call_type);
3574+
return;
3575+
}
3576+
if ((base_type.kind == GDScriptParser::DataType::CLASS && base_type.class_type->is_abstract) || (base_type.kind == GDScriptParser::DataType::SCRIPT && base_type.script_type.is_valid() && base_type.script_type->is_abstract())) {
3577+
push_error(vformat(R"(Cannot construct abstract class "%s".)", base_type.to_string()), p_call);
3578+
}
35743579
}
35753580

35763581
if (get_function_signature(p_call, is_constructor, base_type, p_call->function_name, return_type, par_types, default_arg_count, method_flags)) {

modules/gdscript/gdscript_compiler.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -2709,6 +2709,7 @@ Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptP
27092709
p_script->clearing = false;
27102710

27112711
p_script->tool = parser->is_tool();
2712+
p_script->_is_abstract = p_class->is_abstract;
27122713

27132714
if (p_script->local_name != StringName()) {
27142715
if (ClassDB::class_exists(p_script->local_name) && ClassDB::is_class_exposed(p_script->local_name)) {

modules/gdscript/gdscript_editor.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -1479,7 +1479,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context
14791479

14801480
static const char *_keywords_with_space[] = {
14811481
"and", "not", "or", "in", "as", "class", "class_name", "extends", "is", "func", "signal", "await",
1482-
"const", "enum", "static", "var", "if", "elif", "else", "for", "match", "when", "while",
1482+
"const", "enum", "abstract", "static", "var", "if", "elif", "else", "for", "match", "when", "while",
14831483
nullptr
14841484
};
14851485

modules/gdscript/gdscript_parser.cpp

+72-25
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,8 @@ void GDScriptParser::parse_program() {
633633
PUSH_PENDING_ANNOTATIONS_TO_HEAD;
634634
if (annotation->name == SNAME("@tool") || annotation->name == SNAME("@icon")) {
635635
// Some annotations need to be resolved and applied in the parser.
636-
annotation->apply(this, head, nullptr); // `head->outer == nullptr`.
636+
// The root class is not in any class, so `head->outer == nullptr`.
637+
annotation->apply(this, head, nullptr);
637638
} else {
638639
head->annotations.push_back(annotation);
639640
}
@@ -672,9 +673,25 @@ void GDScriptParser::parse_program() {
672673
}
673674
}
674675

676+
bool has_early_abstract = false;
675677
while (can_have_class_or_extends) {
676678
// Order here doesn't matter, but there should be only one of each at most.
677679
switch (current.type) {
680+
case GDScriptTokenizer::Token::ABSTRACT: {
681+
PUSH_PENDING_ANNOTATIONS_TO_HEAD;
682+
if (head->start_line == 1) {
683+
reset_extents(head, current);
684+
}
685+
advance();
686+
if (has_early_abstract) {
687+
push_error(R"(Expected "class_name", "extends", or "class" after "abstract".)");
688+
} else {
689+
has_early_abstract = true;
690+
}
691+
if (current.type == GDScriptTokenizer::Token::NEWLINE) {
692+
end_statement("class_name abstract");
693+
}
694+
} break;
678695
case GDScriptTokenizer::Token::CLASS_NAME:
679696
PUSH_PENDING_ANNOTATIONS_TO_HEAD;
680697
if (head->start_line == 1) {
@@ -686,6 +703,10 @@ void GDScriptParser::parse_program() {
686703
} else {
687704
parse_class_name();
688705
}
706+
if (has_early_abstract) {
707+
head->is_abstract = true;
708+
has_early_abstract = false;
709+
}
689710
break;
690711
case GDScriptTokenizer::Token::EXTENDS:
691712
PUSH_PENDING_ANNOTATIONS_TO_HEAD;
@@ -699,6 +720,10 @@ void GDScriptParser::parse_program() {
699720
parse_extends();
700721
end_statement("superclass");
701722
}
723+
if (has_early_abstract) {
724+
head->is_abstract = true;
725+
has_early_abstract = false;
726+
}
702727
break;
703728
case GDScriptTokenizer::Token::TK_EOF:
704729
PUSH_PENDING_ANNOTATIONS_TO_HEAD;
@@ -733,7 +758,7 @@ void GDScriptParser::parse_program() {
733758

734759
#undef PUSH_PENDING_ANNOTATIONS_TO_HEAD
735760

736-
parse_class_body(true);
761+
parse_class_body(has_early_abstract, true);
737762

738763
head->end_line = current.end_line;
739764
head->end_column = current.end_column;
@@ -838,12 +863,13 @@ bool GDScriptParser::has_class(const GDScriptParser::ClassNode *p_class) const {
838863
return false;
839864
}
840865

841-
GDScriptParser::ClassNode *GDScriptParser::parse_class(bool p_is_static) {
866+
GDScriptParser::ClassNode *GDScriptParser::parse_class(bool p_is_abstract, bool p_is_static) {
842867
ClassNode *n_class = alloc_node<ClassNode>();
843868

844869
ClassNode *previous_class = current_class;
845870
current_class = n_class;
846871
n_class->outer = previous_class;
872+
n_class->is_abstract = p_is_abstract;
847873

848874
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the class name after "class".)")) {
849875
n_class->identifier = parse_identifier();
@@ -880,7 +906,7 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class(bool p_is_static) {
880906
end_statement("superclass");
881907
}
882908

883-
parse_class_body(multiline);
909+
parse_class_body(false, multiline);
884910
complete_extents(n_class);
885911

886912
if (multiline) {
@@ -939,7 +965,7 @@ void GDScriptParser::parse_extends() {
939965
}
940966

941967
template <typename T>
942-
void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static) {
968+
void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(bool, bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_abstract, bool p_is_static) {
943969
advance();
944970

945971
// Consume annotations.
@@ -955,7 +981,7 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
955981
}
956982
}
957983

958-
T *member = (this->*p_parse_function)(p_is_static);
984+
T *member = (this->*p_parse_function)(p_is_abstract, p_is_static);
959985
if (member == nullptr) {
960986
return;
961987
}
@@ -1009,14 +1035,29 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
10091035
}
10101036
}
10111037

1012-
void GDScriptParser::parse_class_body(bool p_is_multiline) {
1038+
void GDScriptParser::parse_class_body(bool p_is_abstract, bool p_is_multiline) {
10131039
bool class_end = false;
1040+
// The header parsing code might have skipped over abstract, so we start by checking the previous token.
1041+
bool next_is_abstract = p_is_abstract;
1042+
if (next_is_abstract && (current.type != GDScriptTokenizer::Token::CLASS_NAME && current.type != GDScriptTokenizer::Token::CLASS)) {
1043+
push_error(R"(Expected "class_name" or "class" after "abstract".)");
1044+
}
10141045
bool next_is_static = false;
10151046
while (!class_end && !is_at_end()) {
10161047
GDScriptTokenizer::Token token = current;
10171048
switch (token.type) {
1049+
case GDScriptTokenizer::Token::ABSTRACT: {
1050+
advance();
1051+
next_is_abstract = true;
1052+
if (check(GDScriptTokenizer::Token::NEWLINE)) {
1053+
advance();
1054+
}
1055+
if (!check(GDScriptTokenizer::Token::CLASS_NAME) && !check(GDScriptTokenizer::Token::CLASS)) {
1056+
push_error(R"(Expected "class_name" or "class" after "abstract".)");
1057+
}
1058+
} break;
10181059
case GDScriptTokenizer::Token::VAR:
1019-
parse_class_member(&GDScriptParser::parse_variable, AnnotationInfo::VARIABLE, "variable", next_is_static);
1060+
parse_class_member(&GDScriptParser::parse_variable, AnnotationInfo::VARIABLE, "variable", false, next_is_static);
10201061
if (next_is_static) {
10211062
current_class->has_static_data = true;
10221063
}
@@ -1028,11 +1069,12 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
10281069
parse_class_member(&GDScriptParser::parse_signal, AnnotationInfo::SIGNAL, "signal");
10291070
break;
10301071
case GDScriptTokenizer::Token::FUNC:
1031-
parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function", next_is_static);
1032-
break;
1033-
case GDScriptTokenizer::Token::CLASS:
1034-
parse_class_member(&GDScriptParser::parse_class, AnnotationInfo::CLASS, "class");
1072+
parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function", false, next_is_static);
10351073
break;
1074+
case GDScriptTokenizer::Token::CLASS: {
1075+
parse_class_member(&GDScriptParser::parse_class, AnnotationInfo::CLASS, "class", next_is_abstract);
1076+
next_is_abstract = false;
1077+
} break;
10361078
case GDScriptTokenizer::Token::ENUM:
10371079
parse_class_member(&GDScriptParser::parse_enum, AnnotationInfo::NONE, "enum");
10381080
break;
@@ -1103,11 +1145,11 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
11031145
}
11041146
}
11051147

1106-
GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static) {
1107-
return parse_variable(p_is_static, true);
1148+
GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_abstract, bool p_is_static) {
1149+
return parse_variable(p_is_abstract, p_is_static, true);
11081150
}
11091151

1110-
GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, bool p_allow_property) {
1152+
GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_abstract, bool p_is_static, bool p_allow_property) {
11111153
VariableNode *variable = alloc_node<VariableNode>();
11121154

11131155
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) {
@@ -1343,7 +1385,7 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
13431385
}
13441386
}
13451387

1346-
GDScriptParser::ConstantNode *GDScriptParser::parse_constant(bool p_is_static) {
1388+
GDScriptParser::ConstantNode *GDScriptParser::parse_constant(bool p_is_abstract, bool p_is_static) {
13471389
ConstantNode *constant = alloc_node<ConstantNode>();
13481390

13491391
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) {
@@ -1411,7 +1453,7 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() {
14111453
return parameter;
14121454
}
14131455

1414-
GDScriptParser::SignalNode *GDScriptParser::parse_signal(bool p_is_static) {
1456+
GDScriptParser::SignalNode *GDScriptParser::parse_signal(bool p_is_abstract, bool p_is_static) {
14151457
SignalNode *signal = alloc_node<SignalNode>();
14161458

14171459
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected signal name after "signal".)")) {
@@ -1456,7 +1498,7 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal(bool p_is_static) {
14561498
return signal;
14571499
}
14581500

1459-
GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
1501+
GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_abstract, bool p_is_static) {
14601502
EnumNode *enum_node = alloc_node<EnumNode>();
14611503
bool named = false;
14621504

@@ -1609,7 +1651,7 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod
16091651
consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after %s declaration.)", p_type));
16101652
}
16111653

1612-
GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_static) {
1654+
GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_abstract, bool p_is_static) {
16131655
FunctionNode *function = alloc_node<FunctionNode>();
16141656

16151657
make_completion_context(COMPLETION_OVERRIDE_METHOD, function);
@@ -1874,11 +1916,11 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
18741916
break;
18751917
case GDScriptTokenizer::Token::VAR:
18761918
advance();
1877-
result = parse_variable(false, false);
1919+
result = parse_variable(false, false, false);
18781920
break;
18791921
case GDScriptTokenizer::Token::CONST:
18801922
advance();
1881-
result = parse_constant(false);
1923+
result = parse_constant(false, false);
18821924
break;
18831925
case GDScriptTokenizer::Token::IF:
18841926
advance();
@@ -4075,6 +4117,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty
40754117
{ nullptr, nullptr, PREC_NONE }, // MATCH,
40764118
{ nullptr, nullptr, PREC_NONE }, // WHEN,
40774119
// Keywords
4120+
{ nullptr, nullptr, PREC_NONE }, // ABSTRACT
40784121
{ nullptr, &GDScriptParser::parse_cast, PREC_CAST }, // AS,
40794122
{ nullptr, nullptr, PREC_NONE }, // ASSERT,
40804123
{ &GDScriptParser::parse_await, nullptr, PREC_NONE }, // AWAIT,
@@ -6265,17 +6308,21 @@ void GDScriptParser::TreePrinter::print_while(WhileNode *p_while) {
62656308
}
62666309

62676310
void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) {
6268-
ERR_FAIL_NULL_MSG(p_parser.get_tree(), "Parse the code before printing the parse tree.");
6311+
ClassNode *class_tree = p_parser.get_tree();
6312+
ERR_FAIL_NULL_MSG(class_tree, "Parse the code before printing the parse tree.");
62696313

62706314
if (p_parser.is_tool()) {
62716315
push_line("@tool");
62726316
}
6273-
if (!p_parser.get_tree()->icon_path.is_empty()) {
6317+
if (class_tree->is_abstract) {
6318+
push_line("abstract");
6319+
}
6320+
if (!class_tree->icon_path.is_empty()) {
62746321
push_text(R"(@icon (")");
6275-
push_text(p_parser.get_tree()->icon_path);
6322+
push_text(class_tree->icon_path);
62766323
push_line("\")");
62776324
}
6278-
print_class(p_parser.get_tree());
6325+
print_class(class_tree);
62796326

62806327
print_line(String(printed));
62816328
}

modules/gdscript/gdscript_parser.h

+10-9
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,7 @@ class GDScriptParser {
748748
ClassNode *outer = nullptr;
749749
bool extends_used = false;
750750
bool onready_used = false;
751+
bool is_abstract = false;
751752
bool has_static_data = false;
752753
bool annotated_static_unload = false;
753754
String extends_path;
@@ -1488,16 +1489,16 @@ class GDScriptParser {
14881489

14891490
// Main blocks.
14901491
void parse_program();
1491-
ClassNode *parse_class(bool p_is_static);
1492+
ClassNode *parse_class(bool p_is_abstract, bool p_is_static);
14921493
void parse_class_name();
14931494
void parse_extends();
1494-
void parse_class_body(bool p_is_multiline);
1495+
void parse_class_body(bool p_is_abstract, bool p_is_multiline);
14951496
template <typename T>
1496-
void parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static = false);
1497-
SignalNode *parse_signal(bool p_is_static);
1498-
EnumNode *parse_enum(bool p_is_static);
1497+
void parse_class_member(T *(GDScriptParser::*p_parse_function)(bool, bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_abstract = false, bool p_is_static = false);
1498+
SignalNode *parse_signal(bool p_is_abstract, bool p_is_static);
1499+
EnumNode *parse_enum(bool p_is_abstract, bool p_is_static);
14991500
ParameterNode *parse_parameter();
1500-
FunctionNode *parse_function(bool p_is_static);
1501+
FunctionNode *parse_function(bool p_is_abstract, bool p_is_static);
15011502
void parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type);
15021503
SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr, bool p_for_lambda = false);
15031504
// Annotations
@@ -1521,12 +1522,12 @@ class GDScriptParser {
15211522
bool rpc_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
15221523
// Statements.
15231524
Node *parse_statement();
1524-
VariableNode *parse_variable(bool p_is_static);
1525-
VariableNode *parse_variable(bool p_is_static, bool p_allow_property);
1525+
VariableNode *parse_variable(bool p_is_abstract, bool p_is_static);
1526+
VariableNode *parse_variable(bool p_is_abstract, bool p_is_static, bool p_allow_property);
15261527
VariableNode *parse_property(VariableNode *p_variable, bool p_need_indent);
15271528
void parse_property_getter(VariableNode *p_variable);
15281529
void parse_property_setter(VariableNode *p_variable);
1529-
ConstantNode *parse_constant(bool p_is_static);
1530+
ConstantNode *parse_constant(bool p_is_abstract, bool p_is_static);
15301531
AssertNode *parse_assert();
15311532
BreakNode *parse_break();
15321533
ContinueNode *parse_continue();

0 commit comments

Comments
 (0)