Skip to content

Commit c380cc2

Browse files
committed
Implement abstract methods in GDScript
1 parent 4dfafe0 commit c380cc2

File tree

3 files changed

+70
-11
lines changed

3 files changed

+70
-11
lines changed

modules/gdscript/gdscript_analyzer.cpp

+43-2
Original file line numberDiff line numberDiff line change
@@ -1405,6 +1405,41 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
14051405
resolve_pending_lambda_bodies();
14061406
}
14071407

1408+
// Resolve base abstract class/method implementation requirements.
1409+
if (!p_class->is_abstract) {
1410+
HashSet<StringName> implemented_funcs;
1411+
const GDScriptParser::ClassNode *base_class = p_class;
1412+
while (base_class != nullptr) {
1413+
if (!base_class->is_abstract && base_class != p_class) {
1414+
break;
1415+
}
1416+
for (GDScriptParser::ClassNode::Member member : base_class->members) {
1417+
if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) {
1418+
if (member.function->is_abstract) {
1419+
if (base_class == p_class) {
1420+
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.)*", p_class->identifier == nullptr ? p_class->fqcn.get_file() : String(p_class->identifier->name)), p_class);
1421+
break;
1422+
} else if (!implemented_funcs.has(member.function->identifier->name)) {
1423+
push_error(vformat(R"*(Class %s must implement "%s" and other methods from inherited abstract classes, or be marked as @abstract.)*", p_class->identifier == nullptr ? p_class->fqcn.get_file() : String(p_class->identifier->name), vformat(R"(%s::%s)", base_class->identifier == nullptr ? base_class->fqcn.get_file() : String(base_class->identifier->name), member.function->identifier->name)), p_class);
1424+
break;
1425+
}
1426+
} else {
1427+
implemented_funcs.insert(member.function->identifier->name);
1428+
}
1429+
}
1430+
}
1431+
if (base_class->base_type.kind == GDScriptParser::DataType::CLASS) {
1432+
base_class = base_class->base_type.class_type;
1433+
} else if (base_class->base_type.kind == GDScriptParser::DataType::SCRIPT) {
1434+
Ref<GDScriptParserRef> parser_ref = get_parser_for(base_class->base_type.script_path);
1435+
ERR_BREAK(parser_ref.is_null());
1436+
base_class = parser_ref->get_parser()->head;
1437+
} else {
1438+
break;
1439+
}
1440+
}
1441+
}
1442+
14081443
parser->current_class = previous_class;
14091444
}
14101445

@@ -1614,7 +1649,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
16141649
for (int i = 0; i < p_function->parameters.size(); i++) {
16151650
resolve_parameter(p_function->parameters[i]);
16161651
#ifdef DEBUG_ENABLED
1617-
if (p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_")) {
1652+
if (p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_") && !p_function->is_abstract) {
16181653
parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, function_visible_name, p_function->parameters[i]->identifier->name);
16191654
}
16201655
is_shadowing(p_function->parameters[i]->identifier, "function parameter", true);
@@ -1795,11 +1830,17 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun
17951830

17961831
resolve_suite(p_function->body);
17971832

1833+
if (p_function->is_abstract) {
1834+
if (p_function->body->statements.size() != 1 || p_function->body->statements[0]->type != GDScriptParser::Node::PASS) {
1835+
push_error(vformat(R"*(Abstract method %s cannot have an implementation. The method body must contain only a single "pass" statement.)*", p_function->identifier->name), p_function);
1836+
}
1837+
}
1838+
17981839
if (!p_function->get_datatype().is_hard_type() && p_function->body->get_datatype().is_set()) {
17991840
// Use the suite inferred type if return isn't explicitly set.
18001841
p_function->set_datatype(p_function->body->get_datatype());
18011842
} 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)) {
1802-
if (!p_function->body->has_return && (p_is_lambda || p_function->identifier->name != GDScriptLanguage::get_singleton()->strings._init)) {
1843+
if (!p_function->is_abstract && !p_function->body->has_return && (p_is_lambda || p_function->identifier->name != GDScriptLanguage::get_singleton()->strings._init)) {
18031844
push_error(R"(Not all code paths return a value.)", p_function);
18041845
}
18051846
}

modules/gdscript/gdscript_parser.cpp

+26-9
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ GDScriptParser::GDScriptParser() {
9393
register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation);
9494
register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);
9595
register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation);
96-
register_annotation(MethodInfo("@abstract"), AnnotationInfo::SCRIPT | AnnotationInfo::CLASS, &GDScriptParser::abstract_annotation);
96+
register_annotation(MethodInfo("@abstract"), AnnotationInfo::SCRIPT | AnnotationInfo::CLASS | AnnotationInfo::FUNCTION, &GDScriptParser::abstract_annotation);
9797

9898
register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
9999
// Export annotations.
@@ -3886,15 +3886,31 @@ bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p
38863886
}
38873887

38883888
bool GDScriptParser::abstract_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
3889-
ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, R"("@abstract" annotation can only be applied to classes.)");
3889+
// ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS && p_target->type != Node::FUNCTION, false, R"("@abstract" annotation can only be applied to classes and functions.)");
38903890
// Note: Use `p_target`, **not** `p_class`, because when `p_target` is a class then `p_class` refers to the parent class.
3891-
ClassNode *class_node = static_cast<ClassNode *>(p_target);
3892-
if (class_node->is_abstract) {
3893-
push_error(R"("@abstract" annotation can only be used once per class.)", p_annotation);
3894-
return false;
3891+
if (p_target->type == Node::CLASS) {
3892+
ClassNode *class_node = static_cast<ClassNode *>(p_target);
3893+
if (class_node->is_abstract) {
3894+
push_error(R"("@abstract" annotation can only be used once per class.)", p_annotation);
3895+
return false;
3896+
}
3897+
class_node->is_abstract = true;
3898+
return true;
38953899
}
3896-
class_node->is_abstract = true;
3897-
return true;
3900+
if (p_target->type == Node::FUNCTION) {
3901+
FunctionNode *function_node = static_cast<FunctionNode *>(p_target);
3902+
if (function_node->is_abstract) {
3903+
push_error(R"("@abstract" annotation can only be used once per method.)", p_annotation);
3904+
return false;
3905+
}
3906+
if (function_node->is_static) {
3907+
push_error(R"("@abstract" annotation cannot be used on static methods.)");
3908+
return false;
3909+
}
3910+
function_node->is_abstract = true;
3911+
return true;
3912+
}
3913+
ERR_FAIL_V_MSG(false, R"("@abstract" annotation can only be applied to classes and methods.)");
38983914
}
38993915

39003916
bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
@@ -5419,7 +5435,8 @@ void GDScriptParser::TreePrinter::print_while(WhileNode *p_while) {
54195435
}
54205436

54215437
void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) {
5422-
ERR_FAIL_NULL_MSG(p_parser.get_tree(), "Parse the code before printing the parse tree.");
5438+
GDScriptParser::ClassNode *class_tree = p_parser.get_tree();
5439+
ERR_FAIL_NULL_MSG(class_tree, "Parse the code before printing the parse tree.");
54235440

54245441
if (p_parser.is_tool()) {
54255442
push_line("@tool");

modules/gdscript/gdscript_parser.h

+1
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,7 @@ class GDScriptParser {
840840
TypeNode *return_type = nullptr;
841841
SuiteNode *body = nullptr;
842842
bool is_static = false;
843+
bool is_abstract = false;
843844
bool is_coroutine = false;
844845
Variant rpc_config;
845846
MethodInfo info;

0 commit comments

Comments
 (0)