diff options
Diffstat (limited to 'modules/gdscript')
20 files changed, 245 insertions, 63 deletions
diff --git a/modules/gdscript/editor/script_templates/RichTextEffect/default.gd b/modules/gdscript/editor/script_templates/RichTextEffect/default.gd new file mode 100644 index 0000000000..c79eeb91ec --- /dev/null +++ b/modules/gdscript/editor/script_templates/RichTextEffect/default.gd @@ -0,0 +1,17 @@ +# meta-description: Base template for rich text effects + +@tool +class_name _CLASS_ +extends _BASE_ + + +# To use this effect: +# - Enable BBCode on a RichTextLabel. +# - Register this effect on the label. +# - Use [_CLASS_ param=2.0]hello[/_CLASS_] in text. +var bbcode := "_CLASS_" + + +func _process_custom_fx(char_fx: CharFXTransform) -> bool: + var param: float = char_fx.env.get("param", 1.0) + return true diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index a2cab25ce8..aa91f8fe8d 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -479,7 +479,24 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c } if (look_class->has_member(name)) { resolve_class_member(look_class, name, id); - base = look_class->get_member(name).get_datatype(); + GDScriptParser::ClassNode::Member member = look_class->get_member(name); + GDScriptParser::DataType member_datatype = member.get_datatype(); + + switch (member.type) { + case GDScriptParser::ClassNode::Member::CLASS: + break; // OK. + case GDScriptParser::ClassNode::Member::CONSTANT: + if (member_datatype.kind != GDScriptParser::DataType::SCRIPT && member_datatype.kind != GDScriptParser::DataType::CLASS) { + push_error(vformat(R"(Constant "%s" is not a preloaded script or class.)", name), id); + return ERR_PARSE_ERROR; + } + break; + default: + push_error(vformat(R"(Cannot use %s "%s" in extends chain.)", member.get_type_name(), name), id); + return ERR_PARSE_ERROR; + } + + base = member_datatype; found = true; break; } @@ -506,6 +523,9 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c if (!id_type.is_set()) { push_error(vformat(R"(Could not find nested type "%s".)", id->name), id); return ERR_PARSE_ERROR; + } else if (id_type.kind != GDScriptParser::DataType::SCRIPT && id_type.kind != GDScriptParser::DataType::CLASS) { + push_error(vformat(R"(Identifier "%s" is not a preloaded script or class.)", id->name), id); + return ERR_PARSE_ERROR; } base = id_type; @@ -4546,7 +4566,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo result.set_container_element_type(elem_type); } else if (p_property.type == Variant::INT) { // Check if it's enum. - if ((p_property.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) && p_property.class_name != StringName()) { + if ((p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && p_property.class_name != StringName()) { if (CoreConstants::is_global_enum(p_property.class_name)) { result = make_global_enum_type(p_property.class_name, StringName(), false); result.is_constant = false; @@ -4558,6 +4578,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo } } } + // PROPERTY_USAGE_CLASS_IS_BITFIELD: BitField[T] isn't supported (yet?), use plain int. } } return result; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index f3a86522ae..be33c7c591 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -54,6 +54,7 @@ void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const { p_delimiters->push_back("\" \""); p_delimiters->push_back("' '"); p_delimiters->push_back("\"\"\" \"\"\""); + p_delimiters->push_back("''' '''"); } bool GDScriptLanguage::is_using_templates() { @@ -73,9 +74,11 @@ Ref<Script> GDScriptLanguage::make_template(const String &p_template, const Stri .replace(": String", "") .replace(": Array[String]", "") .replace(": float", "") + .replace(": CharFXTransform", "") .replace(":=", "=") .replace(" -> String", "") .replace(" -> int", "") + .replace(" -> bool", "") .replace(" -> void", ""); } @@ -578,29 +581,34 @@ static int _get_enum_constant_location(StringName p_class, StringName p_enum_con // END LOCATION METHODS -static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg = true) { - if (p_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) { - String enum_name = p_info.class_name; - if (!enum_name.contains(".")) { - return enum_name; +static String _trim_parent_class(const String &p_class, const String &p_base_class) { + if (p_base_class.is_empty()) { + return p_class; + } + Vector<String> names = p_class.split(".", false, 1); + if (names.size() == 2) { + String first = names[0]; + String rest = names[1]; + if (ClassDB::class_exists(p_base_class) && ClassDB::class_exists(first) && ClassDB::is_parent_class(p_base_class, first)) { + return rest; } - return enum_name.get_slice(".", 1); } + return p_class; +} - String n = p_info.name; - int idx = n.find(":"); - if (idx != -1) { - return n.substr(idx + 1, n.length()); - } +static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg, const String &p_base_class = "") { + String class_name = p_info.class_name; + bool is_enum = p_info.type == Variant::INT && p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM; + // PROPERTY_USAGE_CLASS_IS_BITFIELD: BitField[T] isn't supported (yet?), use plain int. - if (p_info.type == Variant::OBJECT) { - if (p_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { - return p_info.hint_string; - } else { - return p_info.class_name.operator String(); + if ((p_info.type == Variant::OBJECT || is_enum) && !class_name.is_empty()) { + if (is_enum && CoreConstants::is_global_enum(p_info.class_name)) { + return class_name; } - } - if (p_info.type == Variant::NIL) { + return _trim_parent_class(class_name, p_base_class); + } else if (p_info.type == Variant::ARRAY && p_info.hint == PROPERTY_HINT_ARRAY_TYPE && !p_info.hint_string.is_empty()) { + return "Array[" + _trim_parent_class(p_info.hint_string, p_base_class) + "]"; + } else if (p_info.type == Variant::NIL) { if (p_is_arg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) { return "Variant"; } else { @@ -3001,26 +3009,14 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c arg = arg.substr(0, arg.find(":")); } method_hint += arg; - if (use_type_hint && mi.arguments[i].type != Variant::NIL) { - method_hint += ": "; - if (mi.arguments[i].type == Variant::OBJECT && mi.arguments[i].class_name != StringName()) { - method_hint += mi.arguments[i].class_name.operator String(); - } else { - method_hint += Variant::get_type_name(mi.arguments[i].type); - } + if (use_type_hint) { + method_hint += ": " + _get_visual_datatype(mi.arguments[i], true, class_name); } } } method_hint += ")"; - if (use_type_hint && (mi.return_val.type != Variant::NIL || !(mi.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) { - method_hint += " -> "; - if (mi.return_val.type == Variant::NIL) { - method_hint += "void"; - } else if (mi.return_val.type == Variant::OBJECT && mi.return_val.class_name != StringName()) { - method_hint += mi.return_val.class_name.operator String(); - } else { - method_hint += Variant::get_type_name(mi.return_val.type); - } + if (use_type_hint) { + method_hint += " -> " + _get_visual_datatype(mi.return_val, false, class_name); } method_hint += ":"; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index e2a37ab6e9..8a49398f1a 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -1343,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; } } @@ -1439,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); @@ -1475,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(); @@ -1817,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.)"); @@ -2820,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; @@ -3856,7 +3864,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; } @@ -3901,8 +3909,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; } diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index ef59a07f1a..0cb8e3a2af 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -183,7 +183,7 @@ String GDScriptWarning::get_message() const { return vformat(R"*(The default value is using "%s" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this.)*", symbols[0]); } case ONREADY_WITH_EXPORT: { - return R"(The "@onready" annotation will make the default value to be set after the "@export" takes effect and will override it.)"; + return R"("@onready" will set the default value after "@export" takes effect and will override it.)"; } case WARNING_MAX: break; // Can't happen, but silences warning diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 2ed444c7ad..07f9465516 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -337,7 +337,7 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN symbol.kind = lsp::SymbolKind::Variable; symbol.name = parameter->identifier->name; symbol.range.start.line = LINE_NUMBER_TO_INDEX(parameter->start_line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(parameter->start_line); + symbol.range.start.character = LINE_NUMBER_TO_INDEX(parameter->start_column); symbol.range.end.line = LINE_NUMBER_TO_INDEX(parameter->end_line); symbol.range.end.character = LINE_NUMBER_TO_INDEX(parameter->end_column); symbol.uri = uri; diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index acd75f039a..112db4df3a 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -46,7 +46,7 @@ Error GDScriptLanguageProtocol::LSPeer::handle_data() { while (true) { if (req_pos >= LSP_MAX_BUFFER_SIZE) { req_pos = 0; - ERR_FAIL_COND_V_MSG(true, ERR_OUT_OF_MEMORY, "Response header too big"); + ERR_FAIL_V_MSG(ERR_OUT_OF_MEMORY, "Response header too big"); } Error err = connection->get_partial_data(&req_buf[req_pos], 1, read); if (err != OK) { @@ -237,6 +237,7 @@ void GDScriptLanguageProtocol::poll() { HashMap<int, Ref<LSPeer>>::Iterator E = clients.begin(); while (E != clients.end()) { Ref<LSPeer> peer = E->value; + peer->connection->poll(); StreamPeerTCP::Status status = peer->connection->get_status(); if (status == StreamPeerTCP::STATUS_NONE || status == StreamPeerTCP::STATUS_ERROR) { on_client_disconnected(E->key); diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.gd b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.gd new file mode 100644 index 0000000000..72af099158 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.gd @@ -0,0 +1,9 @@ +# GH-75870 + +const A = 1 + +class B extends A: + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.out b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.out new file mode 100644 index 0000000000..65d629a35b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Constant "A" is not a preloaded script or class. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.gd b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.gd new file mode 100644 index 0000000000..fe334f8cb7 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.gd @@ -0,0 +1,12 @@ +# GH-75870 + +class A: + const X = 1 + +const Y = A.X # A.X is now resolved. + +class B extends A.X: + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.out b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.out new file mode 100644 index 0000000000..951cfb1ea4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Identifier "X" is not a preloaded script or class. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.gd b/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.gd new file mode 100644 index 0000000000..6574d4cf31 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.gd @@ -0,0 +1,9 @@ +# GH-75870 + +var A = 1 + +class B extends A: + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.out b/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.out new file mode 100644 index 0000000000..7b39af6979 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot use variable "A" in extends chain. diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out index ff184f9f04..f861d52f2b 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out @@ -2,5 +2,5 @@ GDTEST_OK >> WARNING >> Line: 3 >> ONREADY_WITH_EXPORT ->> The "@onready" annotation will make the default value to be set after the "@export" takes effect and will override it. +>> "@onready" will set the default value after "@export" takes effect and will override it. warn diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.gd b/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.gd new file mode 100644 index 0000000000..271a831732 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.gd @@ -0,0 +1,4 @@ +@export_enum("A",, "B", "C") var a + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.out b/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.out new file mode 100644 index 0000000000..70eee5b39f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression as the annotation argument. diff --git a/modules/gdscript/tests/scripts/parser/features/annotations.gd b/modules/gdscript/tests/scripts/parser/features/annotations.gd new file mode 100644 index 0000000000..13c89a0a09 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/annotations.gd @@ -0,0 +1,48 @@ +extends Node + +@export_enum("A", "B", "C") var a0 +@export_enum("A", "B", "C",) var a1 + +@export_enum( + "A", + "B", + "C" +) var a2 + +@export_enum( + "A", + "B", + "C", +) var a3 + +@export +var a4: int + +@export() +var a5: int + +@export() var a6: int +@warning_ignore("onready_with_export") @onready @export var a7: int +@warning_ignore("onready_with_export") @onready() @export() var a8: int + +@warning_ignore("onready_with_export") +@onready +@export +var a9: int + +@warning_ignore("onready_with_export") +@onready() +@export() +var a10: int + +@warning_ignore("onready_with_export") +@onready() +@export() + +var a11: int + + +func test(): + for property in get_property_list(): + if property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE: + print(property) diff --git a/modules/gdscript/tests/scripts/parser/features/annotations.out b/modules/gdscript/tests/scripts/parser/features/annotations.out new file mode 100644 index 0000000000..3af0436c53 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/annotations.out @@ -0,0 +1,13 @@ +GDTEST_OK +{ "name": "a0", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 } +{ "name": "a1", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 } +{ "name": "a2", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 } +{ "name": "a3", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 } +{ "name": "a4", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a5", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a6", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a7", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a8", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a9", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a10", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a11", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } diff --git a/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.gd b/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.gd new file mode 100644 index 0000000000..87f9479812 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.gd @@ -0,0 +1,36 @@ +var dict = {} + +func test(): + dict.if = 1 + dict.elif = 1 + dict.else = 1 + dict.for = 1 + dict.while = 1 + dict.match = 1 + dict.break = 1 + dict.continue = 1 + dict.pass = 1 + dict.return = 1 + dict.class = 1 + dict.class_name = 1 + dict.extends = 1 + dict.is = 1 + dict.in = 1 + dict.as = 1 + dict.self = 1 + dict.signal = 1 + dict.func = 1 + dict.static = 1 + dict.const = 1 + dict.enum = 1 + dict.var = 1 + dict.breakpoint = 1 + dict.preload = 1 + dict.await = 1 + dict.yield = 1 + dict.assert = 1 + dict.void = 1 + dict.PI = 1 + dict.TAU = 1 + dict.INF = 1 + dict.NAN = 1 diff --git a/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.out b/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.out @@ -0,0 +1 @@ +GDTEST_OK |
