Merge pull request #106409 from dalexeev/gds-add-abstract-methods
GDScript: Add abstract methods
This commit is contained in:
@ -3083,7 +3083,7 @@
|
||||
Used internally. Allows to not dump core virtual methods (such as [method Object._notification]) to the JSON API.
|
||||
</constant>
|
||||
<constant name="METHOD_FLAG_VIRTUAL_REQUIRED" value="128" enum="MethodFlags" is_bitfield="true">
|
||||
Flag for a virtual method that is required.
|
||||
Flag for a virtual method that is required. In GDScript, this flag is set for abstract functions.
|
||||
</constant>
|
||||
<constant name="METHOD_FLAGS_DEFAULT" value="1" enum="MethodFlags" is_bitfield="true">
|
||||
Default method flags (normal).
|
||||
|
||||
@ -154,6 +154,8 @@ static void _add_qualifiers_to_rt(const String &p_qualifiers, RichTextLabel *p_r
|
||||
hint = TTR("This method has no side effects.\nIt does not modify the object in any way.");
|
||||
} else if (qualifier == "static") {
|
||||
hint = TTR("This method does not need an instance to be called.\nIt can be called directly using the class name.");
|
||||
} else if (qualifier == "abstract") {
|
||||
hint = TTR("This method must be implemented to complete the abstract class.");
|
||||
}
|
||||
|
||||
p_rt->add_text(" ");
|
||||
|
||||
@ -407,7 +407,15 @@ void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_
|
||||
method_doc.deprecated_message = m_func->doc_data.deprecated_message;
|
||||
method_doc.is_experimental = m_func->doc_data.is_experimental;
|
||||
method_doc.experimental_message = m_func->doc_data.experimental_message;
|
||||
method_doc.qualifiers = m_func->is_static ? "static" : "";
|
||||
|
||||
// Currently, an abstract function cannot be static.
|
||||
if (m_func->is_abstract) {
|
||||
method_doc.qualifiers = "abstract";
|
||||
} else if (m_func->is_static) {
|
||||
method_doc.qualifiers = "static";
|
||||
} else {
|
||||
method_doc.qualifiers = "";
|
||||
}
|
||||
|
||||
if (func_name == "_init") {
|
||||
method_doc.return_type = "void";
|
||||
|
||||
@ -1527,6 +1527,44 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
|
||||
resolve_pending_lambda_bodies();
|
||||
}
|
||||
|
||||
// Resolve base abstract class/method implementation requirements.
|
||||
if (!p_class->is_abstract) {
|
||||
HashSet<StringName> implemented_funcs;
|
||||
const GDScriptParser::ClassNode *base_class = p_class;
|
||||
while (base_class != nullptr) {
|
||||
if (!base_class->is_abstract && base_class != p_class) {
|
||||
break;
|
||||
}
|
||||
for (GDScriptParser::ClassNode::Member member : base_class->members) {
|
||||
if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) {
|
||||
if (member.function->is_abstract) {
|
||||
if (base_class == p_class) {
|
||||
const String class_name = p_class->identifier == nullptr ? p_class->fqcn.get_file() : String(p_class->identifier->name);
|
||||
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);
|
||||
break;
|
||||
} else if (!implemented_funcs.has(member.function->identifier->name)) {
|
||||
const String class_name = p_class->identifier == nullptr ? p_class->fqcn.get_file() : String(p_class->identifier->name);
|
||||
const String base_class_name = base_class->identifier == nullptr ? base_class->fqcn.get_file() : String(base_class->identifier->name);
|
||||
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);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
implemented_funcs.insert(member.function->identifier->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (base_class->base_type.kind == GDScriptParser::DataType::CLASS) {
|
||||
base_class = base_class->base_type.class_type;
|
||||
} else if (base_class->base_type.kind == GDScriptParser::DataType::SCRIPT) {
|
||||
Ref<GDScriptParserRef> base_parser_ref = parser->get_depended_parser_for(base_class->base_type.script_path);
|
||||
ERR_BREAK(base_parser_ref.is_null());
|
||||
base_class = base_parser_ref->get_parser()->head;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parser->current_class = previous_class;
|
||||
}
|
||||
|
||||
@ -1741,7 +1779,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *
|
||||
resolve_parameter(p_function->parameters[i]);
|
||||
method_info.arguments.push_back(p_function->parameters[i]->get_datatype().to_property_info(p_function->parameters[i]->identifier->name));
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_")) {
|
||||
if (p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_") && !p_function->is_abstract) {
|
||||
parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, function_visible_name, p_function->parameters[i]->identifier->name);
|
||||
}
|
||||
is_shadowing(p_function->parameters[i]->identifier, "function parameter", true);
|
||||
@ -1920,7 +1958,7 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun
|
||||
// Use the suite inferred type if return isn't explicitly set.
|
||||
p_function->set_datatype(p_function->body->get_datatype());
|
||||
} 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)) {
|
||||
if (!p_function->body->has_return && (p_is_lambda || p_function->identifier->name != GDScriptLanguage::get_singleton()->strings._init)) {
|
||||
if (!p_function->is_abstract && !p_function->body->has_return && (p_is_lambda || p_function->identifier->name != GDScriptLanguage::get_singleton()->strings._init)) {
|
||||
push_error(R"(Not all code paths return a value.)", p_function);
|
||||
}
|
||||
}
|
||||
@ -3585,11 +3623,15 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
|
||||
}
|
||||
|
||||
if (get_function_signature(p_call, is_constructor, base_type, p_call->function_name, return_type, par_types, default_arg_count, method_flags)) {
|
||||
// If the method is implemented in the class hierarchy, the virtual flag will not be set for that MethodInfo and the search stops there.
|
||||
// Virtual check only possible for super() calls because class hierarchy is known. Node/Objects may have scripts attached we don't know of at compile-time.
|
||||
p_call->is_static = method_flags.has_flag(METHOD_FLAG_STATIC);
|
||||
if (p_call->is_super && method_flags.has_flag(METHOD_FLAG_VIRTUAL)) {
|
||||
push_error(vformat(R"*(Cannot call the parent class' virtual function "%s()" because it hasn't been defined.)*", p_call->function_name), p_call);
|
||||
// If the method is implemented in the class hierarchy, the virtual/abstract flag will not be set for that `MethodInfo` and the search stops there.
|
||||
// Virtual/abstract check only possible for super calls because class hierarchy is known. Objects may have scripts attached we don't know of at compile-time.
|
||||
if (p_call->is_super) {
|
||||
if (method_flags.has_flag(METHOD_FLAG_VIRTUAL)) {
|
||||
push_error(vformat(R"*(Cannot call the parent class' virtual function "%s()" because it hasn't been defined.)*", p_call->function_name), p_call);
|
||||
} else if (method_flags.has_flag(METHOD_FLAG_VIRTUAL_REQUIRED)) {
|
||||
push_error(vformat(R"*(Cannot call the parent class' abstract function "%s()" because it hasn't been defined.)*", p_call->function_name), p_call);
|
||||
}
|
||||
}
|
||||
|
||||
// If the function requires typed arrays we must make literals be typed.
|
||||
@ -5799,6 +5841,9 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo
|
||||
}
|
||||
|
||||
if (found_function != nullptr) {
|
||||
if (found_function->is_abstract) {
|
||||
r_method_flags.set_flag(METHOD_FLAG_VIRTUAL_REQUIRED);
|
||||
}
|
||||
if (p_is_constructor || found_function->is_static) {
|
||||
r_method_flags.set_flag(METHOD_FLAG_STATIC);
|
||||
}
|
||||
|
||||
@ -2254,6 +2254,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
|
||||
codegen.function_node = p_func;
|
||||
|
||||
StringName func_name;
|
||||
bool is_abstract = false;
|
||||
bool is_static = false;
|
||||
Variant rpc_config;
|
||||
GDScriptDataType return_type;
|
||||
@ -2267,6 +2268,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
|
||||
} else {
|
||||
func_name = "<anonymous lambda>";
|
||||
}
|
||||
is_abstract = p_func->is_abstract;
|
||||
is_static = p_func->is_static;
|
||||
rpc_config = p_func->rpc_config;
|
||||
return_type = _gdtype_from_datatype(p_func->get_datatype(), p_script);
|
||||
@ -2283,6 +2285,9 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
|
||||
codegen.function_name = func_name;
|
||||
method_info.name = func_name;
|
||||
codegen.is_static = is_static;
|
||||
if (is_abstract) {
|
||||
method_info.flags |= METHOD_FLAG_VIRTUAL_REQUIRED;
|
||||
}
|
||||
if (is_static) {
|
||||
method_info.flags |= METHOD_FLAG_STATIC;
|
||||
}
|
||||
|
||||
@ -674,23 +674,50 @@ void GDScriptParser::parse_program() {
|
||||
reset_extents(head, current);
|
||||
}
|
||||
|
||||
bool has_early_abstract = false;
|
||||
bool first_is_abstract = false;
|
||||
while (can_have_class_or_extends) {
|
||||
// Order here doesn't matter, but there should be only one of each at most.
|
||||
switch (current.type) {
|
||||
case GDScriptTokenizer::Token::ABSTRACT: {
|
||||
PUSH_PENDING_ANNOTATIONS_TO_HEAD;
|
||||
if (head->start_line == 1) {
|
||||
reset_extents(head, current);
|
||||
if (head->is_abstract) {
|
||||
// The root class is already marked as abstract, so this is
|
||||
// the beginning of an abstract function or inner class.
|
||||
can_have_class_or_extends = false;
|
||||
break;
|
||||
}
|
||||
|
||||
const GDScriptTokenizer::Token abstract_token = current;
|
||||
advance();
|
||||
if (has_early_abstract) {
|
||||
push_error(R"(Expected "class_name", "extends", or "class" after "abstract".)");
|
||||
} else {
|
||||
has_early_abstract = true;
|
||||
}
|
||||
|
||||
// A standalone "abstract" is only allowed for script-level stuff.
|
||||
bool is_standalone = false;
|
||||
if (current.type == GDScriptTokenizer::Token::NEWLINE) {
|
||||
end_statement("class_name abstract");
|
||||
is_standalone = true;
|
||||
end_statement("standalone \"abstract\"");
|
||||
}
|
||||
|
||||
switch (current.type) {
|
||||
case GDScriptTokenizer::Token::CLASS_NAME:
|
||||
case GDScriptTokenizer::Token::EXTENDS:
|
||||
PUSH_PENDING_ANNOTATIONS_TO_HEAD;
|
||||
head->is_abstract = true;
|
||||
if (head->start_line == 1) {
|
||||
reset_extents(head, abstract_token);
|
||||
}
|
||||
break;
|
||||
case GDScriptTokenizer::Token::CLASS:
|
||||
case GDScriptTokenizer::Token::FUNC:
|
||||
if (is_standalone) {
|
||||
push_error(R"(Expected "class_name" or "extends" after a standalone "abstract".)");
|
||||
} else {
|
||||
first_is_abstract = true;
|
||||
}
|
||||
// This is the beginning of an abstract function or inner class.
|
||||
can_have_class_or_extends = false;
|
||||
break;
|
||||
default:
|
||||
push_error(R"(Expected "class_name", "extends", "class", or "func" after "abstract".)");
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
case GDScriptTokenizer::Token::CLASS_NAME:
|
||||
@ -701,10 +728,6 @@ void GDScriptParser::parse_program() {
|
||||
} else {
|
||||
parse_class_name();
|
||||
}
|
||||
if (has_early_abstract) {
|
||||
head->is_abstract = true;
|
||||
has_early_abstract = false;
|
||||
}
|
||||
break;
|
||||
case GDScriptTokenizer::Token::EXTENDS:
|
||||
PUSH_PENDING_ANNOTATIONS_TO_HEAD;
|
||||
@ -715,10 +738,6 @@ void GDScriptParser::parse_program() {
|
||||
parse_extends();
|
||||
end_statement("superclass");
|
||||
}
|
||||
if (has_early_abstract) {
|
||||
head->is_abstract = true;
|
||||
has_early_abstract = false;
|
||||
}
|
||||
break;
|
||||
case GDScriptTokenizer::Token::TK_EOF:
|
||||
PUSH_PENDING_ANNOTATIONS_TO_HEAD;
|
||||
@ -753,7 +772,7 @@ void GDScriptParser::parse_program() {
|
||||
|
||||
#undef PUSH_PENDING_ANNOTATIONS_TO_HEAD
|
||||
|
||||
parse_class_body(has_early_abstract, true);
|
||||
parse_class_body(first_is_abstract, true);
|
||||
|
||||
head->end_line = current.end_line;
|
||||
head->end_column = current.end_column;
|
||||
@ -1028,13 +1047,10 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
|
||||
}
|
||||
}
|
||||
|
||||
void GDScriptParser::parse_class_body(bool p_is_abstract, bool p_is_multiline) {
|
||||
void GDScriptParser::parse_class_body(bool p_first_is_abstract, bool p_is_multiline) {
|
||||
bool class_end = false;
|
||||
// The header parsing code might have skipped over abstract, so we start by checking the previous token.
|
||||
bool next_is_abstract = p_is_abstract;
|
||||
if (next_is_abstract && (current.type != GDScriptTokenizer::Token::CLASS_NAME && current.type != GDScriptTokenizer::Token::CLASS)) {
|
||||
push_error(R"(Expected "class_name" or "class" after "abstract".)");
|
||||
}
|
||||
// The header parsing code could consume `abstract` for the first function or inner class.
|
||||
bool next_is_abstract = p_first_is_abstract;
|
||||
bool next_is_static = false;
|
||||
while (!class_end && !is_at_end()) {
|
||||
GDScriptTokenizer::Token token = current;
|
||||
@ -1042,11 +1058,8 @@ void GDScriptParser::parse_class_body(bool p_is_abstract, bool p_is_multiline) {
|
||||
case GDScriptTokenizer::Token::ABSTRACT: {
|
||||
advance();
|
||||
next_is_abstract = true;
|
||||
if (check(GDScriptTokenizer::Token::NEWLINE)) {
|
||||
advance();
|
||||
}
|
||||
if (!check(GDScriptTokenizer::Token::CLASS_NAME) && !check(GDScriptTokenizer::Token::CLASS)) {
|
||||
push_error(R"(Expected "class_name" or "class" after "abstract".)");
|
||||
if (!check(GDScriptTokenizer::Token::CLASS) && !check(GDScriptTokenizer::Token::FUNC)) {
|
||||
push_error(R"(Expected "class" or "func" after "abstract".)");
|
||||
}
|
||||
} break;
|
||||
case GDScriptTokenizer::Token::VAR:
|
||||
@ -1062,12 +1075,11 @@ void GDScriptParser::parse_class_body(bool p_is_abstract, bool p_is_multiline) {
|
||||
parse_class_member(&GDScriptParser::parse_signal, AnnotationInfo::SIGNAL, "signal");
|
||||
break;
|
||||
case GDScriptTokenizer::Token::FUNC:
|
||||
parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function", false, next_is_static);
|
||||
parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function", next_is_abstract, next_is_static);
|
||||
break;
|
||||
case GDScriptTokenizer::Token::CLASS: {
|
||||
case GDScriptTokenizer::Token::CLASS:
|
||||
parse_class_member(&GDScriptParser::parse_class, AnnotationInfo::CLASS, "class", next_is_abstract);
|
||||
next_is_abstract = false;
|
||||
} break;
|
||||
break;
|
||||
case GDScriptTokenizer::Token::ENUM:
|
||||
parse_class_member(&GDScriptParser::parse_enum, AnnotationInfo::NONE, "enum");
|
||||
break;
|
||||
@ -1146,6 +1158,9 @@ void GDScriptParser::parse_class_body(bool p_is_abstract, bool p_is_multiline) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (token.type != GDScriptTokenizer::Token::ABSTRACT) {
|
||||
next_is_abstract = false;
|
||||
}
|
||||
if (token.type != GDScriptTokenizer::Token::STATIC) {
|
||||
next_is_static = false;
|
||||
}
|
||||
@ -1662,18 +1677,23 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (p_type == "function" && p_signature_start != -1) {
|
||||
int signature_end_pos = tokenizer->get_current_position() - 1;
|
||||
String source_code = tokenizer->get_source_code();
|
||||
p_function->signature = source_code.substr(p_signature_start, signature_end_pos - p_signature_start);
|
||||
const int signature_end_pos = tokenizer->get_current_position() - 1;
|
||||
const String source_code = tokenizer->get_source_code();
|
||||
p_function->signature = source_code.substr(p_signature_start, signature_end_pos - p_signature_start).strip_edges(false, true);
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
|
||||
consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after %s declaration.)", p_type));
|
||||
if (p_function->is_abstract) {
|
||||
end_statement("abstract function declaration");
|
||||
} else {
|
||||
// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
|
||||
consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after %s declaration.)", p_type));
|
||||
}
|
||||
}
|
||||
|
||||
GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_abstract, bool p_is_static) {
|
||||
FunctionNode *function = alloc_node<FunctionNode>();
|
||||
function->is_abstract = p_is_abstract;
|
||||
function->is_static = p_is_static;
|
||||
|
||||
make_completion_context(COMPLETION_OVERRIDE_METHOD, function);
|
||||
@ -1714,7 +1734,13 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_abstract,
|
||||
function->min_local_doc_line = previous.end_line + 1;
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
function->body = parse_suite("function declaration", body);
|
||||
if (function->is_abstract) {
|
||||
reset_extents(body, current);
|
||||
complete_extents(body);
|
||||
function->body = body;
|
||||
} else {
|
||||
function->body = parse_suite("function declaration", body);
|
||||
}
|
||||
|
||||
current_function = previous_function;
|
||||
complete_extents(function);
|
||||
@ -5936,6 +5962,9 @@ void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function, const
|
||||
for (const AnnotationNode *E : p_function->annotations) {
|
||||
print_annotation(E);
|
||||
}
|
||||
if (p_function->is_abstract) {
|
||||
push_text("Abstract ");
|
||||
}
|
||||
if (p_function->is_static) {
|
||||
push_text("Static ");
|
||||
}
|
||||
|
||||
@ -853,6 +853,7 @@ public:
|
||||
HashMap<StringName, int> parameters_indices;
|
||||
TypeNode *return_type = nullptr;
|
||||
SuiteNode *body = nullptr;
|
||||
bool is_abstract = false;
|
||||
bool is_static = false; // For lambdas it's determined in the analyzer.
|
||||
bool is_coroutine = false;
|
||||
Variant rpc_config;
|
||||
@ -1502,7 +1503,7 @@ private:
|
||||
ClassNode *parse_class(bool p_is_abstract, bool p_is_static);
|
||||
void parse_class_name();
|
||||
void parse_extends();
|
||||
void parse_class_body(bool p_is_abstract, bool p_is_multiline);
|
||||
void parse_class_body(bool p_first_is_abstract, bool p_is_multiline);
|
||||
template <typename T>
|
||||
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);
|
||||
SignalNode *parse_signal(bool p_is_abstract, bool p_is_static);
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
abstract class AbstractClass:
|
||||
abstract func some_func()
|
||||
|
||||
class ImplementedClass extends AbstractClass:
|
||||
func some_func():
|
||||
pass
|
||||
|
||||
abstract class AbstractClassAgain extends ImplementedClass:
|
||||
abstract func some_func()
|
||||
|
||||
class Test1:
|
||||
abstract func some_func()
|
||||
|
||||
class Test2 extends AbstractClass:
|
||||
pass
|
||||
|
||||
class Test3 extends AbstractClassAgain:
|
||||
pass
|
||||
|
||||
class Test4 extends AbstractClass:
|
||||
func some_func():
|
||||
super()
|
||||
|
||||
func other_func():
|
||||
super.some_func()
|
||||
|
||||
func test():
|
||||
pass
|
||||
@ -0,0 +1,6 @@
|
||||
GDTEST_ANALYZER_ERROR
|
||||
>> ERROR at line 11: Class "Test1" is not abstract but contains abstract methods. Mark the class as abstract or remove "abstract" from all methods in this class.
|
||||
>> ERROR at line 14: Class "Test2" must implement "AbstractClass.some_func()" and other inherited abstract methods or be marked as abstract.
|
||||
>> ERROR at line 17: Class "Test3" must implement "AbstractClassAgain.some_func()" and other inherited abstract methods or be marked as abstract.
|
||||
>> ERROR at line 22: Cannot call the parent class' abstract function "some_func()" because it hasn't been defined.
|
||||
>> ERROR at line 25: Cannot call the parent class' abstract function "some_func()" because it hasn't been defined.
|
||||
@ -0,0 +1,4 @@
|
||||
[output]
|
||||
include=[
|
||||
{"display": "test(x: int) -> void:", "insert_text": "test(x: int) -> void:"},
|
||||
]
|
||||
@ -0,0 +1,5 @@
|
||||
abstract class A:
|
||||
abstract func test(x: int) -> void
|
||||
|
||||
class B extends A:
|
||||
func ➡
|
||||
@ -0,0 +1,8 @@
|
||||
extends RefCounted
|
||||
|
||||
abstract class A:
|
||||
abstract func f():
|
||||
pass
|
||||
|
||||
func test():
|
||||
pass
|
||||
@ -0,0 +1,2 @@
|
||||
GDTEST_PARSER_ERROR
|
||||
Expected end of statement after abstract function declaration, found ":" instead.
|
||||
@ -0,0 +1,8 @@
|
||||
extends RefCounted
|
||||
|
||||
abstract class A:
|
||||
# Currently, an abstract function cannot be static.
|
||||
abstract static func f()
|
||||
|
||||
func test():
|
||||
pass
|
||||
@ -0,0 +1,2 @@
|
||||
GDTEST_PARSER_ERROR
|
||||
Expected "class" or "func" after "abstract".
|
||||
@ -1,2 +0,0 @@
|
||||
GDTEST_PARSER_ERROR
|
||||
Expected "class_name", "extends", or "class" after "abstract".
|
||||
@ -0,0 +1,2 @@
|
||||
GDTEST_PARSER_ERROR
|
||||
Expected "class_name", "extends", "class", or "func" after "abstract".
|
||||
@ -0,0 +1,7 @@
|
||||
extends RefCounted
|
||||
|
||||
abstract class A:
|
||||
abstract abstract func f()
|
||||
|
||||
func test():
|
||||
pass
|
||||
@ -0,0 +1,2 @@
|
||||
GDTEST_PARSER_ERROR
|
||||
Expected "class" or "func" after "abstract".
|
||||
@ -0,0 +1,8 @@
|
||||
extends RefCounted
|
||||
|
||||
abstract class A:
|
||||
# Currently, an abstract function cannot be static.
|
||||
static abstract func f()
|
||||
|
||||
func test():
|
||||
pass
|
||||
@ -0,0 +1,2 @@
|
||||
GDTEST_PARSER_ERROR
|
||||
Expected "func" or "var" after "static".
|
||||
@ -0,0 +1,48 @@
|
||||
abstract class A:
|
||||
abstract func get_text_1() -> String
|
||||
abstract func get_text_2() -> String
|
||||
|
||||
# No `UNUSED_PARAMETER` warning.
|
||||
abstract func func_with_param(param: int) -> int
|
||||
abstract func func_with_semicolon() -> int;
|
||||
abstract func func_1() -> int; abstract func func_2() -> int
|
||||
abstract func func_without_return_type()
|
||||
|
||||
func print_text_1() -> void:
|
||||
print(get_text_1())
|
||||
|
||||
abstract class B extends A:
|
||||
func get_text_1() -> String:
|
||||
return "text_1b"
|
||||
|
||||
func print_text_2() -> void:
|
||||
print(get_text_2())
|
||||
|
||||
class C extends B:
|
||||
func get_text_2() -> String:
|
||||
return "text_2c"
|
||||
|
||||
func func_with_param(param: int) -> int: return param
|
||||
func func_with_semicolon() -> int: return 0
|
||||
func func_1() -> int: return 0
|
||||
func func_2() -> int: return 0
|
||||
func func_without_return_type(): pass
|
||||
|
||||
abstract class D extends C:
|
||||
abstract func get_text_1() -> String
|
||||
|
||||
func get_text_2() -> String:
|
||||
return super() + " text_2d"
|
||||
|
||||
class E extends D:
|
||||
func get_text_1() -> String:
|
||||
return "text_1e"
|
||||
|
||||
func test():
|
||||
var c := C.new()
|
||||
c.print_text_1()
|
||||
c.print_text_2()
|
||||
|
||||
var e := E.new()
|
||||
e.print_text_1()
|
||||
e.print_text_2()
|
||||
@ -0,0 +1,5 @@
|
||||
GDTEST_OK
|
||||
text_1b
|
||||
text_2c
|
||||
text_1e
|
||||
text_2c text_2d
|
||||
@ -1,18 +1,12 @@
|
||||
# GH-82169
|
||||
|
||||
class A:
|
||||
static var test_static_var_a1
|
||||
static var test_static_var_a2
|
||||
var test_var_a1
|
||||
var test_var_a2
|
||||
static func test_static_func_a1(): pass
|
||||
static func test_static_func_a2(): pass
|
||||
func test_func_a1(): pass
|
||||
func test_func_a2(): pass
|
||||
@warning_ignore("unused_signal")
|
||||
signal test_signal_a1()
|
||||
@warning_ignore("unused_signal")
|
||||
signal test_signal_a2()
|
||||
@warning_ignore_start("unused_signal")
|
||||
|
||||
abstract class A:
|
||||
abstract func test_abstract_func_1()
|
||||
abstract func test_abstract_func_2()
|
||||
func test_override_func_1(): pass
|
||||
func test_override_func_2(): pass
|
||||
|
||||
class B extends A:
|
||||
static var test_static_var_b1
|
||||
@ -21,27 +15,67 @@ class B extends A:
|
||||
var test_var_b2
|
||||
static func test_static_func_b1(): pass
|
||||
static func test_static_func_b2(): pass
|
||||
func test_abstract_func_1(): pass
|
||||
func test_abstract_func_2(): pass
|
||||
func test_override_func_1(): pass
|
||||
func test_override_func_2(): pass
|
||||
func test_func_b1(): pass
|
||||
func test_func_b2(): pass
|
||||
@warning_ignore("unused_signal")
|
||||
signal test_signal_b1()
|
||||
@warning_ignore("unused_signal")
|
||||
signal test_signal_b2()
|
||||
|
||||
class C extends B:
|
||||
static var test_static_var_c1
|
||||
static var test_static_var_c2
|
||||
var test_var_c1
|
||||
var test_var_c2
|
||||
static func test_static_func_c1(): pass
|
||||
static func test_static_func_c2(): pass
|
||||
func test_abstract_func_1(): pass
|
||||
func test_abstract_func_2(): pass
|
||||
func test_override_func_1(): pass
|
||||
func test_override_func_2(): pass
|
||||
func test_func_c1(): pass
|
||||
func test_func_c2(): pass
|
||||
signal test_signal_c1()
|
||||
signal test_signal_c2()
|
||||
|
||||
func test_property_signature(name: String, base: Object, is_static: bool = false) -> void:
|
||||
prints("---", name, "---")
|
||||
for property in base.get_property_list():
|
||||
if str(property.name).begins_with("test_"):
|
||||
print(Utils.get_property_signature(property, null, is_static))
|
||||
|
||||
func test_method_signature(name: String, base: Object) -> void:
|
||||
prints("---", name, "---")
|
||||
for method in base.get_method_list():
|
||||
if str(method.name).begins_with("test_"):
|
||||
print(Utils.get_method_signature(method))
|
||||
|
||||
func test_signal_signature(name: String, base: Object) -> void:
|
||||
prints("---", name, "---")
|
||||
for method in base.get_signal_list():
|
||||
if str(method.name).begins_with("test_"):
|
||||
print(Utils.get_method_signature(method, true))
|
||||
|
||||
func test():
|
||||
var b := B.new()
|
||||
for property in (B as GDScript).get_property_list():
|
||||
if str(property.name).begins_with("test_"):
|
||||
print(Utils.get_property_signature(property, null, true))
|
||||
print("---")
|
||||
for property in b.get_property_list():
|
||||
if str(property.name).begins_with("test_"):
|
||||
print(Utils.get_property_signature(property))
|
||||
print("---")
|
||||
for method in b.get_method_list():
|
||||
if str(method.name).begins_with("test_"):
|
||||
print(Utils.get_method_signature(method))
|
||||
print("---")
|
||||
for method in b.get_signal_list():
|
||||
if str(method.name).begins_with("test_"):
|
||||
print(Utils.get_method_signature(method, true))
|
||||
var c := C.new()
|
||||
|
||||
print("=== Class Properties ===")
|
||||
test_property_signature("A", A as GDScript, true)
|
||||
test_property_signature("B", B as GDScript, true)
|
||||
test_property_signature("C", C as GDScript, true)
|
||||
print("=== Member Properties ===")
|
||||
test_property_signature("B", b)
|
||||
test_property_signature("C", c)
|
||||
print("=== Class Methods ===")
|
||||
test_method_signature("A", A as GDScript)
|
||||
test_method_signature("B", B as GDScript)
|
||||
test_method_signature("C", C as GDScript)
|
||||
print("=== Member Methods ===")
|
||||
test_method_signature("B", b)
|
||||
test_method_signature("C", c)
|
||||
print("=== Signals ===")
|
||||
test_signal_signature("B", b)
|
||||
test_signal_signature("C", c)
|
||||
|
||||
@ -1,24 +1,68 @@
|
||||
GDTEST_OK
|
||||
static var test_static_var_a1: Variant
|
||||
static var test_static_var_a2: Variant
|
||||
=== Class Properties ===
|
||||
--- A ---
|
||||
--- B ---
|
||||
static var test_static_var_b1: Variant
|
||||
static var test_static_var_b2: Variant
|
||||
---
|
||||
--- C ---
|
||||
static var test_static_var_b1: Variant
|
||||
static var test_static_var_b2: Variant
|
||||
static var test_static_var_c1: Variant
|
||||
static var test_static_var_c2: Variant
|
||||
=== Member Properties ===
|
||||
--- B ---
|
||||
var test_var_b1: Variant
|
||||
var test_var_b2: Variant
|
||||
var test_var_a1: Variant
|
||||
var test_var_a2: Variant
|
||||
---
|
||||
--- C ---
|
||||
var test_var_c1: Variant
|
||||
var test_var_c2: Variant
|
||||
var test_var_b1: Variant
|
||||
var test_var_b2: Variant
|
||||
=== Class Methods ===
|
||||
--- A ---
|
||||
--- B ---
|
||||
--- C ---
|
||||
=== Member Methods ===
|
||||
--- B ---
|
||||
static func test_static_func_b1() -> void
|
||||
static func test_static_func_b2() -> void
|
||||
func test_abstract_func_1() -> void
|
||||
func test_abstract_func_2() -> void
|
||||
func test_override_func_1() -> void
|
||||
func test_override_func_2() -> void
|
||||
func test_func_b1() -> void
|
||||
func test_func_b2() -> void
|
||||
static func test_static_func_a1() -> void
|
||||
static func test_static_func_a2() -> void
|
||||
func test_func_a1() -> void
|
||||
func test_func_a2() -> void
|
||||
---
|
||||
abstract func test_abstract_func_1() -> void
|
||||
abstract func test_abstract_func_2() -> void
|
||||
func test_override_func_1() -> void
|
||||
func test_override_func_2() -> void
|
||||
--- C ---
|
||||
static func test_static_func_c1() -> void
|
||||
static func test_static_func_c2() -> void
|
||||
func test_abstract_func_1() -> void
|
||||
func test_abstract_func_2() -> void
|
||||
func test_override_func_1() -> void
|
||||
func test_override_func_2() -> void
|
||||
func test_func_c1() -> void
|
||||
func test_func_c2() -> void
|
||||
static func test_static_func_b1() -> void
|
||||
static func test_static_func_b2() -> void
|
||||
func test_abstract_func_1() -> void
|
||||
func test_abstract_func_2() -> void
|
||||
func test_override_func_1() -> void
|
||||
func test_override_func_2() -> void
|
||||
func test_func_b1() -> void
|
||||
func test_func_b2() -> void
|
||||
abstract func test_abstract_func_1() -> void
|
||||
abstract func test_abstract_func_2() -> void
|
||||
func test_override_func_1() -> void
|
||||
func test_override_func_2() -> void
|
||||
=== Signals ===
|
||||
--- B ---
|
||||
signal test_signal_b1()
|
||||
signal test_signal_b2()
|
||||
--- C ---
|
||||
signal test_signal_c1()
|
||||
signal test_signal_c2()
|
||||
signal test_signal_b1()
|
||||
signal test_signal_b2()
|
||||
signal test_signal_a1()
|
||||
signal test_signal_a2()
|
||||
|
||||
@ -100,6 +100,8 @@ static func print_property_extended_info(property: Dictionary, base: Object = nu
|
||||
|
||||
static func get_method_signature(method: Dictionary, is_signal: bool = false) -> String:
|
||||
var result: String = ""
|
||||
if method.flags & METHOD_FLAG_VIRTUAL_REQUIRED:
|
||||
result += "abstract "
|
||||
if method.flags & METHOD_FLAG_STATIC:
|
||||
result += "static "
|
||||
result += ("signal " if is_signal else "func ") + method.name + "("
|
||||
|
||||
Reference in New Issue
Block a user