summaryrefslogtreecommitdiffstats
path: root/modules/gdscript/gdscript_parser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript/gdscript_parser.cpp')
-rw-r--r--modules/gdscript/gdscript_parser.cpp427
1 files changed, 291 insertions, 136 deletions
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 06c66b155f..bcc116cda2 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -51,46 +51,8 @@
static HashMap<StringName, Variant::Type> builtin_types;
Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) {
if (builtin_types.is_empty()) {
- builtin_types["bool"] = Variant::BOOL;
- builtin_types["int"] = Variant::INT;
- builtin_types["float"] = Variant::FLOAT;
- builtin_types["String"] = Variant::STRING;
- builtin_types["Vector2"] = Variant::VECTOR2;
- builtin_types["Vector2i"] = Variant::VECTOR2I;
- builtin_types["Rect2"] = Variant::RECT2;
- builtin_types["Rect2i"] = Variant::RECT2I;
- builtin_types["Transform2D"] = Variant::TRANSFORM2D;
- builtin_types["Vector3"] = Variant::VECTOR3;
- builtin_types["Vector3i"] = Variant::VECTOR3I;
- builtin_types["Vector4"] = Variant::VECTOR4;
- builtin_types["Vector4i"] = Variant::VECTOR4I;
- builtin_types["AABB"] = Variant::AABB;
- builtin_types["Plane"] = Variant::PLANE;
- builtin_types["Quaternion"] = Variant::QUATERNION;
- builtin_types["Basis"] = Variant::BASIS;
- builtin_types["Transform3D"] = Variant::TRANSFORM3D;
- builtin_types["Projection"] = Variant::PROJECTION;
- builtin_types["Color"] = Variant::COLOR;
- builtin_types["RID"] = Variant::RID;
- builtin_types["Object"] = Variant::OBJECT;
- builtin_types["StringName"] = Variant::STRING_NAME;
- builtin_types["NodePath"] = Variant::NODE_PATH;
- builtin_types["Dictionary"] = Variant::DICTIONARY;
- builtin_types["Callable"] = Variant::CALLABLE;
- builtin_types["Signal"] = Variant::SIGNAL;
- builtin_types["Array"] = Variant::ARRAY;
- builtin_types["PackedByteArray"] = Variant::PACKED_BYTE_ARRAY;
- builtin_types["PackedInt32Array"] = Variant::PACKED_INT32_ARRAY;
- builtin_types["PackedInt64Array"] = Variant::PACKED_INT64_ARRAY;
- builtin_types["PackedFloat32Array"] = Variant::PACKED_FLOAT32_ARRAY;
- builtin_types["PackedFloat64Array"] = Variant::PACKED_FLOAT64_ARRAY;
- builtin_types["PackedStringArray"] = Variant::PACKED_STRING_ARRAY;
- builtin_types["PackedVector2Array"] = Variant::PACKED_VECTOR2_ARRAY;
- builtin_types["PackedVector3Array"] = Variant::PACKED_VECTOR3_ARRAY;
- builtin_types["PackedColorArray"] = Variant::PACKED_COLOR_ARRAY;
- // NIL is not here, hence the -1.
- if (builtin_types.size() != Variant::VARIANT_MAX - 1) {
- ERR_PRINT("Outdated parser: amount of built-in types don't match the amount of types in Variant.");
+ for (int i = 1; i < Variant::VARIANT_MAX; i++) {
+ builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i;
}
}
@@ -147,7 +109,7 @@ GDScriptParser::GDScriptParser() {
// Warning annotations.
register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true);
// Networking.
- register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("", "", "", 0), true);
+ register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0), true);
#ifdef DEBUG_ENABLED
is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable");
@@ -196,14 +158,10 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_
return;
}
- if (ignored_warning_codes.has(p_code)) {
+ if (ignored_warnings.has(p_code)) {
return;
}
- String warn_name = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)p_code).to_lower();
- if (ignored_warnings.has(warn_name)) {
- return;
- }
int warn_level = (int)GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(p_code));
if (!warn_level) {
return;
@@ -217,8 +175,8 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_
warning.leftmost_column = p_source->leftmost_column;
warning.rightmost_column = p_source->rightmost_column;
- if (warn_level == GDScriptWarning::WarnLevel::ERROR || bool(GLOBAL_GET("debug/gdscript/warnings/treat_warnings_as_errors"))) {
- push_error(warning.get_message(), p_source);
+ if (warn_level == GDScriptWarning::WarnLevel::ERROR) {
+ push_error(warning.get_message() + String(" (Warning treated as error.)"), p_source);
return;
}
@@ -525,19 +483,34 @@ void GDScriptParser::parse_program() {
current_class = head;
bool can_have_class_or_extends = true;
- while (match(GDScriptTokenizer::Token::ANNOTATION)) {
- AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
- if (annotation != nullptr) {
- if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
- head->annotations.push_back(annotation);
- } else {
- annotation_stack.push_back(annotation);
- // This annotation must appear after script-level annotations
- // and class_name/extends (ex: could be @onready or @export),
- // so we stop looking for script-level stuff.
- can_have_class_or_extends = false;
- break;
+ while (!check(GDScriptTokenizer::Token::TK_EOF)) {
+ if (match(GDScriptTokenizer::Token::ANNOTATION)) {
+ AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
+ if (annotation != nullptr) {
+ if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
+ // `@icon` needs to be applied in the parser. See GH-72444.
+ if (annotation->name == SNAME("@icon")) {
+ annotation->apply(this, head);
+ } else {
+ head->annotations.push_back(annotation);
+ }
+ } else {
+ annotation_stack.push_back(annotation);
+ // This annotation must appear after script-level annotations
+ // and class_name/extends (ex: could be @onready or @export),
+ // so we stop looking for script-level stuff.
+ can_have_class_or_extends = false;
+ break;
+ }
}
+ } else if (check(GDScriptTokenizer::Token::LITERAL) && current.literal.get_type() == Variant::STRING) {
+ // Allow strings in class body as multiline comments.
+ advance();
+ if (!match(GDScriptTokenizer::Token::NEWLINE)) {
+ push_error("Expected newline after comment string.");
+ }
+ } else {
+ break;
}
}
@@ -561,6 +534,16 @@ void GDScriptParser::parse_program() {
end_statement("superclass");
}
break;
+ case GDScriptTokenizer::Token::LITERAL:
+ if (current.literal.get_type() == Variant::STRING) {
+ // Allow strings in class body as multiline comments.
+ advance();
+ if (!match(GDScriptTokenizer::Token::NEWLINE)) {
+ push_error("Expected newline after comment string.");
+ }
+ break;
+ }
+ [[fallthrough]];
default:
// No tokens are allowed between script annotations and class/extends.
can_have_class_or_extends = false;
@@ -729,14 +712,14 @@ void GDScriptParser::parse_extends() {
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after "extends".)")) {
return;
}
- current_class->extends.push_back(previous.literal);
+ current_class->extends.push_back(parse_identifier());
while (match(GDScriptTokenizer::Token::PERIOD)) {
make_completion_context(COMPLETION_INHERIT_TYPE, current_class, chain_index++);
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected superclass name after ".".)")) {
return;
}
- current_class->extends.push_back(previous.literal);
+ current_class->extends.push_back(parse_identifier());
}
}
@@ -847,7 +830,7 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
push_error(R"(Expected newline after a standalone annotation.)");
}
- if (annotation->name == "@export_category" || annotation->name == "@export_group" || annotation->name == "@export_subgroup") {
+ if (annotation->name == SNAME("@export_category") || annotation->name == SNAME("@export_group") || annotation->name == SNAME("@export_subgroup")) {
current_class->add_member_group(annotation);
} else {
// For potential non-group standalone annotations.
@@ -866,6 +849,16 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
case GDScriptTokenizer::Token::DEDENT:
class_end = true;
break;
+ case GDScriptTokenizer::Token::LITERAL:
+ if (current.literal.get_type() == Variant::STRING) {
+ // Allow strings in class body as multiline comments.
+ advance();
+ if (!match(GDScriptTokenizer::Token::NEWLINE)) {
+ push_error("Expected newline after comment string.");
+ }
+ break;
+ }
+ [[fallthrough]];
default:
// Display a completion with identifiers.
make_completion_context(COMPLETION_IDENTIFIER, nullptr);
@@ -982,14 +975,14 @@ GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_var
// Run with a loop because order doesn't matter.
for (int i = 0; i < 2; i++) {
- if (function->name == "set") {
+ if (function->name == SNAME("set")) {
if (setter_used) {
push_error(R"(Properties can only have one setter.)");
} else {
parse_property_setter(property);
setter_used = true;
}
- } else if (function->name == "get") {
+ } else if (function->name == SNAME("get")) {
if (getter_used) {
push_error(R"(Properties can only have one getter.)");
} else {
@@ -1350,7 +1343,7 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod
default_used = true;
} else {
if (default_used) {
- push_error("Cannot have a mandatory parameters after optional parameters.");
+ push_error("Cannot have mandatory parameters after optional parameters.");
continue;
}
}
@@ -1446,27 +1439,32 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
valid = false;
}
- if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
+ if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) {
+ push_multiline(true);
+ advance();
// Arguments.
push_completion_call(annotation);
make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true);
- if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) {
- push_multiline(true);
- int argument_index = 0;
- do {
- make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true);
- set_last_completion_call_arg(argument_index++);
- ExpressionNode *argument = parse_expression(false);
- if (argument == nullptr) {
- valid = false;
- continue;
- }
- annotation->arguments.push_back(argument);
- } while (match(GDScriptTokenizer::Token::COMMA));
- pop_multiline();
+ int argument_index = 0;
+ do {
+ if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) {
+ // Allow for trailing comma.
+ break;
+ }
- consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after annotation arguments.)*");
- }
+ make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true);
+ set_last_completion_call_arg(argument_index++);
+ ExpressionNode *argument = parse_expression(false);
+ if (argument == nullptr) {
+ push_error("Expected expression as the annotation argument.");
+ valid = false;
+ continue;
+ }
+ annotation->arguments.push_back(argument);
+ } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end());
+
+ pop_multiline();
+ consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after annotation arguments.)*");
pop_completion_call();
}
complete_extents(annotation);
@@ -1474,7 +1472,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional.
if (valid) {
- valid = validate_annotation_argument_count(annotation);
+ valid = validate_annotation_arguments(annotation);
}
return valid ? annotation : nullptr;
@@ -1482,7 +1480,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
void GDScriptParser::clear_unused_annotations() {
for (const AnnotationNode *annotation : annotation_stack) {
- push_error(vformat(R"(Annotation "%s" does not precedes a valid target, so it will have no effect.)", annotation->name), annotation);
+ push_error(vformat(R"(Annotation "%s" does not precede a valid target, so it will have no effect.)", annotation->name), annotation);
}
annotation_stack.clear();
@@ -1528,7 +1526,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context,
int error_count = 0;
do {
- if (!multiline && previous.type == GDScriptTokenizer::Token::SEMICOLON && check(GDScriptTokenizer::Token::NEWLINE)) {
+ if (is_at_end() || (!multiline && previous.type == GDScriptTokenizer::Token::SEMICOLON && check(GDScriptTokenizer::Token::NEWLINE))) {
break;
}
Node *statement = parse_statement();
@@ -1712,6 +1710,12 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
// Standalone lambdas can't be used, so make this an error.
push_error("Standalone lambdas cannot be accessed. Consider assigning it to a variable.", expression);
break;
+ case Node::LITERAL:
+ if (static_cast<GDScriptParser::LiteralNode *>(expression)->value.get_type() == Variant::STRING) {
+ // Allow strings as multiline comments.
+ break;
+ }
+ [[fallthrough]];
default:
push_warning(expression, GDScriptWarning::STANDALONE_EXPRESSION);
}
@@ -1818,7 +1822,7 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
n_for->list = parse_expression(false);
if (!n_for->list) {
- push_error(R"(Expected a list or range after "in".)");
+ push_error(R"(Expected iterable after "in".)");
}
consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "for" condition.)");
@@ -1870,10 +1874,18 @@ GDScriptParser::IfNode *GDScriptParser::parse_if(const String &p_token) {
if (match(GDScriptTokenizer::Token::ELIF)) {
SuiteNode *else_block = alloc_node<SuiteNode>();
+ else_block->parent_function = current_function;
+ else_block->parent_block = current_suite;
+
+ SuiteNode *previous_suite = current_suite;
+ current_suite = else_block;
+
IfNode *elif = parse_if("elif");
else_block->statements.push_back(elif);
complete_extents(else_block);
n_if->false_block = else_block;
+
+ current_suite = previous_suite;
} else if (match(GDScriptTokenizer::Token::ELSE)) {
consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "else".)");
n_if->false_block = parse_suite(R"("else" block)");
@@ -1906,10 +1918,8 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() {
return match;
}
-#ifdef DEBUG_ENABLED
bool all_have_return = true;
bool have_wildcard = false;
-#endif
while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) {
MatchBranchNode *branch = parse_match_branch();
@@ -1922,21 +1932,19 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() {
if (have_wildcard && !branch->patterns.is_empty()) {
push_warning(branch->patterns[0], GDScriptWarning::UNREACHABLE_PATTERN);
}
+#endif
have_wildcard = have_wildcard || branch->has_wildcard;
all_have_return = all_have_return && branch->block->has_return;
-#endif
match->branches.push_back(branch);
}
complete_extents(match);
consume(GDScriptTokenizer::Token::DEDENT, R"(Expected an indented block after "match" statement.)");
-#ifdef DEBUG_ENABLED
if (all_have_return && have_wildcard) {
current_suite->has_return = true;
}
-#endif
return match;
}
@@ -2178,7 +2186,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr
make_completion_context(COMPLETION_IDENTIFIER, nullptr);
GDScriptTokenizer::Token token = current;
- ParseFunction prefix_rule = get_rule(token.type)->prefix;
+ GDScriptTokenizer::Token::Type token_type = token.type;
+ if (token.is_identifier()) {
+ // Allow keywords that can be treated as identifiers.
+ token_type = GDScriptTokenizer::Token::IDENTIFIER;
+ }
+ ParseFunction prefix_rule = get_rule(token_type)->prefix;
if (prefix_rule == nullptr) {
// Expected expression. Let the caller give the proper error message.
@@ -2455,9 +2468,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(Expression
operation->operation = BinaryOpNode::OP_LOGIC_OR;
operation->variant_op = Variant::OP_OR;
break;
- case GDScriptTokenizer::Token::IS:
- operation->operation = BinaryOpNode::OP_TYPE_TEST;
- break;
case GDScriptTokenizer::Token::IN:
operation->operation = BinaryOpNode::OP_CONTENT_TEST;
operation->variant_op = Variant::OP_IN;
@@ -2815,6 +2825,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode *
attribute->base = p_previous_operand;
+ if (current.is_node_name()) {
+ current.type = GDScriptTokenizer::Token::IDENTIFIER;
+ }
if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier after "." for attribute access.)")) {
complete_extents(attribute);
return attribute;
@@ -2925,7 +2938,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre
// Arguments.
CompletionType ct = COMPLETION_CALL_ARGUMENTS;
- if (call->function_name == "load") {
+ if (call->function_name == SNAME("load")) {
ct = COMPLETION_RESOURCE_PATH;
}
push_completion_call(call);
@@ -3043,7 +3056,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
path_state = PATH_STATE_NODE_NAME;
} else if (current.is_node_name()) {
advance();
- get_node->full_path += previous.get_identifier();
+ String identifier = previous.get_identifier();
+#ifdef DEBUG_ENABLED
+ // Check spoofing.
+ if (TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY) && TS->spoof_check(identifier)) {
+ push_warning(get_node, GDScriptWarning::CONFUSABLE_IDENTIFIER, identifier);
+ }
+#endif
+ get_node->full_path += identifier;
path_state = PATH_STATE_NODE_NAME;
} else if (!check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) {
@@ -3126,6 +3146,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
bool previous_in_lambda = in_lambda;
in_lambda = true;
+ // Save break/continue state.
+ bool could_break = can_break;
+ bool could_continue = can_continue;
+
+ // Disallow break/continue.
+ can_break = false;
+ can_continue = false;
+
function->body = parse_suite("lambda declaration", body, true);
complete_extents(function);
complete_extents(lambda);
@@ -3143,9 +3171,30 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p
current_function = previous_function;
in_lambda = previous_in_lambda;
lambda->function = function;
+
+ // Reset break/continue state.
+ can_break = could_break;
+ can_continue = could_continue;
+
return lambda;
}
+GDScriptParser::ExpressionNode *GDScriptParser::parse_type_test(ExpressionNode *p_previous_operand, bool p_can_assign) {
+ TypeTestNode *type_test = alloc_node<TypeTestNode>();
+ reset_extents(type_test, p_previous_operand);
+ update_extents(type_test);
+
+ type_test->operand = p_previous_operand;
+ type_test->test_type = parse_type();
+ complete_extents(type_test);
+
+ if (type_test->test_type == nullptr) {
+ push_error(R"(Expected type specifier after "is".)");
+ }
+
+ return type_test;
+}
+
GDScriptParser::ExpressionNode *GDScriptParser::parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign) {
push_error(R"("yield" was removed in Godot 4.0. Use "await" instead.)");
return nullptr;
@@ -3514,7 +3563,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty
{ nullptr, nullptr, PREC_NONE }, // EXTENDS,
{ &GDScriptParser::parse_lambda, nullptr, PREC_NONE }, // FUNC,
{ nullptr, &GDScriptParser::parse_binary_operator, PREC_CONTENT_TEST }, // IN,
- { nullptr, &GDScriptParser::parse_binary_operator, PREC_TYPE_TEST }, // IS,
+ { nullptr, &GDScriptParser::parse_type_test, PREC_TYPE_TEST }, // IS,
{ nullptr, nullptr, PREC_NONE }, // NAMESPACE,
{ &GDScriptParser::parse_preload, nullptr, PREC_NONE }, // PRELOAD,
{ &GDScriptParser::parse_self, nullptr, PREC_NONE }, // SELF,
@@ -3585,7 +3634,11 @@ const GDScriptParser::SuiteNode::Local &GDScriptParser::SuiteNode::get_local(con
return empty;
}
-bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target) const {
+bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target) {
+ if (is_applied) {
+ return true;
+ }
+ is_applied = true;
return (p_this->*(p_this->valid_annotations[name].apply))(this, p_target);
}
@@ -3593,7 +3646,7 @@ bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const {
return (info->target_kind & p_target_kinds) > 0;
}
-bool GDScriptParser::validate_annotation_argument_count(AnnotationNode *p_annotation) {
+bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) {
ERR_FAIL_COND_V_MSG(!valid_annotations.has(p_annotation->name), false, vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name));
const MethodInfo &info = valid_annotations[p_annotation->name].info;
@@ -3608,17 +3661,55 @@ bool GDScriptParser::validate_annotation_argument_count(AnnotationNode *p_annota
return false;
}
+ // `@icon`'s argument needs to be resolved in the parser. See GH-72444.
+ if (p_annotation->name == SNAME("@icon")) {
+ ExpressionNode *argument = p_annotation->arguments[0];
+
+ if (argument->type != Node::LITERAL) {
+ push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)", argument);
+ return false;
+ }
+
+ Variant value = static_cast<LiteralNode *>(argument)->value;
+
+ if (value.get_type() != Variant::STRING) {
+ push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)", argument);
+ return false;
+ }
+
+ p_annotation->resolved_arguments.push_back(value);
+ }
+
+ // For other annotations, see `GDScriptAnalyzer::resolve_annotation()`.
+
return true;
}
bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_node) {
+#ifdef DEBUG_ENABLED
+ if (this->_is_tool) {
+ push_error(R"("@tool" annotation can only be used once.)", p_annotation);
+ return false;
+ }
+#endif // DEBUG_ENABLED
this->_is_tool = true;
return true;
}
bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_node) {
ERR_FAIL_COND_V_MSG(p_node->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)");
+ ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false);
ClassNode *p_class = static_cast<ClassNode *>(p_node);
+#ifdef DEBUG_ENABLED
+ if (!p_class->icon_path.is_empty()) {
+ push_error(R"("@icon" annotation can only be used once.)", p_annotation);
+ return false;
+ }
+ if (String(p_annotation->resolved_arguments[0]).is_empty()) {
+ push_error(R"("@icon" annotation argument must contain the path to the icon.)", p_annotation->arguments[0]);
+ return false;
+ }
+#endif // DEBUG_ENABLED
p_class->icon_path = p_annotation->resolved_arguments[0];
return true;
}
@@ -3626,6 +3717,10 @@ bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p
bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_node) {
ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)");
+ if (current_class && !ClassDB::is_parent_class(current_class->get_datatype().native_type, SNAME("Node"))) {
+ push_error(R"("@onready" can only be used in classes that inherit "Node".)", p_annotation);
+ }
+
VariableNode *variable = static_cast<VariableNode *>(p_node);
if (variable->onready) {
push_error(R"("@onready" annotation can only be used once per variable.)");
@@ -3640,15 +3735,6 @@ template <PropertyHint t_hint, Variant::Type t_type>
bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_node) {
ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name));
- {
- const int max_flags = 32;
-
- if (t_hint == PropertyHint::PROPERTY_HINT_FLAGS && p_annotation->resolved_arguments.size() > max_flags) {
- push_error(vformat(R"(The argument count limit for "@export_flags" is exceeded (%d/%d).)", p_annotation->resolved_arguments.size(), max_flags), p_annotation);
- return false;
- }
- }
-
VariableNode *variable = static_cast<VariableNode *>(p_node);
if (variable->exported) {
push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation);
@@ -3662,14 +3748,50 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
String hint_string;
for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) {
- if (p_annotation->name != SNAME("@export_placeholder") && String(p_annotation->resolved_arguments[i]).contains(",")) {
- push_error(vformat(R"(Argument %d of annotation "%s" contains a comma. Use separate arguments instead.)", i + 1, p_annotation->name), p_annotation->arguments[i]);
- return false;
+ String arg_string = String(p_annotation->resolved_arguments[i]);
+
+ if (p_annotation->name != SNAME("@export_placeholder")) {
+ if (arg_string.is_empty()) {
+ push_error(vformat(R"(Argument %d of annotation "%s" is empty.)", i + 1, p_annotation->name), p_annotation->arguments[i]);
+ return false;
+ }
+ if (arg_string.contains(",")) {
+ push_error(vformat(R"(Argument %d of annotation "%s" contains a comma. Use separate arguments instead.)", i + 1, p_annotation->name), p_annotation->arguments[i]);
+ return false;
+ }
+ }
+
+ if (p_annotation->name == SNAME("@export_flags")) {
+ const int64_t max_flags = 32;
+ Vector<String> t = arg_string.split(":", true, 1);
+ if (t[0].is_empty()) {
+ push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Expected flag name.)", i + 1), p_annotation->arguments[i]);
+ return false;
+ }
+ if (t.size() == 2) {
+ if (t[1].is_empty()) {
+ push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Expected flag value.)", i + 1), p_annotation->arguments[i]);
+ return false;
+ }
+ if (!t[1].is_valid_int()) {
+ push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": The flag value must be a valid integer.)", i + 1), p_annotation->arguments[i]);
+ return false;
+ }
+ int64_t value = t[1].to_int();
+ if (value < 1 || value >= (1LL << max_flags)) {
+ push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": The flag value must be at least 1 and at most 2 ** %d - 1.)", i + 1, max_flags), p_annotation->arguments[i]);
+ return false;
+ }
+ } else if (i >= max_flags) {
+ push_error(vformat(R"(Invalid argument %d of annotation "@export_flags": Starting from argument %d, the flag value must be specified explicitly.)", i + 1, max_flags + 1), p_annotation->arguments[i]);
+ return false;
+ }
}
+
if (i > 0) {
hint_string += ",";
}
- hint_string += String(p_annotation->resolved_arguments[i]);
+ hint_string += arg_string;
}
variable->export_info.hint_string = hint_string;
@@ -3700,6 +3822,13 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.type = Variant::DICTIONARY;
return true;
+ } else if (export_type.builtin_type == Variant::PACKED_STRING_ARRAY) {
+ String hint_prefix = itos(Variant::STRING) + "/" + itos(variable->export_info.hint);
+ variable->export_info.hint = PROPERTY_HINT_TYPE_STRING;
+ variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string;
+ variable->export_info.type = Variant::PACKED_STRING_ARRAY;
+
+ return true;
}
}
@@ -3751,7 +3880,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;
variable->export_info.hint_string = export_type.to_string();
} else {
- push_error(R"(Export type can only be built-in, a resource, a node or an enum.)", variable);
+ push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable);
return false;
}
@@ -3796,8 +3925,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.hint_string = enum_hint_string;
} break;
default:
- // TODO: Allow custom user resources.
- push_error(R"(Export type can only be built-in, a resource, or an enum.)", variable);
+ push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable);
break;
}
@@ -3846,6 +3974,10 @@ template <PropertyUsageFlags t_usage>
bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_node) {
AnnotationNode *annotation = const_cast<AnnotationNode *>(p_annotation);
+ if (annotation->resolved_arguments.is_empty()) {
+ return false;
+ }
+
annotation->export_info.name = annotation->resolved_arguments[0];
switch (t_usage) {
@@ -3903,7 +4035,7 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_
Dictionary rpc_config;
rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY;
- if (p_annotation->resolved_arguments.size()) {
+ if (!p_annotation->resolved_arguments.is_empty()) {
int last = p_annotation->resolved_arguments.size() - 1;
if (p_annotation->resolved_arguments[last].get_type() == Variant::INT) {
rpc_config["channel"] = p_annotation->resolved_arguments[last].operator int();
@@ -3913,26 +4045,46 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_
push_error(R"(Invalid RPC arguments. At most 4 arguments are allowed, where only the last argument can be an integer to specify the channel.')", p_annotation);
return false;
}
+
+ unsigned char locality_args = 0;
+ unsigned char permission_args = 0;
+ unsigned char transfer_mode_args = 0;
+
for (int i = last; i >= 0; i--) {
- String mode = p_annotation->resolved_arguments[i].operator String();
- if (mode == "any_peer") {
- rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_ANY_PEER;
- } else if (mode == "authority") {
- rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY;
- } else if (mode == "call_local") {
+ String arg = p_annotation->resolved_arguments[i].operator String();
+ if (arg == "call_local") {
+ locality_args++;
rpc_config["call_local"] = true;
- } else if (mode == "call_remote") {
+ } else if (arg == "call_remote") {
+ locality_args++;
rpc_config["call_local"] = false;
- } else if (mode == "reliable") {
+ } else if (arg == "any_peer") {
+ permission_args++;
+ rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_ANY_PEER;
+ } else if (arg == "authority") {
+ permission_args++;
+ rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY;
+ } else if (arg == "reliable") {
+ transfer_mode_args++;
rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_RELIABLE;
- } else if (mode == "unreliable") {
+ } else if (arg == "unreliable") {
+ transfer_mode_args++;
rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE;
- } else if (mode == "unreliable_ordered") {
+ } else if (arg == "unreliable_ordered") {
+ transfer_mode_args++;
rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE_ORDERED;
} else {
- push_error(R"(Invalid RPC argument. Must be one of: 'call_local'/'call_remote' (local calls), 'any_peer'/'authority' (permission), 'reliable'/'unreliable'/'unreliable_ordered' (transfer mode).)", p_annotation);
+ push_error(R"(Invalid RPC argument. Must be one of: "call_local"/"call_remote" (local calls), "any_peer"/"authority" (permission), "reliable"/"unreliable"/"unreliable_ordered" (transfer mode).)", p_annotation);
}
}
+
+ if (locality_args > 1) {
+ push_error(R"(Invalid RPC config. The locality ("call_local"/"call_remote") must be specified no more than once.)", p_annotation);
+ } else if (permission_args > 1) {
+ push_error(R"(Invalid RPC config. The permission ("any_peer"/"authority") must be specified no more than once.)", p_annotation);
+ } else if (transfer_mode_args > 1) {
+ push_error(R"(Invalid RPC config. The transfer mode ("reliable"/"unreliable"/"unreliable_ordered") must be specified no more than once.)", p_annotation);
+ }
}
function->rpc_config = rpc_config;
return true;
@@ -3992,9 +4144,6 @@ String GDScriptParser::DataType::to_string() const {
}
return native_type.operator String();
case CLASS:
- if (is_meta_type) {
- return GDScript::get_class_static();
- }
if (class_type->identifier != nullptr) {
return class_type->identifier->name.operator String();
}
@@ -4276,9 +4425,6 @@ void GDScriptParser::TreePrinter::print_binary_op(BinaryOpNode *p_binary_op) {
case BinaryOpNode::OP_LOGIC_OR:
push_text(" OR ");
break;
- case BinaryOpNode::OP_TYPE_TEST:
- push_text(" IS ");
- break;
case BinaryOpNode::OP_CONTENT_TEST:
push_text(" IN ");
break;
@@ -4353,7 +4499,7 @@ void GDScriptParser::TreePrinter::print_class(ClassNode *p_class) {
} else {
first = false;
}
- push_text(p_class->extends[i]);
+ push_text(p_class->extends[i]->name);
}
}
@@ -4481,6 +4627,9 @@ void GDScriptParser::TreePrinter::print_expression(ExpressionNode *p_expression)
case Node::TERNARY_OPERATOR:
print_ternary_op(static_cast<TernaryOpNode *>(p_expression));
break;
+ case Node::TYPE_TEST:
+ print_type_test(static_cast<TypeTestNode *>(p_expression));
+ break;
case Node::UNARY_OPERATOR:
print_unary_op(static_cast<UnaryOpNode *>(p_expression));
break;
@@ -4840,6 +4989,12 @@ void GDScriptParser::TreePrinter::print_type(TypeNode *p_type) {
}
}
+void GDScriptParser::TreePrinter::print_type_test(TypeTestNode *p_test) {
+ print_expression(p_test->operand);
+ push_text(" IS ");
+ print_type(p_test->test_type);
+}
+
void GDScriptParser::TreePrinter::print_unary_op(UnaryOpNode *p_unary_op) {
// Surround in parenthesis for disambiguation.
push_text("(");