Skip to content

Commit e5cc00c

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

19 files changed

+138
-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
@@ -2763,6 +2763,7 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
27632763
"when",
27642764
"while",
27652765
// Declarations.
2766+
"abstract",
27662767
"class",
27672768
"class_name",
27682769
"const",

modules/gdscript/gdscript.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class GDScript : public Script {
6363
bool tool = false;
6464
bool valid = false;
6565
bool reloading = false;
66+
bool _is_abstract = false;
6667

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

247248
virtual bool is_valid() const override { return valid; }
248-
virtual bool is_abstract() const override { return false; } // GDScript does not support abstract classes.
249249

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

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

280280
bool is_tool() const override { return tool; }
281+
bool is_abstract() const override { return _is_abstract; }
281282
Ref<GDScript> get_base() const;
282283

283284
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
@@ -3562,10 +3562,15 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
35623562

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

3565-
if (is_constructor && Engine::get_singleton()->has_singleton(base_type.native_type)) {
3566-
push_error(vformat(R"(Cannot construct native class "%s" because it is an engine singleton.)", base_type.native_type), p_call);
3567-
p_call->set_datatype(call_type);
3568-
return;
3565+
if (is_constructor) {
3566+
if (Engine::get_singleton()->has_singleton(base_type.native_type)) {
3567+
push_error(vformat(R"(Cannot construct native class "%s" because it is an engine singleton.)", base_type.native_type), p_call);
3568+
p_call->set_datatype(call_type);
3569+
return;
3570+
}
3571+
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())) {
3572+
push_error(vformat(R"(Cannot construct abstract class "%s".)", base_type.to_string()), p_call);
3573+
}
35693574
}
35703575

35713576
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
@@ -1476,7 +1476,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context
14761476

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

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" 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();
@@ -4073,6 +4115,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty
40734115
{ nullptr, nullptr, PREC_NONE }, // MATCH,
40744116
{ nullptr, nullptr, PREC_NONE }, // WHEN,
40754117
// Keywords
4118+
{ nullptr, nullptr, PREC_NONE }, // ABSTRACT
40764119
{ nullptr, &GDScriptParser::parse_cast, PREC_CAST }, // AS,
40774120
{ nullptr, nullptr, PREC_NONE }, // ASSERT,
40784121
{ &GDScriptParser::parse_await, nullptr, PREC_NONE }, // AWAIT,
@@ -6263,17 +6306,21 @@ void GDScriptParser::TreePrinter::print_while(WhileNode *p_while) {
62636306
}
62646307

62656308
void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) {
6266-
ERR_FAIL_NULL_MSG(p_parser.get_tree(), "Parse the code before printing the parse tree.");
6309+
ClassNode *class_tree = p_parser.get_tree();
6310+
ERR_FAIL_NULL_MSG(class_tree, "Parse the code before printing the parse tree.");
62676311

62686312
if (p_parser.is_tool()) {
62696313
push_line("@tool");
62706314
}
6271-
if (!p_parser.get_tree()->icon_path.is_empty()) {
6315+
if (class_tree->is_abstract) {
6316+
push_line("@abstract");
6317+
}
6318+
if (!class_tree->icon_path.is_empty()) {
62726319
push_text(R"(@icon (")");
6273-
push_text(p_parser.get_tree()->icon_path);
6320+
push_text(class_tree->icon_path);
62746321
push_line("\")");
62756322
}
6276-
print_class(p_parser.get_tree());
6323+
print_class(class_tree);
62776324

62786325
print_line(String(printed));
62796326
}

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)