summaryrefslogtreecommitdiffstats
path: root/modules/gdscript
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript')
-rw-r--r--modules/gdscript/editor/script_templates/RichTextEffect/default.gd17
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp25
-rw-r--r--modules/gdscript/gdscript_byte_codegen.cpp119
-rw-r--r--modules/gdscript/gdscript_byte_codegen.h2
-rw-r--r--modules/gdscript/gdscript_codegen.h1
-rw-r--r--modules/gdscript/gdscript_compiler.cpp3
-rw-r--r--modules/gdscript/gdscript_editor.cpp64
-rw-r--r--modules/gdscript/gdscript_parser.cpp61
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp2
-rw-r--r--modules/gdscript/gdscript_vm.cpp7
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.cpp16
-rw-r--r--modules/gdscript/language_server/gdscript_language_protocol.cpp2
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.cpp40
-rw-r--r--modules/gdscript/language_server/godot_lsp.h10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.gd12
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/extend_variable.gd9
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/extend_variable.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/prints_base_type_not_found.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/prints_base_type_not_found.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.gd4
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/duplicate_icon.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/duplicate_icon.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/duplicate_tool.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/duplicate_tool.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/annotations.gd48
-rw-r--r--modules/gdscript/tests/scripts/parser/features/annotations.out13
-rw-r--r--modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.gd36
-rw-r--r--modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.out1
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