Skip to content

Commit ebc36a7

Browse files
authored
Merge pull request #107717 from aaronfranke/abstract-annotation
GDScript: Replace `abstract` keyword with `@abstract` annotation
2 parents a39a83a + 1085200 commit ebc36a7

31 files changed

+179
-197
lines changed

modules/gdscript/doc_classes/@GDScript.xml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,27 @@
296296
</constant>
297297
</constants>
298298
<annotations>
299+
<annotation name="@abstract">
300+
<return type="void" />
301+
<description>
302+
Marks a class or a method as abstract.
303+
An abstract class is a class that cannot be instantiated directly. Instead, it is meant to be inherited by other classes. Attempting to instantiate an abstract class will result in an error.
304+
An abstract method is a method that has no implementation. Therefore, a newline or a semicolon is expected after the function header. This defines a contract that inheriting classes must conform to, because the method signature must be compatible when overriding.
305+
Inheriting classes must either provide implementations for all abstract methods, or the inheriting class must be marked as abstract. If a class has at least one abstract method (either its own or an unimplemented inherited one), then it must also be marked as abstract. However, the reverse is not true: an abstract class is allowed to have no abstract methods.
306+
[codeblock]
307+
@abstract class Shape:
308+
@abstract func draw()
309+
310+
class Circle extends Shape:
311+
func draw():
312+
print("Drawing a circle.")
313+
314+
class Square extends Shape:
315+
func draw():
316+
print("Drawing a square.")
317+
[/codeblock]
318+
</description>
319+
</annotation>
299320
<annotation name="@export">
300321
<return type="void" />
301322
<description>

modules/gdscript/gdscript.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2749,7 +2749,6 @@ Vector<String> GDScriptLanguage::get_reserved_words() const {
27492749
"when",
27502750
"while",
27512751
// Declarations.
2752-
"abstract",
27532752
"class",
27542753
"class_name",
27552754
"const",
@@ -2915,7 +2914,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
29152914
*r_icon_path = c->simplified_icon_path;
29162915
}
29172916
if (r_is_abstract) {
2918-
*r_is_abstract = false;
2917+
*r_is_abstract = c->is_abstract;
29192918
}
29202919
if (r_is_tool) {
29212920
*r_is_tool = parser.is_tool();

modules/gdscript/gdscript_analyzer.cpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1540,12 +1540,12 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
15401540
if (member.function->is_abstract) {
15411541
if (base_class == p_class) {
15421542
const String class_name = p_class->identifier == nullptr ? p_class->fqcn.get_file() : String(p_class->identifier->name);
1543-
push_error(vformat(R"*(Class "%s" is not abstract but contains abstract methods. Mark the class as abstract or remove "abstract" from all methods in this class.)*", class_name), p_class);
1543+
push_error(vformat(R"*(Class "%s" is not abstract but contains abstract methods. Mark the class as "@abstract" or remove "@abstract" from all methods in this class.)*", class_name), p_class);
15441544
break;
15451545
} else if (!implemented_funcs.has(member.function->identifier->name)) {
15461546
const String class_name = p_class->identifier == nullptr ? p_class->fqcn.get_file() : String(p_class->identifier->name);
15471547
const String base_class_name = base_class->identifier == nullptr ? base_class->fqcn.get_file() : String(base_class->identifier->name);
1548-
push_error(vformat(R"*(Class "%s" must implement "%s.%s()" and other inherited abstract methods or be marked as abstract.)*", class_name, base_class_name, member.function->identifier->name), p_class);
1548+
push_error(vformat(R"*(Class "%s" must implement "%s.%s()" and other inherited abstract methods or be marked as "@abstract".)*", class_name, base_class_name, member.function->identifier->name), p_class);
15491549
break;
15501550
}
15511551
} else {
@@ -1987,6 +1987,20 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun
19871987
}
19881988
p_function->resolved_body = true;
19891989

1990+
if (p_function->is_abstract) {
1991+
// Abstract functions don't have a body.
1992+
if (!p_function->body->statements.is_empty()) {
1993+
push_error(R"(Abstract function cannot have a body.)", p_function->body);
1994+
}
1995+
return;
1996+
} else {
1997+
// Non-abstract functions must have a body.
1998+
if (p_function->body->statements.is_empty()) {
1999+
push_error(R"(A function must either have a ":" followed by a body, or be marked as "@abstract".)", p_function);
2000+
return;
2001+
}
2002+
}
2003+
19902004
GDScriptParser::FunctionNode *previous_function = parser->current_function;
19912005
parser->current_function = p_function;
19922006

@@ -1999,7 +2013,7 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun
19992013
// Use the suite inferred type if return isn't explicitly set.
20002014
p_function->set_datatype(p_function->body->get_datatype());
20012015
} else if (p_function->get_datatype().is_hard_type() && (p_function->get_datatype().kind != GDScriptParser::DataType::BUILTIN || p_function->get_datatype().builtin_type != Variant::NIL)) {
2002-
if (!p_function->is_abstract && !p_function->body->has_return && (p_is_lambda || p_function->identifier->name != GDScriptLanguage::get_singleton()->strings._init)) {
2016+
if (!p_function->body->has_return && (p_is_lambda || p_function->identifier->name != GDScriptLanguage::get_singleton()->strings._init)) {
20032017
push_error(R"(Not all code paths return a value.)", p_function);
20042018
}
20052019
}

modules/gdscript/gdscript_editor.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1532,7 +1532,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context
15321532

15331533
static const char *_keywords_with_space[] = {
15341534
"and", "not", "or", "in", "as", "class", "class_name", "extends", "is", "func", "signal", "await",
1535-
"const", "enum", "abstract", "static", "var", "if", "elif", "else", "for", "match", "when", "while",
1535+
"const", "enum", "static", "var", "if", "elif", "else", "for", "match", "when", "while",
15361536
nullptr
15371537
};
15381538

0 commit comments

Comments
 (0)