mirror of
https://github.com/godotengine/godot.git
synced 2025-11-11 02:51:25 +00:00
Merge pull request #107717 from aaronfranke/abstract-annotation
GDScript: Replace `abstract` keyword with `@abstract` annotation
This commit is contained in:
commit
ebc36a7225
31 changed files with 179 additions and 197 deletions
|
|
@ -296,6 +296,27 @@
|
||||||
</constant>
|
</constant>
|
||||||
</constants>
|
</constants>
|
||||||
<annotations>
|
<annotations>
|
||||||
|
<annotation name="@abstract">
|
||||||
|
<return type="void" />
|
||||||
|
<description>
|
||||||
|
Marks a class or a method as abstract.
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
[codeblock]
|
||||||
|
@abstract class Shape:
|
||||||
|
@abstract func draw()
|
||||||
|
|
||||||
|
class Circle extends Shape:
|
||||||
|
func draw():
|
||||||
|
print("Drawing a circle.")
|
||||||
|
|
||||||
|
class Square extends Shape:
|
||||||
|
func draw():
|
||||||
|
print("Drawing a square.")
|
||||||
|
[/codeblock]
|
||||||
|
</description>
|
||||||
|
</annotation>
|
||||||
<annotation name="@export">
|
<annotation name="@export">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<description>
|
<description>
|
||||||
|
|
|
||||||
|
|
@ -2749,7 +2749,6 @@ Vector<String> GDScriptLanguage::get_reserved_words() const {
|
||||||
"when",
|
"when",
|
||||||
"while",
|
"while",
|
||||||
// Declarations.
|
// Declarations.
|
||||||
"abstract",
|
|
||||||
"class",
|
"class",
|
||||||
"class_name",
|
"class_name",
|
||||||
"const",
|
"const",
|
||||||
|
|
@ -2915,7 +2914,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
|
||||||
*r_icon_path = c->simplified_icon_path;
|
*r_icon_path = c->simplified_icon_path;
|
||||||
}
|
}
|
||||||
if (r_is_abstract) {
|
if (r_is_abstract) {
|
||||||
*r_is_abstract = false;
|
*r_is_abstract = c->is_abstract;
|
||||||
}
|
}
|
||||||
if (r_is_tool) {
|
if (r_is_tool) {
|
||||||
*r_is_tool = parser.is_tool();
|
*r_is_tool = parser.is_tool();
|
||||||
|
|
|
||||||
|
|
@ -1540,12 +1540,12 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
|
||||||
if (member.function->is_abstract) {
|
if (member.function->is_abstract) {
|
||||||
if (base_class == p_class) {
|
if (base_class == p_class) {
|
||||||
const String class_name = p_class->identifier == nullptr ? p_class->fqcn.get_file() : String(p_class->identifier->name);
|
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);
|
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;
|
break;
|
||||||
} else if (!implemented_funcs.has(member.function->identifier->name)) {
|
} 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 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);
|
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);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1987,6 +1987,20 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun
|
||||||
}
|
}
|
||||||
p_function->resolved_body = true;
|
p_function->resolved_body = true;
|
||||||
|
|
||||||
|
if (p_function->is_abstract) {
|
||||||
|
// Abstract functions don't have a body.
|
||||||
|
if (!p_function->body->statements.is_empty()) {
|
||||||
|
push_error(R"(Abstract function cannot have a body.)", p_function->body);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// Non-abstract functions must have a body.
|
||||||
|
if (p_function->body->statements.is_empty()) {
|
||||||
|
push_error(R"(A function must either have a ":" followed by a body, or be marked as "@abstract".)", p_function);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
GDScriptParser::FunctionNode *previous_function = parser->current_function;
|
GDScriptParser::FunctionNode *previous_function = parser->current_function;
|
||||||
parser->current_function = p_function;
|
parser->current_function = p_function;
|
||||||
|
|
||||||
|
|
@ -1999,7 +2013,7 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun
|
||||||
// Use the suite inferred type if return isn't explicitly set.
|
// Use the suite inferred type if return isn't explicitly set.
|
||||||
p_function->set_datatype(p_function->body->get_datatype());
|
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)) {
|
} 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->is_abstract && !p_function->body->has_return && (p_is_lambda || p_function->identifier->name != GDScriptLanguage::get_singleton()->strings._init)) {
|
if (!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);
|
push_error(R"(Not all code paths return a value.)", p_function);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1532,7 +1532,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context
|
||||||
|
|
||||||
static const char *_keywords_with_space[] = {
|
static const char *_keywords_with_space[] = {
|
||||||
"and", "not", "or", "in", "as", "class", "class_name", "extends", "is", "func", "signal", "await",
|
"and", "not", "or", "in", "as", "class", "class_name", "extends", "is", "func", "signal", "await",
|
||||||
"const", "enum", "abstract", "static", "var", "if", "elif", "else", "for", "match", "when", "while",
|
"const", "enum", "static", "var", "if", "elif", "else", "for", "match", "when", "while",
|
||||||
nullptr
|
nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ GDScriptParser::GDScriptParser() {
|
||||||
register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation);
|
register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation);
|
||||||
register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);
|
register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);
|
||||||
register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation);
|
register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation);
|
||||||
|
register_annotation(MethodInfo("@abstract"), AnnotationInfo::SCRIPT | AnnotationInfo::CLASS | AnnotationInfo::FUNCTION, &GDScriptParser::abstract_annotation);
|
||||||
// Onready annotation.
|
// Onready annotation.
|
||||||
register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
|
register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
|
||||||
// Export annotations.
|
// Export annotations.
|
||||||
|
|
@ -628,7 +629,7 @@ void GDScriptParser::parse_program() {
|
||||||
annotation_stack.push_back(annotation);
|
annotation_stack.push_back(annotation);
|
||||||
} else if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
|
} else if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
|
||||||
PUSH_PENDING_ANNOTATIONS_TO_HEAD;
|
PUSH_PENDING_ANNOTATIONS_TO_HEAD;
|
||||||
if (annotation->name == SNAME("@tool") || annotation->name == SNAME("@icon")) {
|
if (annotation->name == SNAME("@tool") || annotation->name == SNAME("@icon") || annotation->name == SNAME("@static_unload")) {
|
||||||
// Some annotations need to be resolved and applied in the parser.
|
// Some annotations need to be resolved and applied in the parser.
|
||||||
// The root class is not in any class, so `head->outer == nullptr`.
|
// The root class is not in any class, so `head->outer == nullptr`.
|
||||||
annotation->apply(this, head, nullptr);
|
annotation->apply(this, head, nullptr);
|
||||||
|
|
@ -675,52 +676,9 @@ void GDScriptParser::parse_program() {
|
||||||
reset_extents(head, current);
|
reset_extents(head, current);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool first_is_abstract = false;
|
|
||||||
while (can_have_class_or_extends) {
|
while (can_have_class_or_extends) {
|
||||||
// Order here doesn't matter, but there should be only one of each at most.
|
// Order here doesn't matter, but there should be only one of each at most.
|
||||||
switch (current.type) {
|
switch (current.type) {
|
||||||
case GDScriptTokenizer::Token::ABSTRACT: {
|
|
||||||
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();
|
|
||||||
|
|
||||||
// A standalone "abstract" is only allowed for script-level stuff.
|
|
||||||
bool is_standalone = false;
|
|
||||||
if (current.type == GDScriptTokenizer::Token::NEWLINE) {
|
|
||||||
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:
|
case GDScriptTokenizer::Token::CLASS_NAME:
|
||||||
PUSH_PENDING_ANNOTATIONS_TO_HEAD;
|
PUSH_PENDING_ANNOTATIONS_TO_HEAD;
|
||||||
advance();
|
advance();
|
||||||
|
|
@ -765,15 +723,23 @@ void GDScriptParser::parse_program() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When the only thing needed is the class name and the icon, we don't need to parse the hole file.
|
#undef PUSH_PENDING_ANNOTATIONS_TO_HEAD
|
||||||
// It really speed up the call to GDScriptLanguage::get_global_class_name especially for large script.
|
|
||||||
|
for (AnnotationNode *&annotation : head->annotations) {
|
||||||
|
if (annotation->name == SNAME("@abstract")) {
|
||||||
|
// Some annotations need to be resolved and applied in the parser.
|
||||||
|
// The root class is not in any class, so `head->outer == nullptr`.
|
||||||
|
annotation->apply(this, head, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the only thing needed is the class name, icon, and abstractness; we don't need to parse the whole file.
|
||||||
|
// It really speed up the call to `GDScriptLanguage::get_global_class_name()` especially for large script.
|
||||||
if (!parse_body) {
|
if (!parse_body) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef PUSH_PENDING_ANNOTATIONS_TO_HEAD
|
parse_class_body(true);
|
||||||
|
|
||||||
parse_class_body(first_is_abstract, true);
|
|
||||||
|
|
||||||
head->end_line = current.end_line;
|
head->end_line = current.end_line;
|
||||||
head->end_column = current.end_column;
|
head->end_column = current.end_column;
|
||||||
|
|
@ -876,13 +842,12 @@ bool GDScriptParser::has_class(const GDScriptParser::ClassNode *p_class) const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
GDScriptParser::ClassNode *GDScriptParser::parse_class(bool p_is_abstract, bool p_is_static) {
|
GDScriptParser::ClassNode *GDScriptParser::parse_class(bool p_is_static) {
|
||||||
ClassNode *n_class = alloc_node<ClassNode>();
|
ClassNode *n_class = alloc_node<ClassNode>();
|
||||||
|
|
||||||
ClassNode *previous_class = current_class;
|
ClassNode *previous_class = current_class;
|
||||||
current_class = n_class;
|
current_class = n_class;
|
||||||
n_class->outer = previous_class;
|
n_class->outer = previous_class;
|
||||||
n_class->is_abstract = p_is_abstract;
|
|
||||||
|
|
||||||
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the class name after "class".)")) {
|
if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the class name after "class".)")) {
|
||||||
n_class->identifier = parse_identifier();
|
n_class->identifier = parse_identifier();
|
||||||
|
|
@ -919,7 +884,7 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class(bool p_is_abstract, bool
|
||||||
end_statement("superclass");
|
end_statement("superclass");
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_class_body(false, multiline);
|
parse_class_body(multiline);
|
||||||
complete_extents(n_class);
|
complete_extents(n_class);
|
||||||
|
|
||||||
if (multiline) {
|
if (multiline) {
|
||||||
|
|
@ -978,7 +943,7 @@ void GDScriptParser::parse_extends() {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
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) {
|
void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static) {
|
||||||
advance();
|
advance();
|
||||||
|
|
||||||
// Consume annotations.
|
// Consume annotations.
|
||||||
|
|
@ -994,7 +959,7 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
T *member = (this->*p_parse_function)(p_is_abstract, p_is_static);
|
T *member = (this->*p_parse_function)(p_is_static);
|
||||||
if (member == nullptr) {
|
if (member == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1048,23 +1013,14 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GDScriptParser::parse_class_body(bool p_first_is_abstract, bool p_is_multiline) {
|
void GDScriptParser::parse_class_body(bool p_is_multiline) {
|
||||||
bool class_end = false;
|
bool class_end = false;
|
||||||
// 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;
|
bool next_is_static = false;
|
||||||
while (!class_end && !is_at_end()) {
|
while (!class_end && !is_at_end()) {
|
||||||
GDScriptTokenizer::Token token = current;
|
GDScriptTokenizer::Token token = current;
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
case GDScriptTokenizer::Token::ABSTRACT: {
|
|
||||||
advance();
|
|
||||||
next_is_abstract = true;
|
|
||||||
if (!check(GDScriptTokenizer::Token::CLASS) && !check(GDScriptTokenizer::Token::FUNC)) {
|
|
||||||
push_error(R"(Expected "class" or "func" after "abstract".)");
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
case GDScriptTokenizer::Token::VAR:
|
case GDScriptTokenizer::Token::VAR:
|
||||||
parse_class_member(&GDScriptParser::parse_variable, AnnotationInfo::VARIABLE, "variable", false, next_is_static);
|
parse_class_member(&GDScriptParser::parse_variable, AnnotationInfo::VARIABLE, "variable", next_is_static);
|
||||||
if (next_is_static) {
|
if (next_is_static) {
|
||||||
current_class->has_static_data = true;
|
current_class->has_static_data = true;
|
||||||
}
|
}
|
||||||
|
|
@ -1076,10 +1032,10 @@ void GDScriptParser::parse_class_body(bool p_first_is_abstract, bool p_is_multil
|
||||||
parse_class_member(&GDScriptParser::parse_signal, AnnotationInfo::SIGNAL, "signal");
|
parse_class_member(&GDScriptParser::parse_signal, AnnotationInfo::SIGNAL, "signal");
|
||||||
break;
|
break;
|
||||||
case GDScriptTokenizer::Token::FUNC:
|
case GDScriptTokenizer::Token::FUNC:
|
||||||
parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function", next_is_abstract, next_is_static);
|
parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function", next_is_static);
|
||||||
break;
|
break;
|
||||||
case GDScriptTokenizer::Token::CLASS:
|
case GDScriptTokenizer::Token::CLASS:
|
||||||
parse_class_member(&GDScriptParser::parse_class, AnnotationInfo::CLASS, "class", next_is_abstract);
|
parse_class_member(&GDScriptParser::parse_class, AnnotationInfo::CLASS, "class");
|
||||||
break;
|
break;
|
||||||
case GDScriptTokenizer::Token::ENUM:
|
case GDScriptTokenizer::Token::ENUM:
|
||||||
parse_class_member(&GDScriptParser::parse_enum, AnnotationInfo::NONE, "enum");
|
parse_class_member(&GDScriptParser::parse_enum, AnnotationInfo::NONE, "enum");
|
||||||
|
|
@ -1159,9 +1115,6 @@ void GDScriptParser::parse_class_body(bool p_first_is_abstract, bool p_is_multil
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (token.type != GDScriptTokenizer::Token::ABSTRACT) {
|
|
||||||
next_is_abstract = false;
|
|
||||||
}
|
|
||||||
if (token.type != GDScriptTokenizer::Token::STATIC) {
|
if (token.type != GDScriptTokenizer::Token::STATIC) {
|
||||||
next_is_static = false;
|
next_is_static = false;
|
||||||
}
|
}
|
||||||
|
|
@ -1174,11 +1127,11 @@ void GDScriptParser::parse_class_body(bool p_first_is_abstract, bool p_is_multil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_abstract, bool p_is_static) {
|
GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static) {
|
||||||
return parse_variable(p_is_abstract, p_is_static, true);
|
return parse_variable(p_is_static, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_abstract, bool p_is_static, bool p_allow_property) {
|
GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, bool p_allow_property) {
|
||||||
VariableNode *variable = alloc_node<VariableNode>();
|
VariableNode *variable = alloc_node<VariableNode>();
|
||||||
|
|
||||||
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) {
|
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) {
|
||||||
|
|
@ -1414,7 +1367,7 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GDScriptParser::ConstantNode *GDScriptParser::parse_constant(bool p_is_abstract, bool p_is_static) {
|
GDScriptParser::ConstantNode *GDScriptParser::parse_constant(bool p_is_static) {
|
||||||
ConstantNode *constant = alloc_node<ConstantNode>();
|
ConstantNode *constant = alloc_node<ConstantNode>();
|
||||||
|
|
||||||
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) {
|
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) {
|
||||||
|
|
@ -1482,7 +1435,7 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() {
|
||||||
return parameter;
|
return parameter;
|
||||||
}
|
}
|
||||||
|
|
||||||
GDScriptParser::SignalNode *GDScriptParser::parse_signal(bool p_is_abstract, bool p_is_static) {
|
GDScriptParser::SignalNode *GDScriptParser::parse_signal(bool p_is_static) {
|
||||||
SignalNode *signal = alloc_node<SignalNode>();
|
SignalNode *signal = alloc_node<SignalNode>();
|
||||||
|
|
||||||
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected signal name after "signal".)")) {
|
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected signal name after "signal".)")) {
|
||||||
|
|
@ -1527,7 +1480,7 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal(bool p_is_abstract, boo
|
||||||
return signal;
|
return signal;
|
||||||
}
|
}
|
||||||
|
|
||||||
GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_abstract, bool p_is_static) {
|
GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
|
||||||
EnumNode *enum_node = alloc_node<EnumNode>();
|
EnumNode *enum_node = alloc_node<EnumNode>();
|
||||||
bool named = false;
|
bool named = false;
|
||||||
|
|
||||||
|
|
@ -1625,7 +1578,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_abstract, bool p_
|
||||||
return enum_node;
|
return enum_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type, int p_signature_start) {
|
bool GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type, int p_signature_start) {
|
||||||
if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
|
if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
|
||||||
bool default_used = false;
|
bool default_used = false;
|
||||||
do {
|
do {
|
||||||
|
|
@ -1704,17 +1657,16 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod
|
||||||
}
|
}
|
||||||
#endif // TOOLS_ENABLED
|
#endif // TOOLS_ENABLED
|
||||||
|
|
||||||
if (p_function->is_abstract) {
|
// TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens.
|
||||||
end_statement("abstract function declaration");
|
if (p_type == "lambda") {
|
||||||
} else {
|
return consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after lambda declaration.)");
|
||||||
// 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));
|
|
||||||
}
|
}
|
||||||
|
// The colon may not be present in the case of abstract functions.
|
||||||
|
return match(GDScriptTokenizer::Token::COLON);
|
||||||
}
|
}
|
||||||
|
|
||||||
GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_abstract, bool p_is_static) {
|
GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_static) {
|
||||||
FunctionNode *function = alloc_node<FunctionNode>();
|
FunctionNode *function = alloc_node<FunctionNode>();
|
||||||
function->is_abstract = p_is_abstract;
|
|
||||||
function->is_static = p_is_static;
|
function->is_static = p_is_static;
|
||||||
|
|
||||||
make_completion_context(COMPLETION_OVERRIDE_METHOD, function);
|
make_completion_context(COMPLETION_OVERRIDE_METHOD, function);
|
||||||
|
|
@ -1744,9 +1696,9 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_abstract,
|
||||||
consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after function name.)");
|
consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after function name.)");
|
||||||
|
|
||||||
#ifdef TOOLS_ENABLED
|
#ifdef TOOLS_ENABLED
|
||||||
parse_function_signature(function, body, "function", signature_start_pos);
|
const bool has_body = parse_function_signature(function, body, "function", signature_start_pos);
|
||||||
#else // !TOOLS_ENABLED
|
#else // !TOOLS_ENABLED
|
||||||
parse_function_signature(function, body, "function", -1);
|
const bool has_body = parse_function_signature(function, body, "function", -1);
|
||||||
#endif // TOOLS_ENABLED
|
#endif // TOOLS_ENABLED
|
||||||
|
|
||||||
current_suite = previous_suite;
|
current_suite = previous_suite;
|
||||||
|
|
@ -1755,7 +1707,9 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_abstract,
|
||||||
function->min_local_doc_line = previous.end_line + 1;
|
function->min_local_doc_line = previous.end_line + 1;
|
||||||
#endif // TOOLS_ENABLED
|
#endif // TOOLS_ENABLED
|
||||||
|
|
||||||
if (function->is_abstract) {
|
if (!has_body) {
|
||||||
|
// Abstract functions do not have a body.
|
||||||
|
end_statement("bodyless function declaration");
|
||||||
reset_extents(body, current);
|
reset_extents(body, current);
|
||||||
complete_extents(body);
|
complete_extents(body);
|
||||||
function->body = body;
|
function->body = body;
|
||||||
|
|
@ -1995,11 +1949,11 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
|
||||||
break;
|
break;
|
||||||
case GDScriptTokenizer::Token::VAR:
|
case GDScriptTokenizer::Token::VAR:
|
||||||
advance();
|
advance();
|
||||||
result = parse_variable(false, false, false);
|
result = parse_variable(false, false);
|
||||||
break;
|
break;
|
||||||
case GDScriptTokenizer::Token::TK_CONST:
|
case GDScriptTokenizer::Token::TK_CONST:
|
||||||
advance();
|
advance();
|
||||||
result = parse_constant(false, false);
|
result = parse_constant(false);
|
||||||
break;
|
break;
|
||||||
case GDScriptTokenizer::Token::IF:
|
case GDScriptTokenizer::Token::IF:
|
||||||
advance();
|
advance();
|
||||||
|
|
@ -4220,7 +4174,6 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty
|
||||||
{ nullptr, nullptr, PREC_NONE }, // MATCH,
|
{ nullptr, nullptr, PREC_NONE }, // MATCH,
|
||||||
{ nullptr, nullptr, PREC_NONE }, // WHEN,
|
{ nullptr, nullptr, PREC_NONE }, // WHEN,
|
||||||
// Keywords
|
// Keywords
|
||||||
{ nullptr, nullptr, PREC_NONE }, // ABSTRACT
|
|
||||||
{ nullptr, &GDScriptParser::parse_cast, PREC_CAST }, // AS,
|
{ nullptr, &GDScriptParser::parse_cast, PREC_CAST }, // AS,
|
||||||
{ nullptr, nullptr, PREC_NONE }, // ASSERT,
|
{ nullptr, nullptr, PREC_NONE }, // ASSERT,
|
||||||
{ &GDScriptParser::parse_await, nullptr, PREC_NONE }, // AWAIT,
|
{ &GDScriptParser::parse_await, nullptr, PREC_NONE }, // AWAIT,
|
||||||
|
|
@ -4410,6 +4363,33 @@ bool GDScriptParser::static_unload_annotation(AnnotationNode *p_annotation, Node
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GDScriptParser::abstract_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
|
||||||
|
// NOTE: Use `p_target`, **not** `p_class`, because when `p_target` is a class then `p_class` refers to the outer class.
|
||||||
|
if (p_target->type == Node::CLASS) {
|
||||||
|
ClassNode *class_node = static_cast<ClassNode *>(p_target);
|
||||||
|
if (class_node->is_abstract) {
|
||||||
|
push_error(R"("@abstract" annotation can only be used once per class.)", p_annotation);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
class_node->is_abstract = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (p_target->type == Node::FUNCTION) {
|
||||||
|
FunctionNode *function_node = static_cast<FunctionNode *>(p_target);
|
||||||
|
if (function_node->is_static) {
|
||||||
|
push_error(R"("@abstract" annotation cannot be applied to static functions.)", p_annotation);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (function_node->is_abstract) {
|
||||||
|
push_error(R"("@abstract" annotation can only be used once per function.)", p_annotation);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function_node->is_abstract = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
ERR_FAIL_V_MSG(false, R"("@abstract" annotation can only be applied to classes and functions.)");
|
||||||
|
}
|
||||||
|
|
||||||
bool GDScriptParser::onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
|
bool GDScriptParser::onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
|
||||||
ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)");
|
ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)");
|
||||||
|
|
||||||
|
|
@ -5782,8 +5762,8 @@ void GDScriptParser::TreePrinter::print_cast(CastNode *p_cast) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GDScriptParser::TreePrinter::print_class(ClassNode *p_class) {
|
void GDScriptParser::TreePrinter::print_class(ClassNode *p_class) {
|
||||||
if (p_class->is_abstract) {
|
for (const AnnotationNode *E : p_class->annotations) {
|
||||||
push_text("Abstract ");
|
print_annotation(E);
|
||||||
}
|
}
|
||||||
push_text("Class ");
|
push_text("Class ");
|
||||||
if (p_class->identifier == nullptr) {
|
if (p_class->identifier == nullptr) {
|
||||||
|
|
@ -5984,9 +5964,6 @@ void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function, const
|
||||||
for (const AnnotationNode *E : p_function->annotations) {
|
for (const AnnotationNode *E : p_function->annotations) {
|
||||||
print_annotation(E);
|
print_annotation(E);
|
||||||
}
|
}
|
||||||
if (p_function->is_abstract) {
|
|
||||||
push_text("Abstract ");
|
|
||||||
}
|
|
||||||
if (p_function->is_static) {
|
if (p_function->is_static) {
|
||||||
push_text("Static ");
|
push_text("Static ");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1503,17 +1503,17 @@ private:
|
||||||
|
|
||||||
// Main blocks.
|
// Main blocks.
|
||||||
void parse_program();
|
void parse_program();
|
||||||
ClassNode *parse_class(bool p_is_abstract, bool p_is_static);
|
ClassNode *parse_class(bool p_is_static);
|
||||||
void parse_class_name();
|
void parse_class_name();
|
||||||
void parse_extends();
|
void parse_extends();
|
||||||
void parse_class_body(bool p_first_is_abstract, bool p_is_multiline);
|
void parse_class_body(bool p_is_multiline);
|
||||||
template <typename T>
|
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);
|
void parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static = false);
|
||||||
SignalNode *parse_signal(bool p_is_abstract, bool p_is_static);
|
SignalNode *parse_signal(bool p_is_static);
|
||||||
EnumNode *parse_enum(bool p_is_abstract, bool p_is_static);
|
EnumNode *parse_enum(bool p_is_static);
|
||||||
ParameterNode *parse_parameter();
|
ParameterNode *parse_parameter();
|
||||||
FunctionNode *parse_function(bool p_is_abstract, bool p_is_static);
|
FunctionNode *parse_function(bool p_is_static);
|
||||||
void parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type, int p_signature_start);
|
bool parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type, int p_signature_start);
|
||||||
SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr, bool p_for_lambda = false);
|
SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr, bool p_for_lambda = false);
|
||||||
// Annotations
|
// Annotations
|
||||||
AnnotationNode *parse_annotation(uint32_t p_valid_targets);
|
AnnotationNode *parse_annotation(uint32_t p_valid_targets);
|
||||||
|
|
@ -1523,6 +1523,7 @@ private:
|
||||||
bool tool_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
|
bool tool_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
|
||||||
bool icon_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
|
bool icon_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
|
||||||
bool static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
|
bool static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
|
||||||
|
bool abstract_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
|
||||||
bool onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
|
bool onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
|
||||||
template <PropertyHint t_hint, Variant::Type t_type>
|
template <PropertyHint t_hint, Variant::Type t_type>
|
||||||
bool export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
|
bool export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
|
||||||
|
|
@ -1536,12 +1537,12 @@ private:
|
||||||
bool rpc_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
|
bool rpc_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
|
||||||
// Statements.
|
// Statements.
|
||||||
Node *parse_statement();
|
Node *parse_statement();
|
||||||
VariableNode *parse_variable(bool p_is_abstract, bool p_is_static);
|
VariableNode *parse_variable(bool p_is_static);
|
||||||
VariableNode *parse_variable(bool p_is_abstract, bool p_is_static, bool p_allow_property);
|
VariableNode *parse_variable(bool p_is_static, bool p_allow_property);
|
||||||
VariableNode *parse_property(VariableNode *p_variable, bool p_need_indent);
|
VariableNode *parse_property(VariableNode *p_variable, bool p_need_indent);
|
||||||
void parse_property_getter(VariableNode *p_variable);
|
void parse_property_getter(VariableNode *p_variable);
|
||||||
void parse_property_setter(VariableNode *p_variable);
|
void parse_property_setter(VariableNode *p_variable);
|
||||||
ConstantNode *parse_constant(bool p_is_abstract, bool p_is_static);
|
ConstantNode *parse_constant(bool p_is_static);
|
||||||
AssertNode *parse_assert();
|
AssertNode *parse_assert();
|
||||||
BreakNode *parse_break();
|
BreakNode *parse_break();
|
||||||
ContinueNode *parse_continue();
|
ContinueNode *parse_continue();
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,6 @@ static const char *token_names[] = {
|
||||||
"match", // MATCH,
|
"match", // MATCH,
|
||||||
"when", // WHEN,
|
"when", // WHEN,
|
||||||
// Keywords
|
// Keywords
|
||||||
"abstract", // ABSTRACT,
|
|
||||||
"as", // AS,
|
"as", // AS,
|
||||||
"assert", // ASSERT,
|
"assert", // ASSERT,
|
||||||
"await", // AWAIT,
|
"await", // AWAIT,
|
||||||
|
|
@ -200,7 +199,6 @@ bool GDScriptTokenizer::Token::is_identifier() const {
|
||||||
case IDENTIFIER:
|
case IDENTIFIER:
|
||||||
case MATCH: // Used in String.match().
|
case MATCH: // Used in String.match().
|
||||||
case WHEN: // New keyword, avoid breaking existing code.
|
case WHEN: // New keyword, avoid breaking existing code.
|
||||||
case ABSTRACT:
|
|
||||||
// Allow constants to be treated as regular identifiers.
|
// Allow constants to be treated as regular identifiers.
|
||||||
case CONST_PI:
|
case CONST_PI:
|
||||||
case CONST_INF:
|
case CONST_INF:
|
||||||
|
|
@ -216,7 +214,6 @@ bool GDScriptTokenizer::Token::is_node_name() const {
|
||||||
// This is meant to allow keywords with the $ notation, but not as general identifiers.
|
// This is meant to allow keywords with the $ notation, but not as general identifiers.
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case IDENTIFIER:
|
case IDENTIFIER:
|
||||||
case ABSTRACT:
|
|
||||||
case AND:
|
case AND:
|
||||||
case AS:
|
case AS:
|
||||||
case ASSERT:
|
case ASSERT:
|
||||||
|
|
@ -491,7 +488,6 @@ GDScriptTokenizer::Token GDScriptTokenizerText::annotation() {
|
||||||
|
|
||||||
#define KEYWORDS(KEYWORD_GROUP, KEYWORD) \
|
#define KEYWORDS(KEYWORD_GROUP, KEYWORD) \
|
||||||
KEYWORD_GROUP('a') \
|
KEYWORD_GROUP('a') \
|
||||||
KEYWORD("abstract", Token::ABSTRACT) \
|
|
||||||
KEYWORD("as", Token::AS) \
|
KEYWORD("as", Token::AS) \
|
||||||
KEYWORD("and", Token::AND) \
|
KEYWORD("and", Token::AND) \
|
||||||
KEYWORD("assert", Token::ASSERT) \
|
KEYWORD("assert", Token::ASSERT) \
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,6 @@ public:
|
||||||
MATCH,
|
MATCH,
|
||||||
WHEN,
|
WHEN,
|
||||||
// Keywords
|
// Keywords
|
||||||
ABSTRACT,
|
|
||||||
AS,
|
AS,
|
||||||
ASSERT,
|
ASSERT,
|
||||||
AWAIT,
|
AWAIT,
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
abstract class AbstractClass:
|
@abstract class AbstractClass:
|
||||||
abstract func some_func()
|
@abstract func some_func()
|
||||||
|
|
||||||
class ImplementedClass extends AbstractClass:
|
class ImplementedClass extends AbstractClass:
|
||||||
func some_func():
|
func some_func():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
abstract class AbstractClassAgain extends ImplementedClass:
|
@abstract class AbstractClassAgain extends ImplementedClass:
|
||||||
abstract func some_func()
|
@abstract func some_func()
|
||||||
|
|
||||||
class Test1:
|
class Test1:
|
||||||
abstract func some_func()
|
@abstract func some_func()
|
||||||
|
|
||||||
class Test2 extends AbstractClass:
|
class Test2 extends AbstractClass:
|
||||||
pass
|
pass
|
||||||
|
|
@ -24,5 +24,18 @@ class Test4 extends AbstractClass:
|
||||||
func other_func():
|
func other_func():
|
||||||
super.some_func()
|
super.some_func()
|
||||||
|
|
||||||
|
@abstract class A:
|
||||||
|
@abstract @abstract func abstract_dup()
|
||||||
|
|
||||||
|
# An abstract function cannot have a body.
|
||||||
|
@abstract func abstract_bodyful():
|
||||||
|
pass
|
||||||
|
|
||||||
|
# A static function cannot be marked as `@abstract`.
|
||||||
|
@abstract static func abstract_stat()
|
||||||
|
|
||||||
|
@abstract @abstract class DuplicateAbstract:
|
||||||
|
pass
|
||||||
|
|
||||||
func test():
|
func test():
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
GDTEST_ANALYZER_ERROR
|
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 37: "@abstract" annotation can only be used once per 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 28: "@abstract" annotation can only be used once per function.
|
||||||
>> ERROR at line 17: Class "Test3" must implement "AbstractClassAgain.some_func()" and other inherited abstract methods or be marked as abstract.
|
>> ERROR at line 35: "@abstract" annotation cannot be applied to static functions.
|
||||||
|
>> 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 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.
|
>> ERROR at line 25: Cannot call the parent class' abstract function "some_func()" because it hasn't been defined.
|
||||||
|
>> ERROR at line 32: Abstract function cannot have a body.
|
||||||
|
>> ERROR at line 35: A function must either have a ":" followed by a body, or be marked as "@abstract".
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ extends RefCounted
|
||||||
|
|
||||||
const AbstractScript = preload("./construct_abstract_script.notest.gd")
|
const AbstractScript = preload("./construct_abstract_script.notest.gd")
|
||||||
|
|
||||||
abstract class AbstractClass:
|
@abstract class AbstractClass:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
func test():
|
func test():
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
abstract class_name AbstractScript
|
@abstract class_name AbstractScript
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ class B extends A:
|
||||||
class C extends CanvasItem:
|
class C extends CanvasItem:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
abstract class X:
|
@abstract class X:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class Y extends X:
|
class Y extends X:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
abstract class A:
|
@abstract class A:
|
||||||
abstract func test(x: int) -> void
|
@abstract func test(x: int) -> void
|
||||||
|
|
||||||
class B extends A:
|
class B extends A:
|
||||||
func ➡
|
func ➡
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
extends RefCounted
|
|
||||||
|
|
||||||
abstract class A:
|
|
||||||
abstract func f():
|
|
||||||
pass
|
|
||||||
|
|
||||||
func test():
|
|
||||||
pass
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
GDTEST_PARSER_ERROR
|
|
||||||
Expected end of statement after abstract function declaration, found ":" instead.
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
extends RefCounted
|
|
||||||
|
|
||||||
abstract class A:
|
|
||||||
# Currently, an abstract function cannot be static.
|
|
||||||
abstract static func f()
|
|
||||||
|
|
||||||
func test():
|
|
||||||
pass
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
GDTEST_PARSER_ERROR
|
|
||||||
Expected "class" or "func" after "abstract".
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
GDTEST_PARSER_ERROR
|
GDTEST_PARSER_ERROR
|
||||||
Expected ":" after function declaration.
|
Expected end of statement after bodyless function declaration, found "{" instead.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
extends RefCounted
|
|
||||||
|
|
||||||
abstract abstract class A:
|
|
||||||
pass
|
|
||||||
|
|
||||||
func test():
|
|
||||||
pass
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
GDTEST_PARSER_ERROR
|
|
||||||
Expected "class_name", "extends", "class", or "func" after "abstract".
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
extends RefCounted
|
|
||||||
|
|
||||||
abstract class A:
|
|
||||||
abstract abstract func f()
|
|
||||||
|
|
||||||
func test():
|
|
||||||
pass
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
GDTEST_PARSER_ERROR
|
|
||||||
Expected "class" or "func" after "abstract".
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# No colon --v
|
||||||
|
var f = func () -> void
|
||||||
|
pass
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
GDTEST_PARSER_ERROR
|
||||||
|
Expected ":" after lambda declaration.
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
extends RefCounted
|
|
||||||
|
|
||||||
abstract class A:
|
|
||||||
# Currently, an abstract function cannot be static.
|
|
||||||
static abstract func f()
|
|
||||||
|
|
||||||
func test():
|
|
||||||
pass
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
GDTEST_PARSER_ERROR
|
|
||||||
Expected "func" or "var" after "static".
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
abstract class A:
|
@abstract class A:
|
||||||
abstract func get_text_1() -> String
|
@abstract func get_text_1() -> String
|
||||||
abstract func get_text_2() -> String
|
@abstract func get_text_2() -> String
|
||||||
|
|
||||||
# No `UNUSED_PARAMETER` warning.
|
# No `UNUSED_PARAMETER` warning.
|
||||||
abstract func func_with_param(param: int) -> int
|
@abstract func func_with_param(param: int) -> int
|
||||||
abstract func func_with_rest_param(...args: Array) -> int
|
@abstract func func_with_rest_param(...args: Array) -> int
|
||||||
abstract func func_with_semicolon() -> int;
|
@abstract func func_with_semicolon() -> int;
|
||||||
abstract func func_1() -> int; abstract func func_2() -> int
|
@abstract func func_1() -> int; @abstract func func_2() -> int
|
||||||
abstract func func_without_return_type()
|
@abstract func func_without_return_type()
|
||||||
|
|
||||||
func print_text_1() -> void:
|
func print_text_1() -> void:
|
||||||
print(get_text_1())
|
print(get_text_1())
|
||||||
|
|
||||||
abstract class B extends A:
|
@abstract class B extends A:
|
||||||
func get_text_1() -> String:
|
func get_text_1() -> String:
|
||||||
return "text_1b"
|
return "text_1b"
|
||||||
|
|
||||||
|
|
@ -30,8 +30,8 @@ class C extends B:
|
||||||
func func_2() -> int: return 0
|
func func_2() -> int: return 0
|
||||||
func func_without_return_type(): pass
|
func func_without_return_type(): pass
|
||||||
|
|
||||||
abstract class D extends C:
|
@abstract class D extends C:
|
||||||
abstract func get_text_1() -> String
|
@abstract func get_text_1() -> String
|
||||||
|
|
||||||
func get_text_2() -> String:
|
func get_text_2() -> String:
|
||||||
return super() + " text_2d"
|
return super() + " text_2d"
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
@warning_ignore_start("unused_signal")
|
@warning_ignore_start("unused_signal")
|
||||||
|
|
||||||
abstract class A:
|
@abstract class A:
|
||||||
abstract func test_abstract_func_1()
|
@abstract func test_abstract_func_1()
|
||||||
abstract func test_abstract_func_2()
|
@abstract func test_abstract_func_2()
|
||||||
func test_override_func_1(): pass
|
func test_override_func_1(): pass
|
||||||
func test_override_func_2(): pass
|
func test_override_func_2(): pass
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ func test_override_func_1() -> void
|
||||||
func test_override_func_2() -> void
|
func test_override_func_2() -> void
|
||||||
func test_func_b1() -> void
|
func test_func_b1() -> void
|
||||||
func test_func_b2() -> void
|
func test_func_b2() -> void
|
||||||
abstract func test_abstract_func_1() -> void
|
@abstract func test_abstract_func_1() -> void
|
||||||
abstract func test_abstract_func_2() -> void
|
@abstract func test_abstract_func_2() -> void
|
||||||
func test_override_func_1() -> void
|
func test_override_func_1() -> void
|
||||||
func test_override_func_2() -> void
|
func test_override_func_2() -> void
|
||||||
--- C ---
|
--- C ---
|
||||||
|
|
@ -53,8 +53,8 @@ func test_override_func_1() -> void
|
||||||
func test_override_func_2() -> void
|
func test_override_func_2() -> void
|
||||||
func test_func_b1() -> void
|
func test_func_b1() -> void
|
||||||
func test_func_b2() -> void
|
func test_func_b2() -> void
|
||||||
abstract func test_abstract_func_1() -> void
|
@abstract func test_abstract_func_1() -> void
|
||||||
abstract func test_abstract_func_2() -> void
|
@abstract func test_abstract_func_2() -> void
|
||||||
func test_override_func_1() -> void
|
func test_override_func_1() -> void
|
||||||
func test_override_func_2() -> void
|
func test_override_func_2() -> void
|
||||||
=== Signals ===
|
=== Signals ===
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ static func print_property_extended_info(
|
||||||
static func get_method_signature(method: Dictionary, is_signal: bool = false) -> String:
|
static func get_method_signature(method: Dictionary, is_signal: bool = false) -> String:
|
||||||
var result: String = ""
|
var result: String = ""
|
||||||
if method.flags & METHOD_FLAG_VIRTUAL_REQUIRED:
|
if method.flags & METHOD_FLAG_VIRTUAL_REQUIRED:
|
||||||
result += "abstract "
|
result += "@abstract "
|
||||||
if method.flags & METHOD_FLAG_STATIC:
|
if method.flags & METHOD_FLAG_STATIC:
|
||||||
result += "static "
|
result += "static "
|
||||||
result += ("signal " if is_signal else "func ") + method.name + "("
|
result += ("signal " if is_signal else "func ") + method.name + "("
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue