summaryrefslogtreecommitdiff
path: root/modules/gdscript
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript')
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml2
-rw-r--r--modules/gdscript/gdscript.cpp100
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp85
-rw-r--r--modules/gdscript/gdscript_analyzer.h2
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp67
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h25
-rw-r--r--modules/gdscript/gdscript_codegen.h6
-rw-r--r--modules/gdscript/gdscript_compiler.cpp17
-rw-r--r--modules/gdscript/gdscript_disassembler.cpp21
-rw-r--r--modules/gdscript/gdscript_editor.cpp3
-rw-r--r--modules/gdscript/gdscript_function.h10
-rw-r--r--modules/gdscript/gdscript_parser.cpp121
-rw-r--r--modules/gdscript/gdscript_parser.h9
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp62
-rw-r--r--modules/gdscript/gdscript_tokenizer.h7
-rw-r--r--modules/gdscript/gdscript_warning.cpp5
-rw-r--r--modules/gdscript/gdscript_warning.h1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.gd10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.out1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/identifier_similar_to_keyword.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/identifier_similar_to_keyword.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/match.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/match.out1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/nested_match.gd7
-rw-r--r--modules/gdscript/tests/scripts/parser/features/nested_match.out1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd35
-rw-r--r--modules/gdscript/tests/scripts/parser/features/unicode_identifiers.out14
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.out7
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.out7
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/await_on_void.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd10
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.out2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/gdscript.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd10
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.gd2
55 files changed, 405 insertions, 328 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 3fe741a582..5bed1b9da3 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -485,7 +485,7 @@
Export a [NodePath] property with a filter for allowed node types.
See also [constant PROPERTY_HINT_NODE_PATH_VALID_TYPES].
[codeblock]
- @export_node_path(Button, TouchScreenButton) var some_button
+ @export_node_path("Button", "TouchScreenButton") var some_button
[/codeblock]
</description>
</annotation>
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 6b325d6451..4fc3929bbd 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -2448,7 +2448,6 @@ bool GDScriptLanguage::handles_global_class_type(const String &p_type) const {
}
String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const {
- Vector<uint8_t> sourcef;
Error err;
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
if (err) {
@@ -2459,88 +2458,31 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
GDScriptParser parser;
err = parser.parse(source, p_path, false);
+ if (err) {
+ return String();
+ }
- // TODO: Simplify this code by using the analyzer to get full inheritance.
- if (err == OK) {
- const GDScriptParser::ClassNode *c = parser.get_tree();
- if (r_icon_path) {
- if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) {
- *r_icon_path = c->icon_path;
- } else if (c->icon_path.is_relative_path()) {
- *r_icon_path = p_path.get_base_dir().path_join(c->icon_path).simplify_path();
- }
- }
- if (r_base_type) {
- const GDScriptParser::ClassNode *subclass = c;
- String path = p_path;
- GDScriptParser subparser;
- while (subclass) {
- if (subclass->extends_used) {
- if (!subclass->extends_path.is_empty()) {
- if (subclass->extends.size() == 0) {
- get_global_class_name(subclass->extends_path, r_base_type);
- subclass = nullptr;
- break;
- } else {
- Vector<StringName> extend_classes = subclass->extends;
-
- Ref<FileAccess> subfile = FileAccess::open(subclass->extends_path, FileAccess::READ);
- if (subfile.is_null()) {
- break;
- }
- String subsource = subfile->get_as_utf8_string();
-
- if (subsource.is_empty()) {
- break;
- }
- String subpath = subclass->extends_path;
- if (subpath.is_relative_path()) {
- subpath = path.get_base_dir().path_join(subpath).simplify_path();
- }
-
- if (OK != subparser.parse(subsource, subpath, false)) {
- break;
- }
- path = subpath;
- subclass = subparser.get_tree();
-
- while (extend_classes.size() > 0) {
- bool found = false;
- for (int i = 0; i < subclass->members.size(); i++) {
- if (subclass->members[i].type != GDScriptParser::ClassNode::Member::CLASS) {
- continue;
- }
-
- const GDScriptParser::ClassNode *inner_class = subclass->members[i].m_class;
- if (inner_class->identifier->name == extend_classes[0]) {
- extend_classes.remove_at(0);
- found = true;
- subclass = inner_class;
- break;
- }
- }
- if (!found) {
- subclass = nullptr;
- break;
- }
- }
- }
- } else if (subclass->extends.size() == 1) {
- *r_base_type = subclass->extends[0];
- subclass = nullptr;
- } else {
- break;
- }
- } else {
- *r_base_type = "RefCounted";
- subclass = nullptr;
- }
- }
+ GDScriptAnalyzer analyzer(&parser);
+ err = analyzer.resolve_inheritance();
+ if (err) {
+ return String();
+ }
+
+ const GDScriptParser::ClassNode *c = parser.get_tree();
+
+ if (r_base_type) {
+ *r_base_type = c->get_datatype().native_type;
+ }
+
+ if (r_icon_path) {
+ if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) {
+ *r_icon_path = c->icon_path.simplify_path();
+ } else if (c->icon_path.is_relative_path()) {
+ *r_icon_path = p_path.get_base_dir().path_join(c->icon_path).simplify_path();
}
- return c->identifier != nullptr ? String(c->identifier->name) : String();
}
- return String();
+ return c->identifier != nullptr ? String(c->identifier->name) : String();
}
GDScriptLanguage::GDScriptLanguage() {
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index e04a962dcb..f6385dd132 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -802,6 +802,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
// Apply annotations.
for (GDScriptParser::AnnotationNode *&E : member.variable->annotations) {
+ resolve_annotation(E);
E->apply(parser, member.variable);
}
} break;
@@ -812,6 +813,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
// Apply annotations.
for (GDScriptParser::AnnotationNode *&E : member.constant->annotations) {
+ resolve_annotation(E);
E->apply(parser, member.constant);
}
} break;
@@ -835,6 +837,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
// Apply annotations.
for (GDScriptParser::AnnotationNode *&E : member.signal->annotations) {
+ resolve_annotation(E);
E->apply(parser, member.signal);
}
} break;
@@ -876,12 +879,13 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
current_enum = prev_enum;
- dictionary.set_read_only(true);
+ dictionary.make_read_only();
member.m_enum->set_datatype(enum_type);
member.m_enum->dictionary = dictionary;
// Apply annotations.
for (GDScriptParser::AnnotationNode *&E : member.m_enum->annotations) {
+ resolve_annotation(E);
E->apply(parser, member.m_enum);
}
} break;
@@ -1064,6 +1068,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) {
// Apply annotations.
for (GDScriptParser::AnnotationNode *&E : member.function->annotations) {
+ resolve_annotation(E);
E->apply(parser, member.function);
}
@@ -1290,7 +1295,55 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root
}
void GDScriptAnalyzer::resolve_annotation(GDScriptParser::AnnotationNode *p_annotation) {
- // TODO: Add second validation function for annotations, so they can use checked types.
+ ERR_FAIL_COND_MSG(!parser->valid_annotations.has(p_annotation->name), vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name));
+
+ const MethodInfo &annotation_info = parser->valid_annotations[p_annotation->name].info;
+
+ const List<PropertyInfo>::Element *E = annotation_info.arguments.front();
+ for (int i = 0; i < p_annotation->arguments.size(); i++) {
+ GDScriptParser::ExpressionNode *argument = p_annotation->arguments[i];
+ const PropertyInfo &argument_info = E->get();
+
+ if (E->next() != nullptr) {
+ E = E->next();
+ }
+
+ reduce_expression(argument);
+
+ if (!argument->is_constant) {
+ push_error(vformat(R"(Argument %d of annotation "%s" isn't a constant expression.)", i + 1, p_annotation->name), argument);
+ return;
+ }
+
+ Variant value = argument->reduced_value;
+
+ if (value.get_type() != argument_info.type) {
+#ifdef DEBUG_ENABLED
+ if (argument_info.type == Variant::INT && value.get_type() == Variant::FLOAT) {
+ parser->push_warning(argument, GDScriptWarning::NARROWING_CONVERSION);
+ }
+#endif
+
+ if (!Variant::can_convert_strict(value.get_type(), argument_info.type)) {
+ push_error(vformat(R"(Invalid argument for annotation "%s": argument %d should be "%s" but is "%s".)", p_annotation->name, i + 1, Variant::get_type_name(argument_info.type), argument->get_datatype().to_string()), argument);
+ return;
+ }
+
+ Variant converted_to;
+ const Variant *converted_from = &value;
+ Callable::CallError call_error;
+ Variant::construct(argument_info.type, converted_to, &converted_from, 1, call_error);
+
+ if (call_error.error != Callable::CallError::CALL_OK) {
+ push_error(vformat(R"(Cannot convert argument %d of annotation "%s" from "%s" to "%s".)", i + 1, p_annotation->name, Variant::get_type_name(value.get_type()), Variant::get_type_name(argument_info.type)), argument);
+ return;
+ }
+
+ value = converted_to;
+ }
+
+ p_annotation->resolved_arguments.push_back(value);
+ }
}
void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *p_function, const GDScriptParser::Node *p_source, bool p_is_lambda) {
@@ -1486,8 +1539,10 @@ void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScript
void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) {
for (int i = 0; i < p_suite->statements.size(); i++) {
GDScriptParser::Node *stmt = p_suite->statements[i];
- for (GDScriptParser::AnnotationNode *&annotation : stmt->annotations) {
- annotation->apply(parser, stmt);
+ // Apply annotations.
+ for (GDScriptParser::AnnotationNode *&E : stmt->annotations) {
+ resolve_annotation(E);
+ E->apply(parser, stmt);
}
#ifdef DEBUG_ENABLED
@@ -1544,10 +1599,8 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
GDScriptParser::DataType initializer_type = p_assignable->initializer->get_datatype();
if (p_assignable->infer_datatype) {
- if (!initializer_type.is_set() || initializer_type.has_no_type()) {
+ if (!initializer_type.is_set() || initializer_type.has_no_type() || !initializer_type.is_hard_type()) {
push_error(vformat(R"(Cannot infer the type of "%s" %s because the value doesn't have a set type.)", p_assignable->identifier->name, p_kind), p_assignable->initializer);
- } else if (initializer_type.is_variant() && !initializer_type.is_hard_type()) {
- push_error(vformat(R"(Cannot infer the type of "%s" %s because the value is Variant. Use explicit "Variant" type if this is intended.)", p_assignable->identifier->name, p_kind), p_assignable->initializer);
} else if (initializer_type.kind == GDScriptParser::DataType::BUILTIN && initializer_type.builtin_type == Variant::NIL && !is_constant) {
push_error(vformat(R"(Cannot infer the type of "%s" %s because the value is "null".)", p_assignable->identifier->name, p_kind), p_assignable->initializer);
}
@@ -2014,7 +2067,7 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre
reduce_subscript(static_cast<GDScriptParser::SubscriptNode *>(p_expression));
break;
case GDScriptParser::Node::TERNARY_OPERATOR:
- reduce_ternary_op(static_cast<GDScriptParser::TernaryOpNode *>(p_expression));
+ reduce_ternary_op(static_cast<GDScriptParser::TernaryOpNode *>(p_expression), p_is_root);
break;
case GDScriptParser::Node::UNARY_OPERATOR:
reduce_unary_op(static_cast<GDScriptParser::UnaryOpNode *>(p_expression));
@@ -3534,6 +3587,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
}
#endif
result_type.kind = GDScriptParser::DataType::VARIANT;
+ mark_node_unsafe(p_subscript);
}
}
if (!valid) {
@@ -3735,10 +3789,10 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
p_subscript->set_datatype(result_type);
}
-void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op) {
+void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op, bool p_is_root) {
reduce_expression(p_ternary_op->condition);
- reduce_expression(p_ternary_op->true_expr);
- reduce_expression(p_ternary_op->false_expr);
+ reduce_expression(p_ternary_op->true_expr, p_is_root);
+ reduce_expression(p_ternary_op->false_expr, p_is_root);
GDScriptParser::DataType result;
@@ -3838,7 +3892,7 @@ void GDScriptAnalyzer::const_fold_array(GDScriptParser::ArrayNode *p_array, bool
array[i] = p_array->elements[i]->reduced_value;
}
if (p_is_const) {
- array.set_read_only(true);
+ array.make_read_only();
}
p_array->is_constant = true;
p_array->reduced_value = array;
@@ -3865,7 +3919,7 @@ void GDScriptAnalyzer::const_fold_dictionary(GDScriptParser::DictionaryNode *p_d
dict[element.key->reduced_value] = element.value->reduced_value;
}
if (p_is_const) {
- dict.set_read_only(true);
+ dict.make_read_only();
}
p_dictionary->is_constant = true;
p_dictionary->reduced_value = dict;
@@ -4552,6 +4606,11 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) {
}
Error GDScriptAnalyzer::resolve_inheritance() {
+ // Apply annotations.
+ for (GDScriptParser::AnnotationNode *&E : parser->head->annotations) {
+ resolve_annotation(E);
+ E->apply(parser, parser->head);
+ }
return resolve_class_inheritance(parser->head, true);
}
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index b22d47982f..5397be33f0 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -99,7 +99,7 @@ class GDScriptAnalyzer {
void reduce_preload(GDScriptParser::PreloadNode *p_preload);
void reduce_self(GDScriptParser::SelfNode *p_self);
void reduce_subscript(GDScriptParser::SubscriptNode *p_subscript);
- void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op);
+ void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op, bool p_is_root = false);
void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op);
void const_fold_array(GDScriptParser::ArrayNode *p_array, bool p_is_const);
diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp
index 6c80fb7665..e19dda090e 100644
--- a/modules/gdscript/gdscript_byte_codegen.cpp
+++ b/modules/gdscript/gdscript_byte_codegen.cpp
@@ -401,6 +401,16 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() {
function->_instruction_args_size = instr_args_max;
function->_ptrcall_args_size = ptrcall_max;
+#ifdef DEBUG_ENABLED
+ function->operator_names = operator_names;
+ function->setter_names = setter_names;
+ function->getter_names = getter_names;
+ function->builtin_methods_names = builtin_methods_names;
+ function->constructors_names = constructors_names;
+ function->utilities_names = utilities_names;
+ function->gds_utilities_names = gds_utilities_names;
+#endif
+
ended = true;
return function;
}
@@ -551,6 +561,9 @@ void GDScriptByteCodeGenerator::write_unary_operator(const Address &p_target, Va
append(Address());
append(p_target);
append(op_func);
+#ifdef DEBUG_ENABLED
+ add_debug_name(operator_names, get_operation_pos(op_func), Variant::get_operator_name(p_operator));
+#endif
return;
}
@@ -580,6 +593,9 @@ void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, V
append(p_right_operand);
append(p_target);
append(op_func);
+#ifdef DEBUG_ENABLED
+ add_debug_name(operator_names, get_operation_pos(op_func), Variant::get_operator_name(p_operator));
+#endif
return;
}
@@ -765,6 +781,9 @@ void GDScriptByteCodeGenerator::write_set_named(const Address &p_target, const S
append(p_target);
append(p_source);
append(setter);
+#ifdef DEBUG_ENABLED
+ add_debug_name(setter_names, get_setter_pos(setter), p_name);
+#endif
return;
}
append_opcode(GDScriptFunction::OPCODE_SET_NAMED);
@@ -780,6 +799,9 @@ void GDScriptByteCodeGenerator::write_get_named(const Address &p_target, const S
append(p_source);
append(p_target);
append(getter);
+#ifdef DEBUG_ENABLED
+ add_debug_name(getter_names, get_getter_pos(getter), p_name);
+#endif
return;
}
append_opcode(GDScriptFunction::OPCODE_GET_NAMED);
@@ -972,14 +994,18 @@ void GDScriptByteCodeGenerator::write_call_async(const Address &p_target, const
append(p_function_name);
}
-void GDScriptByteCodeGenerator::write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) {
+void GDScriptByteCodeGenerator::write_call_gdscript_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) {
append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_GDSCRIPT_UTILITY, 1 + p_arguments.size());
+ GDScriptUtilityFunctions::FunctionPtr gds_function = GDScriptUtilityFunctions::get_function(p_function);
for (int i = 0; i < p_arguments.size(); i++) {
append(p_arguments[i]);
}
append(get_call_target(p_target));
append(p_arguments.size());
- append(p_function);
+ append(gds_function);
+#ifdef DEBUG_ENABLED
+ add_debug_name(gds_utilities_names, get_gds_utility_pos(gds_function), p_function);
+#endif
}
void GDScriptByteCodeGenerator::write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) {
@@ -1012,6 +1038,9 @@ void GDScriptByteCodeGenerator::write_call_utility(const Address &p_target, cons
append(target);
append(p_arguments.size());
append(Variant::get_validated_utility_function(p_function));
+#ifdef DEBUG_ENABLED
+ add_debug_name(utilities_names, get_utility_pos(Variant::get_validated_utility_function(p_function)), p_function);
+#endif
} else {
append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_UTILITY, 1 + p_arguments.size());
for (int i = 0; i < p_arguments.size(); i++) {
@@ -1074,6 +1103,9 @@ void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target,
append(target);
append(p_arguments.size());
append(Variant::get_validated_builtin_method(p_type, p_method));
+#ifdef DEBUG_ENABLED
+ add_debug_name(builtin_methods_names, get_builtin_method_pos(Variant::get_validated_builtin_method(p_type, p_method)), p_method);
+#endif
}
void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) {
@@ -1263,6 +1295,9 @@ void GDScriptByteCodeGenerator::write_construct(const Address &p_target, Variant
append(get_call_target(p_target));
append(p_arguments.size());
append(Variant::get_validated_constructor(p_type, valid_constructor));
+#ifdef DEBUG_ENABLED
+ add_debug_name(constructors_names, get_constructor_pos(Variant::get_validated_constructor(p_type, valid_constructor)), Variant::get_type_name(p_type));
+#endif
return;
}
}
@@ -1543,28 +1578,6 @@ void GDScriptByteCodeGenerator::write_endwhile() {
current_breaks_to_patch.pop_back();
}
-void GDScriptByteCodeGenerator::start_match() {
- match_continues_to_patch.push_back(List<int>());
-}
-
-void GDScriptByteCodeGenerator::start_match_branch() {
- // Patch continue statements.
- for (const int &E : match_continues_to_patch.back()->get()) {
- patch_jump(E);
- }
- match_continues_to_patch.pop_back();
- // Start a new list for next branch.
- match_continues_to_patch.push_back(List<int>());
-}
-
-void GDScriptByteCodeGenerator::end_match() {
- // Patch continue statements.
- for (const int &E : match_continues_to_patch.back()->get()) {
- patch_jump(E);
- }
- match_continues_to_patch.pop_back();
-}
-
void GDScriptByteCodeGenerator::write_break() {
append_opcode(GDScriptFunction::OPCODE_JUMP);
current_breaks_to_patch.back()->get().push_back(opcodes.size());
@@ -1576,12 +1589,6 @@ void GDScriptByteCodeGenerator::write_continue() {
append(continue_addrs.back()->get());
}
-void GDScriptByteCodeGenerator::write_continue_match() {
- append_opcode(GDScriptFunction::OPCODE_JUMP);
- match_continues_to_patch.back()->get().push_back(opcodes.size());
- append(0);
-}
-
void GDScriptByteCodeGenerator::write_breakpoint() {
append_opcode(GDScriptFunction::OPCODE_BREAKPOINT);
}
diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h
index 171c505116..8aa02b86c4 100644
--- a/modules/gdscript/gdscript_byte_codegen.h
+++ b/modules/gdscript/gdscript_byte_codegen.h
@@ -95,6 +95,24 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
RBMap<MethodBind *, int> method_bind_map;
RBMap<GDScriptFunction *, int> lambdas_map;
+#if DEBUG_ENABLED
+ // Keep method and property names for pointer and validated operations.
+ // Used when disassembling the bytecode.
+ Vector<String> operator_names;
+ Vector<String> setter_names;
+ Vector<String> getter_names;
+ Vector<String> builtin_methods_names;
+ Vector<String> constructors_names;
+ Vector<String> utilities_names;
+ Vector<String> gds_utilities_names;
+ void add_debug_name(Vector<String> &vector, int index, const String &name) {
+ if (index >= vector.size()) {
+ vector.resize(index + 1);
+ }
+ vector.write[index] = name;
+ }
+#endif
+
// Lists since these can be nested.
List<int> if_jmp_addrs;
List<int> for_jmp_addrs;
@@ -113,7 +131,6 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator {
List<int> ternary_jump_skip_pos;
List<List<int>> current_breaks_to_patch;
- List<List<int>> match_continues_to_patch;
void add_stack_identifier(const StringName &p_id, int p_stackpos) {
if (locals.size() > max_locals) {
@@ -468,8 +485,8 @@ public:
virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
virtual void write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override;
virtual void write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) override;
- virtual void write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) override;
void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, bool p_is_static, const Vector<Address> &p_arguments);
+ virtual void write_call_gdscript_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) override;
virtual void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override;
virtual void write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override;
virtual void write_call_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) override;
@@ -496,12 +513,8 @@ public:
virtual void start_while_condition() override;
virtual void write_while(const Address &p_condition) override;
virtual void write_endwhile() override;
- virtual void start_match() override;
- virtual void start_match_branch() override;
- virtual void end_match() override;
virtual void write_break() override;
virtual void write_continue() override;
- virtual void write_continue_match() override;
virtual void write_breakpoint() override;
virtual void write_newline(int p_line) override;
virtual void write_return(const Address &p_return_value) override;
diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h
index e885938eba..6d42d152b9 100644
--- a/modules/gdscript/gdscript_codegen.h
+++ b/modules/gdscript/gdscript_codegen.h
@@ -121,7 +121,7 @@ public:
virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
virtual void write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0;
virtual void write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) = 0;
- virtual void write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) = 0;
+ virtual void write_call_gdscript_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) = 0;
virtual void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) = 0;
virtual void write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) = 0;
virtual void write_call_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) = 0;
@@ -148,12 +148,8 @@ public:
virtual void start_while_condition() = 0; // Used to allow a jump to the expression evaluation.
virtual void write_while(const Address &p_condition) = 0;
virtual void write_endwhile() = 0;
- virtual void start_match() = 0;
- virtual void start_match_branch() = 0;
- virtual void end_match() = 0;
virtual void write_break() = 0;
virtual void write_continue() = 0;
- virtual void write_continue_match() = 0;
virtual void write_breakpoint() = 0;
virtual void write_newline(int p_line) = 0;
virtual void write_return(const Address &p_return_value) = 0;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index d63a1b4536..c78dd1528f 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -546,7 +546,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
gen->write_call_utility(result, call->function_name, arguments);
} else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptUtilityFunctions::function_exists(call->function_name)) {
// GDScript utility function.
- gen->write_call_gdscript_utility(result, GDScriptUtilityFunctions::get_function(call->function_name), arguments);
+ gen->write_call_gdscript_utility(result, call->function_name, arguments);
} else {
// Regular function.
const GDScriptParser::ExpressionNode *callee = call->callee;
@@ -1410,7 +1410,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c
GDScriptCodeGenerator::Address value_length_addr = codegen.add_temporary(temp_type);
Vector<GDScriptCodeGenerator::Address> len_args;
len_args.push_back(p_value_addr);
- codegen.generator->write_call_gdscript_utility(value_length_addr, GDScriptUtilityFunctions::get_function("len"), len_args);
+ codegen.generator->write_call_gdscript_utility(value_length_addr, "len", len_args);
// Test length compatibility.
temp_type.builtin_type = Variant::BOOL;
@@ -1508,7 +1508,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c
GDScriptCodeGenerator::Address value_length_addr = codegen.add_temporary(temp_type);
Vector<GDScriptCodeGenerator::Address> func_args;
func_args.push_back(p_value_addr);
- codegen.generator->write_call_gdscript_utility(value_length_addr, GDScriptUtilityFunctions::get_function("len"), func_args);
+ codegen.generator->write_call_gdscript_utility(value_length_addr, "len", func_args);
// Test length compatibility.
temp_type.builtin_type = Variant::BOOL;
@@ -1679,7 +1679,6 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
case GDScriptParser::Node::MATCH: {
const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(s);
- gen->start_match();
codegen.start_block();
// Evaluate the match expression.
@@ -1718,7 +1717,6 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
const GDScriptParser::MatchBranchNode *branch = match->branches[j];
- gen->start_match_branch(); // Need so lower level code can patch 'continue' jumps.
codegen.start_block(); // Create an extra block around for binds.
// Add locals in block before patterns, so temporaries don't use the stack address for binds.
@@ -1756,8 +1754,6 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
for (int j = 0; j < match->branches.size(); j++) {
gen->write_endif();
}
-
- gen->end_match();
} break;
case GDScriptParser::Node::IF: {
const GDScriptParser::IfNode *if_n = static_cast<const GDScriptParser::IfNode *>(s);
@@ -1845,12 +1841,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui
gen->write_break();
} break;
case GDScriptParser::Node::CONTINUE: {
- const GDScriptParser::ContinueNode *cont = static_cast<const GDScriptParser::ContinueNode *>(s);
- if (cont->is_for_match) {
- gen->write_continue_match();
- } else {
- gen->write_continue();
- }
+ gen->write_continue();
} break;
case GDScriptParser::Node::RETURN: {
const GDScriptParser::ReturnNode *return_n = static_cast<const GDScriptParser::ReturnNode *>(s);
diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp
index 4edabdcb40..b5c8a6f478 100644
--- a/modules/gdscript/gdscript_disassembler.cpp
+++ b/modules/gdscript/gdscript_disassembler.cpp
@@ -128,7 +128,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += DADDR(3);
text += " = ";
text += DADDR(1);
- text += " <operator function> ";
+ text += " ";
+ text += operator_names[_code_ptr[ip + 4]];
+ text += " ";
text += DADDR(2);
incr += 5;
@@ -230,7 +232,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += "set_named validated ";
text += DADDR(1);
text += "[\"";
- text += "<unknown name>";
+ text += setter_names[_code_ptr[ip + 3]];
text += "\"] = ";
text += DADDR(2);
@@ -253,7 +255,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += " = ";
text += DADDR(1);
text += "[\"";
- text += "<unknown name>";
+ text += getter_names[_code_ptr[ip + 3]];
text += "\"]";
incr += 4;
@@ -398,7 +400,8 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += DADDR(1 + argc);
text += " = ";
- text += "<unknown type>(";
+ text += constructors_names[_code_ptr[ip + 3 + argc]];
+ text += "(";
for (int i = 0; i < argc; i++) {
if (i > 0) {
text += ", ";
@@ -687,7 +690,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
text += DADDR(2 + argc) + " = ";
text += DADDR(1) + ".";
- text += "<unknown method>";
+ text += builtin_methods_names[_code_ptr[ip + 4 + argc]];
text += "(";
@@ -725,12 +728,12 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
case OPCODE_CALL_UTILITY_VALIDATED: {
int instr_var_args = _code_ptr[++ip];
- text += "call-utility ";
+ text += "call-utility validated ";
int argc = _code_ptr[ip + 1 + instr_var_args];
text += DADDR(1 + argc) + " = ";
- text += "<unknown function>";
+ text += utilities_names[_code_ptr[ip + 3 + argc]];
text += "(";
for (int i = 0; i < argc; i++) {
@@ -746,12 +749,12 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
case OPCODE_CALL_GDSCRIPT_UTILITY: {
int instr_var_args = _code_ptr[++ip];
- text += "call-gscript-utility ";
+ text += "call-gdscript-utility ";
int argc = _code_ptr[ip + 1 + instr_var_args];
text += DADDR(1 + argc) + " = ";
- text += "<unknown function>";
+ text += gds_utilities_names[_code_ptr[ip + 3 + argc]];
text += "(";
for (int i = 0; i < argc; i++) {
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 3fc0924b4c..f88ac581ca 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -782,6 +782,7 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a
}
} else if (p_annotation->name == SNAME("@export_node_path")) {
ScriptLanguage::CodeCompletionOption node("Node", ScriptLanguage::CODE_COMPLETION_KIND_CLASS);
+ node.insert_text = node.display.quote(p_quote_style);
r_result.insert(node.display, node);
List<StringName> node_types;
ClassDB::get_inheriters_from_class("Node", &node_types);
@@ -790,11 +791,13 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a
continue;
}
ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS);
+ option.insert_text = option.display.quote(p_quote_style);
r_result.insert(option.display, option);
}
} else if (p_annotation->name == SNAME("@warning_ignore")) {
for (int warning_code = 0; warning_code < GDScriptWarning::WARNING_MAX; warning_code++) {
ScriptLanguage::CodeCompletionOption warning(GDScriptWarning::get_name_from_code((GDScriptWarning::Code)warning_code).to_lower(), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
+ warning.insert_text = warning.display.quote(p_quote_style);
r_result.insert(warning.display, warning);
}
}
diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h
index 76214f3482..37416a734d 100644
--- a/modules/gdscript/gdscript_function.h
+++ b/modules/gdscript/gdscript_function.h
@@ -505,6 +505,16 @@ private:
Vector<Variant> default_arg_values;
#endif
+#ifdef DEBUG_ENABLED
+ Vector<String> operator_names;
+ Vector<String> setter_names;
+ Vector<String> getter_names;
+ Vector<String> builtin_methods_names;
+ Vector<String> constructors_names;
+ Vector<String> utilities_names;
+ Vector<String> gds_utilities_names;
+#endif
+
List<StackDebug> stack_debug;
Variant _get_default_variant_for_data_type(const GDScriptDataType &p_data_type);
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index f5d3306376..1a744d59ba 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -41,6 +41,7 @@
#include "core/os/os.h"
#include "core/string/string_builder.h"
#include "gdscript_warning.h"
+#include "servers/text_server.h"
#endif // DEBUG_ENABLED
#ifdef TOOLS_ENABLED
@@ -186,24 +187,6 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) {
}
#ifdef DEBUG_ENABLED
-void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1, const String &p_symbol2, const String &p_symbol3, const String &p_symbol4) {
- ERR_FAIL_COND(p_source == nullptr);
- Vector<String> symbols;
- if (!p_symbol1.is_empty()) {
- symbols.push_back(p_symbol1);
- }
- if (!p_symbol2.is_empty()) {
- symbols.push_back(p_symbol2);
- }
- if (!p_symbol3.is_empty()) {
- symbols.push_back(p_symbol3);
- }
- if (!p_symbol4.is_empty()) {
- symbols.push_back(p_symbol4);
- }
- push_warning(p_source, p_code, symbols);
-}
-
void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) {
ERR_FAIL_COND(p_source == nullptr);
if (is_ignoring_warnings) {
@@ -546,7 +529,7 @@ void GDScriptParser::parse_program() {
AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
if (annotation != nullptr) {
if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
- annotation->apply(this, head);
+ head->annotations.push_back(annotation);
} else {
annotation_stack.push_back(annotation);
// This annotation must appear after script-level annotations
@@ -788,7 +771,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
return;
}
- // Apply annotations.
for (AnnotationNode *&annotation : annotations) {
member->annotations.push_back(annotation);
}
@@ -865,7 +847,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.)");
}
- annotation->apply(this, head);
+ head->annotations.push_back(annotation);
} else {
annotation_stack.push_back(annotation);
}
@@ -1451,7 +1433,11 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
annotation->info = &valid_annotations[annotation->name];
if (!annotation->applies_to(p_valid_targets)) {
- push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name));
+ if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
+ push_error(vformat(R"(Annotation "%s" must be at the top of the script, before "extends" and "class_name".)", annotation->name));
+ } else {
+ push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name));
+ }
valid = false;
}
@@ -1483,7 +1469,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional.
if (valid) {
- valid = validate_annotation_arguments(annotation);
+ valid = validate_annotation_argument_count(annotation);
}
return valid ? annotation : nullptr;
@@ -1714,6 +1700,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
case Node::CALL:
case Node::ASSIGNMENT:
case Node::AWAIT:
+ case Node::TERNARY_OPERATOR:
// Fine.
break;
case Node::LAMBDA:
@@ -1729,7 +1716,6 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
}
}
- // Apply annotations to statement.
while (!is_annotation && result != nullptr && !annotation_stack.is_empty()) {
AnnotationNode *last_annotation = annotation_stack.back()->get();
if (last_annotation->applies_to(AnnotationInfo::STATEMENT)) {
@@ -1806,11 +1792,10 @@ GDScriptParser::BreakNode *GDScriptParser::parse_break() {
GDScriptParser::ContinueNode *GDScriptParser::parse_continue() {
if (!can_continue) {
- push_error(R"(Cannot use "continue" outside of a loop or pattern matching block.)");
+ push_error(R"(Cannot use "continue" outside of a loop.)");
}
current_suite->has_continue = true;
ContinueNode *cont = alloc_node<ContinueNode>();
- cont->is_for_match = is_continue_match;
complete_extents(cont);
end_statement(R"("continue")");
return cont;
@@ -1836,12 +1821,10 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
// Save break/continue state.
bool could_break = can_break;
bool could_continue = can_continue;
- bool was_continue_match = is_continue_match;
// Allow break/continue.
can_break = true;
can_continue = true;
- is_continue_match = false;
SuiteNode *suite = alloc_node<SuiteNode>();
if (n_for->variable) {
@@ -1859,7 +1842,6 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() {
// Reset break/continue state.
can_break = could_break;
can_continue = could_continue;
- is_continue_match = was_continue_match;
return n_for;
}
@@ -1996,13 +1978,6 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
return nullptr;
}
- // Save continue state.
- bool could_continue = can_continue;
- bool was_continue_match = is_continue_match;
- // Allow continue for match.
- can_continue = true;
- is_continue_match = true;
-
SuiteNode *suite = alloc_node<SuiteNode>();
if (branch->patterns.size() > 0) {
for (const KeyValue<StringName, IdentifierNode *> &E : branch->patterns[0]->binds) {
@@ -2015,10 +1990,6 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() {
branch->block = parse_suite("match pattern block", suite);
complete_extents(branch);
- // Restore continue state.
- can_continue = could_continue;
- is_continue_match = was_continue_match;
-
return branch;
}
@@ -2177,12 +2148,10 @@ GDScriptParser::WhileNode *GDScriptParser::parse_while() {
// Save break/continue state.
bool could_break = can_break;
bool could_continue = can_continue;
- bool was_continue_match = is_continue_match;
// Allow break/continue.
can_break = true;
can_continue = true;
- is_continue_match = false;
n_while->loop = parse_suite(R"("while" block)");
n_while->loop->is_loop = true;
@@ -2191,7 +2160,6 @@ GDScriptParser::WhileNode *GDScriptParser::parse_while() {
// Reset break/continue state.
can_break = could_break;
can_continue = could_continue;
- is_continue_match = was_continue_match;
return n_while;
}
@@ -2251,7 +2219,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_expression(bool p_can_assi
}
GDScriptParser::IdentifierNode *GDScriptParser::parse_identifier() {
- return static_cast<IdentifierNode *>(parse_identifier(nullptr, false));
+ IdentifierNode *identifier = static_cast<IdentifierNode *>(parse_identifier(nullptr, false));
+#ifdef DEBUG_ENABLED
+ // Check for spoofing here (if available in TextServer) since this isn't called inside expressions. This is only relevant for declarations.
+ if (identifier && TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY) && TS->spoof_check(identifier->name.operator String())) {
+ push_warning(identifier, GDScriptWarning::CONFUSABLE_IDENTIFIER, identifier->name.operator String());
+ }
+#endif
+ return identifier;
}
GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode *p_previous_operand, bool p_can_assign) {
@@ -3621,7 +3596,7 @@ bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const {
return (info->target_kind & p_target_kinds) > 0;
}
-bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) {
+bool GDScriptParser::validate_annotation_argument_count(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;
@@ -3636,62 +3611,6 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation)
return false;
}
- const List<PropertyInfo>::Element *E = info.arguments.front();
- for (int i = 0; i < p_annotation->arguments.size(); i++) {
- ExpressionNode *argument = p_annotation->arguments[i];
- const PropertyInfo &parameter = E->get();
-
- if (E->next() != nullptr) {
- E = E->next();
- }
-
- switch (parameter.type) {
- case Variant::STRING:
- case Variant::STRING_NAME:
- case Variant::NODE_PATH:
- // Allow "quote-less strings", as long as they are recognized as identifiers.
- if (argument->type == Node::IDENTIFIER) {
- IdentifierNode *string = static_cast<IdentifierNode *>(argument);
- Callable::CallError error;
- Vector<Variant> args = varray(string->name);
- const Variant *name = args.ptr();
- Variant r;
- Variant::construct(parameter.type, r, &(name), 1, error);
- p_annotation->resolved_arguments.push_back(r);
- if (error.error != Callable::CallError::CALL_OK) {
- push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
- p_annotation->resolved_arguments.remove_at(p_annotation->resolved_arguments.size() - 1);
- return false;
- }
- break;
- }
- [[fallthrough]];
- default: {
- if (argument->type != Node::LITERAL) {
- push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
- return false;
- }
-
- Variant value = static_cast<LiteralNode *>(argument)->value;
- if (!Variant::can_convert_strict(value.get_type(), parameter.type)) {
- push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
- return false;
- }
- Callable::CallError error;
- const Variant *args = &value;
- Variant r;
- Variant::construct(parameter.type, r, &(args), 1, error);
- p_annotation->resolved_arguments.push_back(r);
- if (error.error != Callable::CallError::CALL_OK) {
- push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
- p_annotation->resolved_arguments.remove_at(p_annotation->resolved_arguments.size() - 1);
- return false;
- }
- break;
- }
- }
- }
-
return true;
}
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 0903f62061..74e12d0b5e 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -758,7 +758,6 @@ public:
};
struct ContinueNode : public Node {
- bool is_for_match = false;
ContinueNode() {
type = CONTINUE;
}
@@ -1254,7 +1253,6 @@ private:
bool panic_mode = false;
bool can_break = false;
bool can_continue = false;
- bool is_continue_match = false; // Whether a `continue` will act on a `match`.
List<bool> multiline_stack;
ClassNode *head = nullptr;
@@ -1361,8 +1359,11 @@ private:
void clear();
void push_error(const String &p_message, const Node *p_origin = nullptr);
#ifdef DEBUG_ENABLED
- void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1 = String(), const String &p_symbol2 = String(), const String &p_symbol3 = String(), const String &p_symbol4 = String());
void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols);
+ template <typename... Symbols>
+ void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Symbols &...p_symbols) {
+ push_warning(p_source, p_code, Vector<String>{ p_symbols... });
+ }
#endif
void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = false);
@@ -1400,7 +1401,7 @@ private:
// Annotations
AnnotationNode *parse_annotation(uint32_t p_valid_targets);
bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false);
- bool validate_annotation_arguments(AnnotationNode *p_annotation);
+ bool validate_annotation_argument_count(AnnotationNode *p_annotation);
void clear_unused_annotations();
bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target);
bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target);
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index e17a804003..d7f1114fd3 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -31,10 +31,14 @@
#include "gdscript_tokenizer.h"
#include "core/error/error_macros.h"
+#include "core/string/char_utils.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_settings.h"
#endif
+#ifdef DEBUG_ENABLED
+#include "servers/text_server.h"
+#endif
static const char *token_names[] = {
"Empty", // EMPTY,
@@ -435,10 +439,12 @@ GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(char32_t p_test, To
}
GDScriptTokenizer::Token GDScriptTokenizer::annotation() {
- if (!is_ascii_identifier_char(_peek())) {
+ if (is_unicode_identifier_start(_peek())) {
+ _advance(); // Consume start character.
+ } else {
push_error("Expected annotation identifier after \"@\".");
}
- while (is_ascii_identifier_char(_peek())) {
+ while (is_unicode_identifier_continue(_peek())) {
// Consume all identifier characters.
_advance();
}
@@ -447,7 +453,6 @@ GDScriptTokenizer::Token GDScriptTokenizer::annotation() {
return annotation;
}
-GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
#define KEYWORDS(KEYWORD_GROUP, KEYWORD) \
KEYWORD_GROUP('a') \
KEYWORD("as", Token::AS) \
@@ -512,8 +517,21 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
#define MIN_KEYWORD_LENGTH 2
#define MAX_KEYWORD_LENGTH 10
- // Consume all alphanumeric characters.
- while (is_ascii_identifier_char(_peek())) {
+#ifdef DEBUG_ENABLED
+void GDScriptTokenizer::make_keyword_list() {
+#define KEYWORD_LINE(keyword, token_type) keyword,
+#define KEYWORD_GROUP_IGNORE(group)
+ keyword_list = {
+ KEYWORDS(KEYWORD_GROUP_IGNORE, KEYWORD_LINE)
+ };
+#undef KEYWORD_LINE
+#undef KEYWORD_GROUP_IGNORE
+}
+#endif // DEBUG_ENABLED
+
+GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
+ // Consume all identifier characters.
+ while (is_unicode_identifier_continue(_peek())) {
_advance();
}
@@ -565,15 +583,28 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
}
// Not a keyword, so must be an identifier.
- return make_identifier(name);
+ Token id = make_identifier(name);
+
+#ifdef DEBUG_ENABLED
+ // Additional checks for identifiers but only in debug and if it's available in TextServer.
+ if (TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY)) {
+ int64_t confusable = TS->is_confusable(name, keyword_list);
+ if (confusable >= 0) {
+ push_error(vformat(R"(Identifier "%s" is visually similar to the GDScript keyword "%s" and thus not allowed.)", name, keyword_list[confusable]));
+ }
+ }
+#endif // DEBUG_ENABLED
+
+ return id;
-#undef KEYWORDS
-#undef MIN_KEYWORD_LENGTH
-#undef MAX_KEYWORD_LENGTH
#undef KEYWORD_GROUP_CASE
#undef KEYWORD
}
+#undef MAX_KEYWORD_LENGTH
+#undef MIN_KEYWORD_LENGTH
+#undef KEYWORDS
+
void GDScriptTokenizer::newline(bool p_make_token) {
// Don't overwrite previous newline, nor create if we want a line continuation.
if (p_make_token && !pending_newline && !line_continuation) {
@@ -720,7 +751,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
error.rightmost_column = column + 1;
push_error(error);
has_error = true;
- } else if (is_ascii_identifier_char(_peek())) {
+ } else if (is_unicode_identifier_start(_peek()) || is_unicode_identifier_continue(_peek())) {
// Letter at the end of the number.
push_error("Invalid numeric notation.");
}
@@ -1311,7 +1342,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() {
if (is_digit(c)) {
return number();
- } else if (is_ascii_identifier_char(c)) {
+ } else if (is_unicode_identifier_start(c)) {
return potential_identifier();
}
@@ -1504,7 +1535,11 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() {
}
default:
- return make_error(vformat(R"(Unknown character "%s".)", String(&c, 1)));
+ if (is_whitespace(c)) {
+ return make_error(vformat(R"(Invalid white space character "\\u%X".)", static_cast<int32_t>(c)));
+ } else {
+ return make_error(vformat(R"(Unknown character "%s".)", String(&c, 1)));
+ }
}
}
@@ -1514,4 +1549,7 @@ GDScriptTokenizer::GDScriptTokenizer() {
tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size");
}
#endif // TOOLS_ENABLED
+#ifdef DEBUG_ENABLED
+ make_keyword_list();
+#endif // DEBUG_ENABLED
}
diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h
index 9588922122..608840d3f1 100644
--- a/modules/gdscript/gdscript_tokenizer.h
+++ b/modules/gdscript/gdscript_tokenizer.h
@@ -224,6 +224,9 @@ private:
char32_t indent_char = '\0';
int position = 0;
int length = 0;
+#ifdef DEBUG_ENABLED
+ Vector<String> keyword_list;
+#endif // DEBUG_ENABLED
#ifdef TOOLS_ENABLED
HashMap<int, CommentData> comments;
@@ -239,6 +242,10 @@ private:
void _skip_whitespace();
void check_indent();
+#ifdef DEBUG_ENABLED
+ void make_keyword_list();
+#endif // DEBUG_ENABLED
+
Token make_error(const String &p_message);
void push_error(const String &p_message);
void push_error(const Token &p_error);
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index 184cecb316..a6cbb7f6ae 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -155,6 +155,10 @@ String GDScriptWarning::get_message() const {
CHECK_SYMBOLS(2);
return vformat(R"(The function '%s()' is a static function but was called from an instance. Instead, it should be directly called from the type: '%s.%s()'.)", symbols[0], symbols[1], symbols[0]);
}
+ case CONFUSABLE_IDENTIFIER: {
+ CHECK_SYMBOLS(1);
+ return vformat(R"(The identifier "%s" has misleading characters and might be confused with something else.)", symbols[0]);
+ }
case WARNING_MAX:
break; // Can't happen, but silences warning
}
@@ -219,6 +223,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"SHADOWED_GLOBAL_IDENTIFIER",
"INT_ASSIGNED_TO_ENUM",
"STATIC_CALLED_ON_INSTANCE",
+ "CONFUSABLE_IDENTIFIER",
};
static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index e3aee45f33..b485f02b9c 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -78,6 +78,7 @@ public:
SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable.
INT_ASSIGNED_TO_ENUM, // An integer value was assigned to an enum-typed variable without casting.
STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself.
+ CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e").
WARNING_MAX,
};
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.gd b/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.gd
new file mode 100644
index 0000000000..75524c32ae
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.gd
@@ -0,0 +1,6 @@
+var num := 1
+
+@export_range(num, 10) var a
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.out b/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.out
new file mode 100644
index 0000000000..b4f0e79237
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Argument 1 of annotation "@export_range" isn't a constant expression.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.gd b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.gd
new file mode 100644
index 0000000000..6014ee831c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.gd
@@ -0,0 +1,3 @@
+func test():
+ var untyped = 1
+ var inferred := untyped
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.out b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.out
new file mode 100644
index 0000000000..b6dc6d0b01
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot infer the type of "inferred" variable because the value doesn't have a set type.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.gd b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.gd
new file mode 100644
index 0000000000..040aa2e82a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.gd
@@ -0,0 +1,5 @@
+var untyped = 1
+var inferred := untyped
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.out b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.out
new file mode 100644
index 0000000000..b6dc6d0b01
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot infer the type of "inferred" variable because the value doesn't have a set type.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.gd b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.gd
new file mode 100644
index 0000000000..80c676488e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.gd
@@ -0,0 +1,5 @@
+func check(untyped = 1, inferred := untyped):
+ pass
+
+func test():
+ check()
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.out b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.out
new file mode 100644
index 0000000000..8c9f0c13ae
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot infer the type of "inferred" parameter because the value doesn't have a set type.
diff --git a/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.gd b/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.gd
new file mode 100644
index 0000000000..272dce8bbe
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.gd
@@ -0,0 +1,10 @@
+const BEFORE = 1
+
+@export_range(-10, 10) var a = 0
+@export_range(1 + 2, absi(-10) + 1) var b = 5
+@export_range(BEFORE + 1, BEFORE + AFTER + 1) var c = 5
+
+const AFTER = 10
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.out b/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd
index 2d2c2bef19..595563541f 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd
@@ -1,9 +1,4 @@
func test():
- var one_0 = 0
- one_0 = 1
- var one_1 := one_0
- print(one_1)
-
var two: Variant = 0
two += 2
print(two)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out
index 7536c38490..0ddfa4b75f 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out
+++ b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out
@@ -1,5 +1,4 @@
GDTEST_OK
-1
2
3
4
diff --git a/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd b/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd
index 877a4ea221..4c02fd4b0d 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd
@@ -1,12 +1,12 @@
-@warning_ignore(unused_private_class_variable)
+@warning_ignore("unused_private_class_variable")
var _unused = 2
-@warning_ignore(unused_variable)
+@warning_ignore("unused_variable")
func test():
print("test")
var unused = 3
- @warning_ignore(redundant_await)
+ @warning_ignore("redundant_await")
print(await regular_func())
print("done")
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.out b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.out
index b018091c18..32e230fc80 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.out
@@ -2,4 +2,4 @@ GDTEST_OK
>> WARNING
>> Line: 2
>> UNUSED_PARAMETER
->>
+>> The parameter 'unused' is never used in the function ''. If this is intended, prefix it with an underscore: '_unused'
diff --git a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out
index a598ff8424..5fcf1cbcad 100644
--- a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out
+++ b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out
@@ -1,2 +1,2 @@
GDTEST_PARSER_ERROR
-Annotation "@icon" is not allowed in this level.
+Annotation "@icon" must be at the top of the script, before "extends" and "class_name".
diff --git a/modules/gdscript/tests/scripts/parser/errors/identifier_similar_to_keyword.gd b/modules/gdscript/tests/scripts/parser/errors/identifier_similar_to_keyword.gd
new file mode 100644
index 0000000000..4b1f284070
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/identifier_similar_to_keyword.gd
@@ -0,0 +1,3 @@
+func test():
+ var аs # Using Cyrillic "а".
+ print(аs)
diff --git a/modules/gdscript/tests/scripts/parser/errors/identifier_similar_to_keyword.out b/modules/gdscript/tests/scripts/parser/errors/identifier_similar_to_keyword.out
new file mode 100644
index 0000000000..337dec2f4d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/identifier_similar_to_keyword.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Identifier "аs" is visually similar to the GDScript keyword "as" and thus not allowed.
diff --git a/modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd b/modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd
index cc78309ae4..a34cc26e67 100644
--- a/modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd
+++ b/modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd
@@ -1,6 +1,6 @@
# https://github.com/godotengine/godot/issues/50285
-@warning_ignore(unused_local_constant)
+@warning_ignore("unused_local_constant")
func test():
const CONST_INNER_DICTIONARY = { "key": true }
const CONST_NESTED_DICTIONARY_OLD_WORKAROUND = {
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
index 1e072728fc..acf9ff2e21 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
@@ -5,7 +5,7 @@
@export var color: Color
@export_color_no_alpha var color_no_alpha: Color
-@export_node_path(Sprite2D, Sprite3D, Control, Node) var nodepath := ^"hello"
+@export_node_path("Sprite2D", "Sprite3D", "Control", "Node") var nodepath := ^"hello"
func test():
diff --git a/modules/gdscript/tests/scripts/parser/features/match.gd b/modules/gdscript/tests/scripts/parser/features/match.gd
index 4d05490aa5..59b5ba2426 100644
--- a/modules/gdscript/tests/scripts/parser/features/match.gd
+++ b/modules/gdscript/tests/scripts/parser/features/match.gd
@@ -3,8 +3,6 @@ func test():
match i:
"Hello":
print("hello")
- # This will fall through to the default case below.
- continue
"Good bye":
print("bye")
_:
diff --git a/modules/gdscript/tests/scripts/parser/features/match.out b/modules/gdscript/tests/scripts/parser/features/match.out
index 732885c7a2..a2cb94399c 100644
--- a/modules/gdscript/tests/scripts/parser/features/match.out
+++ b/modules/gdscript/tests/scripts/parser/features/match.out
@@ -1,4 +1,3 @@
GDTEST_OK
hello
-default
This will match
diff --git a/modules/gdscript/tests/scripts/parser/features/nested_match.gd b/modules/gdscript/tests/scripts/parser/features/nested_match.gd
index aaddcc7e83..491d917a8e 100644
--- a/modules/gdscript/tests/scripts/parser/features/nested_match.gd
+++ b/modules/gdscript/tests/scripts/parser/features/nested_match.gd
@@ -8,11 +8,10 @@ func test():
1234:
print("2")
match number:
- 1234:
- print("3")
- continue
+ 4321:
+ print("Should not be printed")
_:
- print("Should also be printed")
+ print("3")
match number:
1234:
print("4")
diff --git a/modules/gdscript/tests/scripts/parser/features/nested_match.out b/modules/gdscript/tests/scripts/parser/features/nested_match.out
index 651d76cc59..c2d2e29a06 100644
--- a/modules/gdscript/tests/scripts/parser/features/nested_match.out
+++ b/modules/gdscript/tests/scripts/parser/features/nested_match.out
@@ -2,7 +2,6 @@ GDTEST_OK
1
2
3
-Should also be printed
4
5
6
diff --git a/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd
new file mode 100644
index 0000000000..523959a016
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd
@@ -0,0 +1,35 @@
+const π = PI
+var ㄥ = π
+
+func test():
+ var փորձարկում = "test"
+ prints("փորձարկում", փորձարկում)
+ var امتحان = "test"
+ prints("امتحان", امتحان)
+ var পরীক্ষা = "test"
+ prints("পরীক্ষা", পরীক্ষা)
+ var тест = "test"
+ prints("тест", тест)
+ var जाँच = "test"
+ prints("जाँच", जाँच)
+ var 기준 = "test"
+ prints("기준", 기준)
+ var 测试 = "test"
+ prints("测试", 测试)
+ var テスト = "test"
+ prints("テスト", テスト)
+ var 試験 = "test"
+ prints("試験", 試験)
+ var പരീക്ഷ = "test"
+ prints("പരീക്ഷ", പരീക്ഷ)
+ var ทดสอบ = "test"
+ prints("ทดสอบ", ทดสอบ)
+ var δοκιμή = "test"
+ prints("δοκιμή", δοκιμή)
+
+ const d = 1.1
+ _process(d)
+ print(is_equal_approx(ㄥ, PI + (d * PI)))
+
+func _process(Δ: float) -> void:
+ ㄥ += Δ * π
diff --git a/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.out b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.out
new file mode 100644
index 0000000000..c071380a8f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.out
@@ -0,0 +1,14 @@
+GDTEST_OK
+փորձարկում test
+امتحان test
+পরীক্ষা test
+тест test
+जाँच test
+기준 test
+测试 test
+テスト test
+試験 test
+പരീക്ഷ test
+ทดสอบ test
+δοκιμή test
+true
diff --git a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd
new file mode 100644
index 0000000000..e2caac8ffd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd
@@ -0,0 +1,5 @@
+func test():
+ var port = 0 # Only latin characters.
+ var pοrt = 1 # The "ο" is Greek omicron.
+
+ prints(port, pοrt)
diff --git a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out
new file mode 100644
index 0000000000..c483396443
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out
@@ -0,0 +1,6 @@
+GDTEST_OK
+>> WARNING
+>> Line: 3
+>> CONFUSABLE_IDENTIFIER
+>> The identifier "pοrt" has misleading characters and might be confused with something else.
+0 1
diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.gd b/modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.gd
deleted file mode 100644
index 3e71cd0518..0000000000
--- a/modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.gd
+++ /dev/null
@@ -1,4 +0,0 @@
-const array: Array = [0]
-
-func test():
- array.push_back(0)
diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.out b/modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.out
deleted file mode 100644
index ba3e1c46c6..0000000000
--- a/modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.out
+++ /dev/null
@@ -1,7 +0,0 @@
-GDTEST_RUNTIME_ERROR
->> ERROR
->> on function: push_back()
->> core/variant/array.cpp
->> 253
->> Condition "_p->read_only" is true.
->> Array is in read-only state.
diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.gd b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.gd
deleted file mode 100644
index 7b350e81ad..0000000000
--- a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.gd
+++ /dev/null
@@ -1,4 +0,0 @@
-const dictionary := {}
-
-func test():
- dictionary.erase(0)
diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.out b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.out
deleted file mode 100644
index 3e7ca11a4f..0000000000
--- a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.out
+++ /dev/null
@@ -1,7 +0,0 @@
-GDTEST_RUNTIME_ERROR
->> ERROR
->> on function: erase()
->> core/variant/dictionary.cpp
->> 177
->> Condition "_p->read_only" is true. Returning: false
->> Dictionary is in read-only state.
diff --git a/modules/gdscript/tests/scripts/runtime/features/await_on_void.gd b/modules/gdscript/tests/scripts/runtime/features/await_on_void.gd
index 46b9fbc951..1490a164c9 100644
--- a/modules/gdscript/tests/scripts/runtime/features/await_on_void.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/await_on_void.gd
@@ -2,6 +2,6 @@ func wait() -> void:
pass
func test():
- @warning_ignore(redundant_await)
+ @warning_ignore("redundant_await")
await wait()
print("end")
diff --git a/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd b/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd
new file mode 100644
index 0000000000..d1746979be
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd
@@ -0,0 +1,10 @@
+const array: Array = [0]
+const dictionary := {1: 2}
+
+@warning_ignore("assert_always_true")
+func test():
+ assert(array.is_read_only() == true)
+ assert(str(array) == '[0]')
+ assert(dictionary.is_read_only() == true)
+ assert(str(dictionary) == '{ 1: 2 }')
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.out b/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd b/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd
index 1d4b400d81..48af734317 100644
--- a/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd
@@ -7,11 +7,11 @@ func test():
func builtin_method():
var pba := PackedByteArray()
- @warning_ignore(return_value_discarded)
+ @warning_ignore("return_value_discarded")
pba.resize(1) # Built-in validated.
func builtin_method_static():
var _pba := PackedByteArray()
- @warning_ignore(return_value_discarded)
+ @warning_ignore("return_value_discarded")
Vector2.from_angle(PI) # Static built-in validated.
diff --git a/modules/gdscript/tests/scripts/runtime/features/gdscript.gd b/modules/gdscript/tests/scripts/runtime/features/gdscript.gd
index f2368643de..e686cffc48 100644
--- a/modules/gdscript/tests/scripts/runtime/features/gdscript.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/gdscript.gd
@@ -11,10 +11,10 @@ class InnerClass:
func _init() -> void:
prints("Inner")
'''
- @warning_ignore(return_value_discarded)
+ @warning_ignore("return_value_discarded")
gdscr.reload()
var inst = gdscr.new()
- @warning_ignore(unsafe_method_access)
+ @warning_ignore("unsafe_method_access")
inst.test()
diff --git a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd
index cc34e71b01..2f55059334 100644
--- a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd
@@ -20,26 +20,26 @@ func test_utility(v, f):
assert(not f) # Test unary operator reading from `nil`.
func test_builtin_call(v, f):
- @warning_ignore(unsafe_method_access)
+ @warning_ignore("unsafe_method_access")
v.angle() # Built-in method call.
assert(not f) # Test unary operator reading from `nil`.
func test_builtin_call_validated(v: Vector2, f):
- @warning_ignore(return_value_discarded)
+ @warning_ignore("return_value_discarded")
v.abs() # Built-in method call validated.
assert(not f) # Test unary operator reading from `nil`.
func test_object_call(v, f):
- @warning_ignore(unsafe_method_access)
+ @warning_ignore("unsafe_method_access")
v.get_reference_count() # Native type method call.
assert(not f) # Test unary operator reading from `nil`.
func test_object_call_method_bind(v: Resource, f):
- @warning_ignore(return_value_discarded)
+ @warning_ignore("return_value_discarded")
v.duplicate() # Native type method call with MethodBind.
assert(not f) # Test unary operator reading from `nil`.
func test_object_call_ptrcall(v: RefCounted, f):
- @warning_ignore(return_value_discarded)
+ @warning_ignore("return_value_discarded")
v.get_reference_count() # Native type method call with ptrcall.
assert(not f) # Test unary operator reading from `nil`.
diff --git a/modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.gd b/modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.gd
index af3f3cb941..efa8270526 100644
--- a/modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.gd
@@ -1,7 +1,7 @@
# https://github.com/godotengine/godot/issues/71172
func test():
- @warning_ignore(narrowing_conversion)
+ @warning_ignore("narrowing_conversion")
var foo: int = 0.0
print(typeof(foo) == TYPE_INT)
var dict : Dictionary = {"a":0.0}