diff options
Diffstat (limited to 'modules/gdscript')
32 files changed, 398 insertions, 133 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_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index d6f21d297a..1414075ba8 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -69,56 +69,52 @@ uint32_t GDScriptByteCodeGenerator::add_or_get_name(const StringName &p_name) { uint32_t GDScriptByteCodeGenerator::add_temporary(const GDScriptDataType &p_type) { Variant::Type temp_type = Variant::NIL; - if (p_type.has_type) { - if (p_type.kind == GDScriptDataType::BUILTIN) { - switch (p_type.builtin_type) { - case Variant::NIL: - case Variant::BOOL: - case Variant::INT: - case Variant::FLOAT: - case Variant::STRING: - case Variant::VECTOR2: - case Variant::VECTOR2I: - case Variant::RECT2: - case Variant::RECT2I: - case Variant::VECTOR3: - case Variant::VECTOR3I: - case Variant::TRANSFORM2D: - case Variant::VECTOR4: - case Variant::VECTOR4I: - case Variant::PLANE: - case Variant::QUATERNION: - case Variant::AABB: - case Variant::BASIS: - case Variant::TRANSFORM3D: - case Variant::PROJECTION: - case Variant::COLOR: - case Variant::STRING_NAME: - case Variant::NODE_PATH: - case Variant::RID: - case Variant::OBJECT: - case Variant::CALLABLE: - case Variant::SIGNAL: - case Variant::DICTIONARY: - case Variant::ARRAY: - temp_type = p_type.builtin_type; - break; - case Variant::PACKED_BYTE_ARRAY: - case Variant::PACKED_INT32_ARRAY: - case Variant::PACKED_INT64_ARRAY: - case Variant::PACKED_FLOAT32_ARRAY: - case Variant::PACKED_FLOAT64_ARRAY: - case Variant::PACKED_STRING_ARRAY: - case Variant::PACKED_VECTOR2_ARRAY: - case Variant::PACKED_VECTOR3_ARRAY: - case Variant::PACKED_COLOR_ARRAY: - case Variant::VARIANT_MAX: - // Packed arrays are reference counted, so we don't use the pool for them. - temp_type = Variant::NIL; - break; - } - } else { - temp_type = Variant::OBJECT; + if (p_type.has_type && p_type.kind == GDScriptDataType::BUILTIN) { + switch (p_type.builtin_type) { + case Variant::NIL: + case Variant::BOOL: + case Variant::INT: + case Variant::FLOAT: + case Variant::STRING: + case Variant::VECTOR2: + case Variant::VECTOR2I: + case Variant::RECT2: + case Variant::RECT2I: + case Variant::VECTOR3: + case Variant::VECTOR3I: + case Variant::TRANSFORM2D: + case Variant::VECTOR4: + case Variant::VECTOR4I: + case Variant::PLANE: + case Variant::QUATERNION: + case Variant::AABB: + case Variant::BASIS: + case Variant::TRANSFORM3D: + case Variant::PROJECTION: + case Variant::COLOR: + case Variant::STRING_NAME: + case Variant::NODE_PATH: + case Variant::RID: + case Variant::CALLABLE: + case Variant::SIGNAL: + temp_type = p_type.builtin_type; + break; + case Variant::OBJECT: + case Variant::DICTIONARY: + case Variant::ARRAY: + case Variant::PACKED_BYTE_ARRAY: + case Variant::PACKED_INT32_ARRAY: + case Variant::PACKED_INT64_ARRAY: + case Variant::PACKED_FLOAT32_ARRAY: + case Variant::PACKED_FLOAT64_ARRAY: + case Variant::PACKED_STRING_ARRAY: + case Variant::PACKED_VECTOR2_ARRAY: + case Variant::PACKED_VECTOR3_ARRAY: + case Variant::PACKED_COLOR_ARRAY: + case Variant::VARIANT_MAX: + // Arrays, dictionaries, and objects are reference counted, so we don't use the pool for them. + temp_type = Variant::NIL; + break; } } @@ -143,10 +139,12 @@ void GDScriptByteCodeGenerator::pop_temporary() { ERR_FAIL_COND(used_temporaries.is_empty()); int slot_idx = used_temporaries.back()->get(); const StackSlot &slot = temporaries[slot_idx]; - if (slot.type == Variant::OBJECT) { + if (slot.type == Variant::NIL) { // Avoid keeping in the stack long-lived references to objects, // which may prevent RefCounted objects from being freed. - write_assign_false(Address(Address::TEMPORARY, slot_idx)); + // However, the cleanup will be performed an the end of the + // statement, to allow object references to survive chaining. + temporaries_pending_clear.push_back(slot_idx); } temporaries_pool[slot.type].push_back(slot_idx); used_temporaries.pop_back(); @@ -1756,6 +1754,23 @@ void GDScriptByteCodeGenerator::end_block() { pop_stack_identifiers(); } +void GDScriptByteCodeGenerator::clean_temporaries() { + List<int>::Element *E = temporaries_pending_clear.front(); + while (E) { + // The temporary may have been re-used as something else than an object + // since it was added to the list. In that case, there's no need to clear it. + int slot_idx = E->get(); + const StackSlot &slot = temporaries[slot_idx]; + if (slot.type == Variant::NIL) { + write_assign_false(Address(Address::TEMPORARY, slot_idx)); + } + + List<int>::Element *next = E->next(); + E->erase(); + E = next; + } +} + GDScriptByteCodeGenerator::~GDScriptByteCodeGenerator() { if (!ended && function != nullptr) { memdelete(function); diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index dc05de9fc6..42c6f80455 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -88,6 +88,7 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { Vector<StackSlot> locals; Vector<StackSlot> temporaries; List<int> used_temporaries; + List<int> temporaries_pending_clear; RBMap<Variant::Type, List<int>> temporaries_pool; List<GDScriptFunction::StackDebug> stack_debug; @@ -463,6 +464,7 @@ public: virtual uint32_t add_or_get_name(const StringName &p_name) override; virtual uint32_t add_temporary(const GDScriptDataType &p_type) override; virtual void pop_temporary() override; + virtual void clean_temporaries() override; virtual void start_parameters() override; virtual void end_parameters() override; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index 7847ab28c7..e82b4b08ab 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -72,6 +72,7 @@ public: virtual uint32_t add_or_get_name(const StringName &p_name) = 0; virtual uint32_t add_temporary(const GDScriptDataType &p_type) = 0; virtual void pop_temporary() = 0; + virtual void clean_temporaries() = 0; virtual void start_parameters() = 0; virtual void end_parameters() = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index e27b977e9d..42619a12a8 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1655,6 +1655,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui Error err = OK; GDScriptCodeGenerator *gen = codegen.generator; + gen->clean_temporaries(); codegen.start_block(); if (p_add_locals) { @@ -1957,6 +1958,8 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui } } break; } + + gen->clean_temporaries(); } codegen.end_block(); 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 d1d50351d0..bcc116cda2 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -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); @@ -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; @@ -3678,6 +3686,12 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) } bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_node) { +#ifdef DEBUG_ENABLED + if (this->_is_tool) { + push_error(R"("@tool" annotation can only be used once.)", p_annotation); + return false; + } +#endif // DEBUG_ENABLED this->_is_tool = true; return true; } @@ -3686,6 +3700,16 @@ bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p ERR_FAIL_COND_V_MSG(p_node->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)"); ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false); ClassNode *p_class = static_cast<ClassNode *>(p_node); +#ifdef DEBUG_ENABLED + if (!p_class->icon_path.is_empty()) { + push_error(R"("@icon" annotation can only be used once.)", p_annotation); + return false; + } + if (String(p_annotation->resolved_arguments[0]).is_empty()) { + push_error(R"("@icon" annotation argument must contain the path to the icon.)", p_annotation->arguments[0]); + return false; + } +#endif // DEBUG_ENABLED p_class->icon_path = p_annotation->resolved_arguments[0]; return true; } @@ -4120,9 +4144,6 @@ String GDScriptParser::DataType::to_string() const { } return native_type.operator String(); case CLASS: - if (is_meta_type) { - return GDScript::get_class_static(); - } if (class_type->identifier != nullptr) { return class_type->identifier->name.operator String(); } diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index d586380c41..a45a73a8d5 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -1099,7 +1099,7 @@ void GDScriptTokenizer::check_indent() { _advance(); } - if (mixed) { + if (mixed && !(line_continuation || multiline_mode)) { Token error = make_error("Mixed use of tabs and spaces for indentation."); error.start_line = line; error.start_column = 1; diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 83d2ed6010..7098e4cd40 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -3572,8 +3572,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a //error // function, file, line, error, explanation String err_file; - if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && !p_instance->script->path.is_empty()) { - err_file = p_instance->script->path; + bool instance_valid_with_script = p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid(); + if (instance_valid_with_script && !get_script()->path.is_empty()) { + err_file = get_script()->path; } else if (script) { err_file = script->path; } @@ -3581,7 +3582,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a err_file = "<built-in>"; } String err_func = name; - if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && !p_instance->script->name.is_empty()) { + if (instance_valid_with_script && !p_instance->script->name.is_empty()) { err_func = p_instance->script->name + "." + err_func; } int err_line = line; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 2ed444c7ad..3a5a54e275 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; @@ -400,6 +400,20 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN } } break; + case GDScriptParser::TypeNode::VARIABLE: { + GDScriptParser::VariableNode *variable_node = (GDScriptParser::VariableNode *)(node); + lsp::DocumentSymbol symbol; + symbol.kind = lsp::SymbolKind::Variable; + symbol.name = variable_node->identifier->name; + symbol.range.start.line = LINE_NUMBER_TO_INDEX(variable_node->start_line); + symbol.range.start.character = LINE_NUMBER_TO_INDEX(variable_node->start_column); + symbol.range.end.line = LINE_NUMBER_TO_INDEX(variable_node->end_line); + symbol.range.end.character = LINE_NUMBER_TO_INDEX(variable_node->end_column); + symbol.uri = uri; + symbol.script_path = path; + r_symbol.children.push_back(symbol); + } break; + default: continue; } diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 0aa53c1dbe..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) { diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index b90c452346..7fc2962341 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -185,15 +185,27 @@ const lsp::DocumentSymbol *GDScriptWorkspace::get_local_symbol(const ExtendGDScr const lsp::DocumentSymbol *class_symbol = &p_parser->get_symbols(); for (int i = 0; i < class_symbol->children.size(); ++i) { - if (class_symbol->children[i].kind == lsp::SymbolKind::Function || class_symbol->children[i].kind == lsp::SymbolKind::Class) { - const lsp::DocumentSymbol *function_symbol = &class_symbol->children[i]; + int kind = class_symbol->children[i].kind; + switch (kind) { + case lsp::SymbolKind::Function: + case lsp::SymbolKind::Method: + case lsp::SymbolKind::Class: { + const lsp::DocumentSymbol *function_symbol = &class_symbol->children[i]; + + for (int l = 0; l < function_symbol->children.size(); ++l) { + const lsp::DocumentSymbol *local = &function_symbol->children[l]; + if (!local->detail.is_empty() && local->name == p_symbol_identifier) { + return local; + } + } + } break; - for (int l = 0; l < function_symbol->children.size(); ++l) { - const lsp::DocumentSymbol *local = &function_symbol->children[l]; - if (!local->detail.is_empty() && local->name == p_symbol_identifier) { - return local; + case lsp::SymbolKind::Variable: { + const lsp::DocumentSymbol *variable_symbol = &class_symbol->children[i]; + if (variable_symbol->name == p_symbol_identifier) { + return variable_symbol; } - } + } break; } } @@ -650,8 +662,18 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) { symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location)); - if (symbol && symbol->kind == lsp::SymbolKind::Function && symbol->name != symbol_identifier) { - symbol = get_parameter_symbol(symbol, symbol_identifier); + if (symbol) { + switch (symbol->kind) { + case lsp::SymbolKind::Function: { + if (symbol->name != symbol_identifier) { + symbol = get_parameter_symbol(symbol, symbol_identifier); + } + } break; + + case lsp::SymbolKind::Variable: { + symbol = get_local_symbol(parser, symbol_identifier); + } break; + } } } diff --git a/modules/gdscript/language_server/godot_lsp.h b/modules/gdscript/language_server/godot_lsp.h index 8a033204da..5b5327bdb7 100644 --- a/modules/gdscript/language_server/godot_lsp.h +++ b/modules/gdscript/language_server/godot_lsp.h @@ -293,16 +293,6 @@ struct WorkspaceEdit { } _FORCE_INLINE_ void add_change(const String &uri, const int &line, const int &start_character, const int &end_character, const String &new_text) { - if (HashMap<String, Vector<TextEdit>>::Iterator E = changes.find(uri)) { - Vector<TextEdit> edit_list = E->value; - for (int i = 0; i < edit_list.size(); ++i) { - TextEdit edit = edit_list[i]; - if (edit.range.start.character == start_character) { - return; - } - } - } - TextEdit new_edit; new_edit.newText = new_text; new_edit.range.start.line = line; 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/errors/prints_base_type_not_found.gd b/modules/gdscript/tests/scripts/analyzer/errors/prints_base_type_not_found.gd new file mode 100644 index 0000000000..e56ae7b11d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/prints_base_type_not_found.gd @@ -0,0 +1,6 @@ +class InnerClass: + pass + +func test(): + var x : InnerClass.DoesNotExist + print("FAIL") diff --git a/modules/gdscript/tests/scripts/analyzer/errors/prints_base_type_not_found.out b/modules/gdscript/tests/scripts/analyzer/errors/prints_base_type_not_found.out new file mode 100644 index 0000000000..29c75ae3c0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/prints_base_type_not_found.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Could not find type "DoesNotExist" under base "InnerClass". 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/errors/duplicate_icon.gd b/modules/gdscript/tests/scripts/parser/errors/duplicate_icon.gd new file mode 100644 index 0000000000..7500e406f6 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/duplicate_icon.gd @@ -0,0 +1,5 @@ +@icon("res://1.png") +@icon("res://1.png") + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/duplicate_icon.out b/modules/gdscript/tests/scripts/parser/errors/duplicate_icon.out new file mode 100644 index 0000000000..d6cbc95d10 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/duplicate_icon.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +"@icon" annotation can only be used once. diff --git a/modules/gdscript/tests/scripts/parser/errors/duplicate_tool.gd b/modules/gdscript/tests/scripts/parser/errors/duplicate_tool.gd new file mode 100644 index 0000000000..3a2f7118f9 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/duplicate_tool.gd @@ -0,0 +1,5 @@ +@tool +@tool + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/duplicate_tool.out b/modules/gdscript/tests/scripts/parser/errors/duplicate_tool.out new file mode 100644 index 0000000000..26fe23fb78 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/duplicate_tool.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +"@tool" annotation can only be used once. 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 |