Browse Source

Merge pull request #71634 from dalexeev/gds-annotations-analyzer

GDScript: Allow constant expressions in annotations
Rémi Verschelde 2 years ago
parent
commit
a5c211641f
23 changed files with 156 additions and 188 deletions
  1. 1 1
      doc/classes/Dictionary.xml
  2. 2 2
      doc/classes/MultiplayerAPI.xml
  3. 1 1
      doc/classes/Node.xml
  4. 4 2
      editor/plugins/script_text_editor.cpp
  5. 20 20
      editor/project_converter_3_to_4.cpp
  6. 1 1
      modules/gdscript/doc_classes/@GDScript.xml
  7. 21 79
      modules/gdscript/gdscript.cpp
  8. 63 3
      modules/gdscript/gdscript_analyzer.cpp
  9. 3 0
      modules/gdscript/gdscript_editor.cpp
  10. 4 62
      modules/gdscript/gdscript_parser.cpp
  11. 1 1
      modules/gdscript/gdscript_parser.h
  12. 6 0
      modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.gd
  13. 2 0
      modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.out
  14. 10 0
      modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.gd
  15. 1 0
      modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.out
  16. 3 3
      modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd
  17. 1 1
      modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd
  18. 1 1
      modules/gdscript/tests/scripts/parser/features/export_variable.gd
  19. 1 1
      modules/gdscript/tests/scripts/runtime/features/await_on_void.gd
  20. 2 2
      modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd
  21. 2 2
      modules/gdscript/tests/scripts/runtime/features/gdscript.gd
  22. 5 5
      modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd
  23. 1 1
      modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.gd

+ 1 - 1
doc/classes/Dictionary.xml

@@ -42,7 +42,7 @@
 		You can access a dictionary's value by referencing its corresponding key. In the above example, [code]points_dict["White"][/code] will return [code]50[/code]. You can also write [code]points_dict.White[/code], which is equivalent. However, you'll have to use the bracket syntax if the key you're accessing the dictionary with isn't a fixed string (such as a number or variable).
 		[codeblocks]
 		[gdscript]
-		@export(String, "White", "Yellow", "Orange") var my_color
+		@export_enum("White", "Yellow", "Orange") var my_color: String
 		var points_dict = {"White": 50, "Yellow": 75, "Orange": 100}
 		func _ready():
 		    # We can't use dot syntax here as `my_color` is a variable.

+ 2 - 2
doc/classes/MultiplayerAPI.xml

@@ -138,10 +138,10 @@
 			Used with [method Node.rpc_config] to disable a method or property for all RPC calls, making it unavailable. Default for all methods.
 		</constant>
 		<constant name="RPC_MODE_ANY_PEER" value="1" enum="RPCMode">
-			Used with [method Node.rpc_config] to set a method to be callable remotely by any peer. Analogous to the [code]@rpc(any)[/code] annotation. Calls are accepted from all remote peers, no matter if they are node's authority or not.
+			Used with [method Node.rpc_config] to set a method to be callable remotely by any peer. Analogous to the [code]@rpc("any")[/code] annotation. Calls are accepted from all remote peers, no matter if they are node's authority or not.
 		</constant>
 		<constant name="RPC_MODE_AUTHORITY" value="2" enum="RPCMode">
-			Used with [method Node.rpc_config] to set a method to be callable remotely only by the current multiplayer authority (which is the server by default). Analogous to the [code]@rpc(authority)[/code] annotation. See [method Node.set_multiplayer_authority].
+			Used with [method Node.rpc_config] to set a method to be callable remotely only by the current multiplayer authority (which is the server by default). Analogous to the [code]@rpc("authority")[/code] annotation. See [method Node.set_multiplayer_authority].
 		</constant>
 	</constants>
 </class>

+ 1 - 1
doc/classes/Node.xml

@@ -666,7 +666,7 @@
 				    channel = 0,
 				}
 				[/codeblock]
-				See [enum MultiplayerAPI.RPCMode] and [enum MultiplayerPeer.TransferMode]. An alternative is annotating methods and properties with the corresponding annotation ([code]@rpc(any)[/code], [code]@rpc(authority)[/code]). By default, methods are not exposed to networking (and RPCs).
+				See [enum MultiplayerAPI.RPCMode] and [enum MultiplayerPeer.TransferMode]. An alternative is annotating methods and properties with the corresponding annotation ([code]@rpc("any")[/code], [code]@rpc("authority")[/code]). By default, methods are not exposed to networking (and RPCs).
 			</description>
 		</method>
 		<method name="rpc_id" qualifiers="vararg">

+ 4 - 2
editor/plugins/script_text_editor.cpp

@@ -252,12 +252,14 @@ void ScriptTextEditor::_warning_clicked(Variant p_line) {
 	} else if (p_line.get_type() == Variant::DICTIONARY) {
 		Dictionary meta = p_line.operator Dictionary();
 		const int line = meta["line"].operator int64_t() - 1;
+		const String code = meta["code"].operator String();
+		const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\"";
 
 		CodeEdit *text_editor = code_editor->get_text_editor();
 		String prev_line = line > 0 ? text_editor->get_line(line - 1) : "";
 		if (prev_line.contains("@warning_ignore")) {
 			const int closing_bracket_idx = prev_line.find(")");
-			const String text_to_insert = ", " + meta["code"].operator String();
+			const String text_to_insert = ", " + code.quote(quote_style);
 			prev_line = prev_line.insert(closing_bracket_idx, text_to_insert);
 			text_editor->set_line(line - 1, prev_line);
 		} else {
@@ -268,7 +270,7 @@ void ScriptTextEditor::_warning_clicked(Variant p_line) {
 			} else {
 				annotation_indent = String(" ").repeat(text_editor->get_indent_size() * indent);
 			}
-			text_editor->insert_line_at(line, annotation_indent + "@warning_ignore(" + meta["code"].operator String() + ")");
+			text_editor->insert_line_at(line, annotation_indent + "@warning_ignore(" + code.quote(quote_style) + ")");
 		}
 
 		_validate_script();

+ 20 - 20
editor/project_converter_3_to_4.cpp

@@ -2549,14 +2549,14 @@ bool ProjectConverter3To4::test_conversion(RegExContainer &reg_container) {
 	valid = valid && test_conversion_with_regex("tool", "@tool", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
 	valid = valid && test_conversion_with_regex("\n    tool", "\n    tool", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
 	valid = valid && test_conversion_with_regex("\n\ntool", "\n\n@tool", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("\n\nremote func", "\n\n@rpc(any_peer) func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("\n\nremotesync func", "\n\n@rpc(any_peer, call_local) func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("\n\nsync func", "\n\n@rpc(any_peer, call_local) func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
+	valid = valid && test_conversion_with_regex("\n\nremote func", "\n\n@rpc(\"any_peer\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
+	valid = valid && test_conversion_with_regex("\n\nremotesync func", "\n\n@rpc(\"any_peer\", \"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
+	valid = valid && test_conversion_with_regex("\n\nsync func", "\n\n@rpc(\"any_peer\", \"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
 	valid = valid && test_conversion_with_regex("\n\nslave func", "\n\n@rpc func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
 	valid = valid && test_conversion_with_regex("\n\npuppet func", "\n\n@rpc func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("\n\npuppetsync func", "\n\n@rpc(call_local) func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
+	valid = valid && test_conversion_with_regex("\n\npuppetsync func", "\n\n@rpc(\"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
 	valid = valid && test_conversion_with_regex("\n\nmaster func", "\n\nThe master and mastersync rpc behavior is not officially supported anymore. Try using another keyword or making custom logic using get_multiplayer().get_remote_sender_id()\n@rpc func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("\n\nmastersync func", "\n\nThe master and mastersync rpc behavior is not officially supported anymore. Try using another keyword or making custom logic using get_multiplayer().get_remote_sender_id()\n@rpc(call_local) func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
+	valid = valid && test_conversion_with_regex("\n\nmastersync func", "\n\nThe master and mastersync rpc behavior is not officially supported anymore. Try using another keyword or making custom logic using get_multiplayer().get_remote_sender_id()\n@rpc(\"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
 
 	valid = valid && test_conversion_gdscript_builtin("var size : Vector2 = Vector2() setget set_function , get_function", "var size : Vector2 = Vector2() : get = get_function, set = set_function", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 	valid = valid && test_conversion_gdscript_builtin("var size : Vector2 = Vector2() setget set_function , ", "var size : Vector2 = Vector2() : set = set_function", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
@@ -4087,13 +4087,13 @@ void ProjectConverter3To4::rename_gdscript_keywords(Vector<String> &lines, const
 				line = reg_container.keyword_gdscript_onready.sub(line, "@onready", true);
 			}
 			if (line.contains("remote")) {
-				line = reg_container.keyword_gdscript_remote.sub(line, "@rpc(any_peer) func", true);
+				line = reg_container.keyword_gdscript_remote.sub(line, "@rpc(\"any_peer\") func", true);
 			}
 			if (line.contains("remote")) {
-				line = reg_container.keyword_gdscript_remotesync.sub(line, "@rpc(any_peer, call_local) func", true);
+				line = reg_container.keyword_gdscript_remotesync.sub(line, "@rpc(\"any_peer\", \"call_local\") func", true);
 			}
 			if (line.contains("sync")) {
-				line = reg_container.keyword_gdscript_sync.sub(line, "@rpc(any_peer, call_local) func", true);
+				line = reg_container.keyword_gdscript_sync.sub(line, "@rpc(\"any_peer\", \"call_local\") func", true);
 			}
 			if (line.contains("slave")) {
 				line = reg_container.keyword_gdscript_slave.sub(line, "@rpc func", true);
@@ -4102,13 +4102,13 @@ void ProjectConverter3To4::rename_gdscript_keywords(Vector<String> &lines, const
 				line = reg_container.keyword_gdscript_puppet.sub(line, "@rpc func", true);
 			}
 			if (line.contains("puppet")) {
-				line = reg_container.keyword_gdscript_puppetsync.sub(line, "@rpc(call_local) func", true);
+				line = reg_container.keyword_gdscript_puppetsync.sub(line, "@rpc(\"call_local\") func", true);
 			}
 			if (line.contains("master")) {
 				line = reg_container.keyword_gdscript_master.sub(line, error_message + "@rpc func", true);
 			}
 			if (line.contains("master")) {
-				line = reg_container.keyword_gdscript_mastersync.sub(line, error_message + "@rpc(call_local) func", true);
+				line = reg_container.keyword_gdscript_mastersync.sub(line, error_message + "@rpc(\"call_local\") func", true);
 			}
 		}
 	}
@@ -4156,25 +4156,25 @@ Vector<String> ProjectConverter3To4::check_for_rename_gdscript_keywords(Vector<S
 
 			if (line.contains("remote")) {
 				old = line;
-				line = reg_container.keyword_gdscript_remote.sub(line, "@rpc(any_peer) func", true);
+				line = reg_container.keyword_gdscript_remote.sub(line, "@rpc(\"any_peer\") func", true);
 				if (old != line) {
-					found_renames.append(line_formatter(current_line, "remote func", "@rpc(any_peer) func", line));
+					found_renames.append(line_formatter(current_line, "remote func", "@rpc(\"any_peer\") func", line));
 				}
 			}
 
 			if (line.contains("remote")) {
 				old = line;
-				line = reg_container.keyword_gdscript_remotesync.sub(line, "@rpc(any_peer, call_local)) func", true);
+				line = reg_container.keyword_gdscript_remotesync.sub(line, "@rpc(\"any_peer\", \"call_local\")) func", true);
 				if (old != line) {
-					found_renames.append(line_formatter(current_line, "remotesync func", "@rpc(any_peer, call_local)) func", line));
+					found_renames.append(line_formatter(current_line, "remotesync func", "@rpc(\"any_peer\", \"call_local\")) func", line));
 				}
 			}
 
 			if (line.contains("sync")) {
 				old = line;
-				line = reg_container.keyword_gdscript_sync.sub(line, "@rpc(any_peer, call_local)) func", true);
+				line = reg_container.keyword_gdscript_sync.sub(line, "@rpc(\"any_peer\", \"call_local\")) func", true);
 				if (old != line) {
-					found_renames.append(line_formatter(current_line, "sync func", "@rpc(any_peer, call_local)) func", line));
+					found_renames.append(line_formatter(current_line, "sync func", "@rpc(\"any_peer\", \"call_local\")) func", line));
 				}
 			}
 
@@ -4196,9 +4196,9 @@ Vector<String> ProjectConverter3To4::check_for_rename_gdscript_keywords(Vector<S
 
 			if (line.contains("puppet")) {
 				old = line;
-				line = reg_container.keyword_gdscript_puppetsync.sub(line, "@rpc(call_local) func", true);
+				line = reg_container.keyword_gdscript_puppetsync.sub(line, "@rpc(\"call_local\") func", true);
 				if (old != line) {
-					found_renames.append(line_formatter(current_line, "puppetsync func", "@rpc(call_local) func", line));
+					found_renames.append(line_formatter(current_line, "puppetsync func", "@rpc(\"call_local\") func", line));
 				}
 			}
 
@@ -4212,9 +4212,9 @@ Vector<String> ProjectConverter3To4::check_for_rename_gdscript_keywords(Vector<S
 
 			if (line.contains("master")) {
 				old = line;
-				line = reg_container.keyword_gdscript_master.sub(line, "@rpc(call_local) func", true);
+				line = reg_container.keyword_gdscript_master.sub(line, "@rpc(\"call_local\") func", true);
 				if (old != line) {
-					found_renames.append(line_formatter(current_line, "mastersync func", "@rpc(call_local) func", line));
+					found_renames.append(line_formatter(current_line, "mastersync func", "@rpc(\"call_local\") func", line));
 				}
 			}
 		}

+ 1 - 1
modules/gdscript/doc_classes/@GDScript.xml

@@ -485,7 +485,7 @@
 				Export a [NodePath] property with a filter for allowed node types.
 				See also [constant PROPERTY_HINT_NODE_PATH_VALID_TYPES].
 				[codeblock]
-				@export_node_path(Button, TouchScreenButton) var some_button
+				@export_node_path("Button", "TouchScreenButton") var some_button
 				[/codeblock]
 			</description>
 		</annotation>

+ 21 - 79
modules/gdscript/gdscript.cpp

@@ -2448,7 +2448,6 @@ bool GDScriptLanguage::handles_global_class_type(const String &p_type) const {
 }
 
 String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const {
-	Vector<uint8_t> sourcef;
 	Error err;
 	Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
 	if (err) {
@@ -2459,88 +2458,31 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
 
 	GDScriptParser parser;
 	err = parser.parse(source, p_path, false);
+	if (err) {
+		return String();
+	}
 
-	// TODO: Simplify this code by using the analyzer to get full inheritance.
-	if (err == OK) {
-		const GDScriptParser::ClassNode *c = parser.get_tree();
-		if (r_icon_path) {
-			if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) {
-				*r_icon_path = c->icon_path;
-			} else if (c->icon_path.is_relative_path()) {
-				*r_icon_path = p_path.get_base_dir().path_join(c->icon_path).simplify_path();
-			}
-		}
-		if (r_base_type) {
-			const GDScriptParser::ClassNode *subclass = c;
-			String path = p_path;
-			GDScriptParser subparser;
-			while (subclass) {
-				if (subclass->extends_used) {
-					if (!subclass->extends_path.is_empty()) {
-						if (subclass->extends.size() == 0) {
-							get_global_class_name(subclass->extends_path, r_base_type);
-							subclass = nullptr;
-							break;
-						} else {
-							Vector<StringName> extend_classes = subclass->extends;
-
-							Ref<FileAccess> subfile = FileAccess::open(subclass->extends_path, FileAccess::READ);
-							if (subfile.is_null()) {
-								break;
-							}
-							String subsource = subfile->get_as_utf8_string();
-
-							if (subsource.is_empty()) {
-								break;
-							}
-							String subpath = subclass->extends_path;
-							if (subpath.is_relative_path()) {
-								subpath = path.get_base_dir().path_join(subpath).simplify_path();
-							}
-
-							if (OK != subparser.parse(subsource, subpath, false)) {
-								break;
-							}
-							path = subpath;
-							subclass = subparser.get_tree();
-
-							while (extend_classes.size() > 0) {
-								bool found = false;
-								for (int i = 0; i < subclass->members.size(); i++) {
-									if (subclass->members[i].type != GDScriptParser::ClassNode::Member::CLASS) {
-										continue;
-									}
-
-									const GDScriptParser::ClassNode *inner_class = subclass->members[i].m_class;
-									if (inner_class->identifier->name == extend_classes[0]) {
-										extend_classes.remove_at(0);
-										found = true;
-										subclass = inner_class;
-										break;
-									}
-								}
-								if (!found) {
-									subclass = nullptr;
-									break;
-								}
-							}
-						}
-					} else if (subclass->extends.size() == 1) {
-						*r_base_type = subclass->extends[0];
-						subclass = nullptr;
-					} else {
-						break;
-					}
-				} else {
-					*r_base_type = "RefCounted";
-					subclass = nullptr;
-				}
-			}
+	GDScriptAnalyzer analyzer(&parser);
+	err = analyzer.resolve_inheritance();
+	if (err) {
+		return String();
+	}
+
+	const GDScriptParser::ClassNode *c = parser.get_tree();
+
+	if (r_base_type) {
+		*r_base_type = c->get_datatype().native_type;
+	}
+
+	if (r_icon_path) {
+		if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) {
+			*r_icon_path = c->icon_path.simplify_path();
+		} else if (c->icon_path.is_relative_path()) {
+			*r_icon_path = p_path.get_base_dir().path_join(c->icon_path).simplify_path();
 		}
-		return c->identifier != nullptr ? String(c->identifier->name) : String();
 	}
 
-	return String();
+	return c->identifier != nullptr ? String(c->identifier->name) : String();
 }
 
 GDScriptLanguage::GDScriptLanguage() {

+ 63 - 3
modules/gdscript/gdscript_analyzer.cpp

@@ -802,6 +802,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
 
 				// Apply annotations.
 				for (GDScriptParser::AnnotationNode *&E : member.variable->annotations) {
+					resolve_annotation(E);
 					E->apply(parser, member.variable);
 				}
 			} break;
@@ -812,6 +813,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
 
 				// Apply annotations.
 				for (GDScriptParser::AnnotationNode *&E : member.constant->annotations) {
+					resolve_annotation(E);
 					E->apply(parser, member.constant);
 				}
 			} break;
@@ -835,6 +837,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
 
 				// Apply annotations.
 				for (GDScriptParser::AnnotationNode *&E : member.signal->annotations) {
+					resolve_annotation(E);
 					E->apply(parser, member.signal);
 				}
 			} break;
@@ -882,6 +885,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
 
 				// Apply annotations.
 				for (GDScriptParser::AnnotationNode *&E : member.m_enum->annotations) {
+					resolve_annotation(E);
 					E->apply(parser, member.m_enum);
 				}
 			} break;
@@ -1064,6 +1068,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
 		if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) {
 			// Apply annotations.
 			for (GDScriptParser::AnnotationNode *&E : member.function->annotations) {
+				resolve_annotation(E);
 				E->apply(parser, member.function);
 			}
 
@@ -1290,7 +1295,55 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root
 }
 
 void GDScriptAnalyzer::resolve_annotation(GDScriptParser::AnnotationNode *p_annotation) {
-	// TODO: Add second validation function for annotations, so they can use checked types.
+	ERR_FAIL_COND_MSG(!parser->valid_annotations.has(p_annotation->name), vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name));
+
+	const MethodInfo &annotation_info = parser->valid_annotations[p_annotation->name].info;
+
+	const List<PropertyInfo>::Element *E = annotation_info.arguments.front();
+	for (int i = 0; i < p_annotation->arguments.size(); i++) {
+		GDScriptParser::ExpressionNode *argument = p_annotation->arguments[i];
+		const PropertyInfo &argument_info = E->get();
+
+		if (E->next() != nullptr) {
+			E = E->next();
+		}
+
+		reduce_expression(argument);
+
+		if (!argument->is_constant) {
+			push_error(vformat(R"(Argument %d of annotation "%s" isn't a constant expression.)", i + 1, p_annotation->name), argument);
+			return;
+		}
+
+		Variant value = argument->reduced_value;
+
+		if (value.get_type() != argument_info.type) {
+#ifdef DEBUG_ENABLED
+			if (argument_info.type == Variant::INT && value.get_type() == Variant::FLOAT) {
+				parser->push_warning(argument, GDScriptWarning::NARROWING_CONVERSION);
+			}
+#endif
+
+			if (!Variant::can_convert_strict(value.get_type(), argument_info.type)) {
+				push_error(vformat(R"(Invalid argument for annotation "%s": argument %d should be "%s" but is "%s".)", p_annotation->name, i + 1, Variant::get_type_name(argument_info.type), argument->get_datatype().to_string()), argument);
+				return;
+			}
+
+			Variant converted_to;
+			const Variant *converted_from = &value;
+			Callable::CallError call_error;
+			Variant::construct(argument_info.type, converted_to, &converted_from, 1, call_error);
+
+			if (call_error.error != Callable::CallError::CALL_OK) {
+				push_error(vformat(R"(Cannot convert argument %d of annotation "%s" from "%s" to "%s".)", i + 1, p_annotation->name, Variant::get_type_name(value.get_type()), Variant::get_type_name(argument_info.type)), argument);
+				return;
+			}
+
+			value = converted_to;
+		}
+
+		p_annotation->resolved_arguments.push_back(value);
+	}
 }
 
 void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *p_function, const GDScriptParser::Node *p_source, bool p_is_lambda) {
@@ -1486,8 +1539,10 @@ void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScript
 void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) {
 	for (int i = 0; i < p_suite->statements.size(); i++) {
 		GDScriptParser::Node *stmt = p_suite->statements[i];
-		for (GDScriptParser::AnnotationNode *&annotation : stmt->annotations) {
-			annotation->apply(parser, stmt);
+		// Apply annotations.
+		for (GDScriptParser::AnnotationNode *&E : stmt->annotations) {
+			resolve_annotation(E);
+			E->apply(parser, stmt);
 		}
 
 #ifdef DEBUG_ENABLED
@@ -4551,6 +4606,11 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) {
 }
 
 Error GDScriptAnalyzer::resolve_inheritance() {
+	// Apply annotations.
+	for (GDScriptParser::AnnotationNode *&E : parser->head->annotations) {
+		resolve_annotation(E);
+		E->apply(parser, parser->head);
+	}
 	return resolve_class_inheritance(parser->head, true);
 }
 

+ 3 - 0
modules/gdscript/gdscript_editor.cpp

@@ -782,6 +782,7 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a
 		}
 	} else if (p_annotation->name == SNAME("@export_node_path")) {
 		ScriptLanguage::CodeCompletionOption node("Node", ScriptLanguage::CODE_COMPLETION_KIND_CLASS);
+		node.insert_text = node.display.quote(p_quote_style);
 		r_result.insert(node.display, node);
 		List<StringName> node_types;
 		ClassDB::get_inheriters_from_class("Node", &node_types);
@@ -790,11 +791,13 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a
 				continue;
 			}
 			ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS);
+			option.insert_text = option.display.quote(p_quote_style);
 			r_result.insert(option.display, option);
 		}
 	} else if (p_annotation->name == SNAME("@warning_ignore")) {
 		for (int warning_code = 0; warning_code < GDScriptWarning::WARNING_MAX; warning_code++) {
 			ScriptLanguage::CodeCompletionOption warning(GDScriptWarning::get_name_from_code((GDScriptWarning::Code)warning_code).to_lower(), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
+			warning.insert_text = warning.display.quote(p_quote_style);
 			r_result.insert(warning.display, warning);
 		}
 	}

+ 4 - 62
modules/gdscript/gdscript_parser.cpp

@@ -529,7 +529,7 @@ void GDScriptParser::parse_program() {
 		AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
 		if (annotation != nullptr) {
 			if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
-				annotation->apply(this, head);
+				head->annotations.push_back(annotation);
 			} else {
 				annotation_stack.push_back(annotation);
 				// This annotation must appear after script-level annotations
@@ -771,7 +771,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
 		return;
 	}
 
-	// Apply annotations.
 	for (AnnotationNode *&annotation : annotations) {
 		member->annotations.push_back(annotation);
 	}
@@ -848,7 +847,7 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
 						if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
 							push_error(R"(Expected newline after a standalone annotation.)");
 						}
-						annotation->apply(this, head);
+						head->annotations.push_back(annotation);
 					} else {
 						annotation_stack.push_back(annotation);
 					}
@@ -1470,7 +1469,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
 	match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional.
 
 	if (valid) {
-		valid = validate_annotation_arguments(annotation);
+		valid = validate_annotation_argument_count(annotation);
 	}
 
 	return valid ? annotation : nullptr;
@@ -1717,7 +1716,6 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
 		}
 	}
 
-	// Apply annotations to statement.
 	while (!is_annotation && result != nullptr && !annotation_stack.is_empty()) {
 		AnnotationNode *last_annotation = annotation_stack.back()->get();
 		if (last_annotation->applies_to(AnnotationInfo::STATEMENT)) {
@@ -3598,7 +3596,7 @@ bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const {
 	return (info->target_kind & p_target_kinds) > 0;
 }
 
-bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) {
+bool GDScriptParser::validate_annotation_argument_count(AnnotationNode *p_annotation) {
 	ERR_FAIL_COND_V_MSG(!valid_annotations.has(p_annotation->name), false, vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name));
 
 	const MethodInfo &info = valid_annotations[p_annotation->name].info;
@@ -3613,62 +3611,6 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation)
 		return false;
 	}
 
-	const List<PropertyInfo>::Element *E = info.arguments.front();
-	for (int i = 0; i < p_annotation->arguments.size(); i++) {
-		ExpressionNode *argument = p_annotation->arguments[i];
-		const PropertyInfo &parameter = E->get();
-
-		if (E->next() != nullptr) {
-			E = E->next();
-		}
-
-		switch (parameter.type) {
-			case Variant::STRING:
-			case Variant::STRING_NAME:
-			case Variant::NODE_PATH:
-				// Allow "quote-less strings", as long as they are recognized as identifiers.
-				if (argument->type == Node::IDENTIFIER) {
-					IdentifierNode *string = static_cast<IdentifierNode *>(argument);
-					Callable::CallError error;
-					Vector<Variant> args = varray(string->name);
-					const Variant *name = args.ptr();
-					Variant r;
-					Variant::construct(parameter.type, r, &(name), 1, error);
-					p_annotation->resolved_arguments.push_back(r);
-					if (error.error != Callable::CallError::CALL_OK) {
-						push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
-						p_annotation->resolved_arguments.remove_at(p_annotation->resolved_arguments.size() - 1);
-						return false;
-					}
-					break;
-				}
-				[[fallthrough]];
-			default: {
-				if (argument->type != Node::LITERAL) {
-					push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
-					return false;
-				}
-
-				Variant value = static_cast<LiteralNode *>(argument)->value;
-				if (!Variant::can_convert_strict(value.get_type(), parameter.type)) {
-					push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
-					return false;
-				}
-				Callable::CallError error;
-				const Variant *args = &value;
-				Variant r;
-				Variant::construct(parameter.type, r, &(args), 1, error);
-				p_annotation->resolved_arguments.push_back(r);
-				if (error.error != Callable::CallError::CALL_OK) {
-					push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
-					p_annotation->resolved_arguments.remove_at(p_annotation->resolved_arguments.size() - 1);
-					return false;
-				}
-				break;
-			}
-		}
-	}
-
 	return true;
 }
 

+ 1 - 1
modules/gdscript/gdscript_parser.h

@@ -1401,7 +1401,7 @@ private:
 	// Annotations
 	AnnotationNode *parse_annotation(uint32_t p_valid_targets);
 	bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false);
-	bool validate_annotation_arguments(AnnotationNode *p_annotation);
+	bool validate_annotation_argument_count(AnnotationNode *p_annotation);
 	void clear_unused_annotations();
 	bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target);
 	bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target);

+ 6 - 0
modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.gd

@@ -0,0 +1,6 @@
+var num := 1
+
+@export_range(num, 10) var a
+
+func test():
+	pass

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.out

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Argument 1 of annotation "@export_range" isn't a constant expression.

+ 10 - 0
modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.gd

@@ -0,0 +1,10 @@
+const BEFORE = 1
+
+@export_range(-10, 10) var a = 0
+@export_range(1 + 2, absi(-10) + 1) var b = 5
+@export_range(BEFORE + 1, BEFORE + AFTER + 1) var c = 5
+
+const AFTER = 10
+
+func test():
+	pass

+ 1 - 0
modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.out

@@ -0,0 +1 @@
+GDTEST_OK

+ 3 - 3
modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd

@@ -1,12 +1,12 @@
-@warning_ignore(unused_private_class_variable)
+@warning_ignore("unused_private_class_variable")
 var _unused = 2
 
-@warning_ignore(unused_variable)
+@warning_ignore("unused_variable")
 func test():
 	print("test")
 	var unused = 3
 
-	@warning_ignore(redundant_await)
+	@warning_ignore("redundant_await")
 	print(await regular_func())
 
 	print("done")

+ 1 - 1
modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd

@@ -1,6 +1,6 @@
 # https://github.com/godotengine/godot/issues/50285
 
-@warning_ignore(unused_local_constant)
+@warning_ignore("unused_local_constant")
 func test():
 	const CONST_INNER_DICTIONARY = { "key": true }
 	const CONST_NESTED_DICTIONARY_OLD_WORKAROUND = {

+ 1 - 1
modules/gdscript/tests/scripts/parser/features/export_variable.gd

@@ -5,7 +5,7 @@
 
 @export var color: Color
 @export_color_no_alpha var color_no_alpha: Color
-@export_node_path(Sprite2D, Sprite3D, Control, Node) var nodepath := ^"hello"
+@export_node_path("Sprite2D", "Sprite3D", "Control", "Node") var nodepath := ^"hello"
 
 
 func test():

+ 1 - 1
modules/gdscript/tests/scripts/runtime/features/await_on_void.gd

@@ -2,6 +2,6 @@ func wait() -> void:
 	pass
 
 func test():
-	@warning_ignore(redundant_await)
+	@warning_ignore("redundant_await")
 	await wait()
 	print("end")

+ 2 - 2
modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd

@@ -7,11 +7,11 @@ func test():
 
 func builtin_method():
 	var pba := PackedByteArray()
-	@warning_ignore(return_value_discarded)
+	@warning_ignore("return_value_discarded")
 	pba.resize(1) # Built-in validated.
 
 
 func builtin_method_static():
 	var _pba := PackedByteArray()
-	@warning_ignore(return_value_discarded)
+	@warning_ignore("return_value_discarded")
 	Vector2.from_angle(PI) # Static built-in validated.

+ 2 - 2
modules/gdscript/tests/scripts/runtime/features/gdscript.gd

@@ -11,10 +11,10 @@ class InnerClass:
 	func _init() -> void:
 		prints("Inner")
 '''
-	@warning_ignore(return_value_discarded)
+	@warning_ignore("return_value_discarded")
 	gdscr.reload()
 
 	var inst = gdscr.new()
 
-	@warning_ignore(unsafe_method_access)
+	@warning_ignore("unsafe_method_access")
 	inst.test()

+ 5 - 5
modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd

@@ -20,26 +20,26 @@ func test_utility(v, f):
 	assert(not f) # Test unary operator reading from `nil`.
 
 func test_builtin_call(v, f):
-	@warning_ignore(unsafe_method_access)
+	@warning_ignore("unsafe_method_access")
 	v.angle() # Built-in method call.
 	assert(not f) # Test unary operator reading from `nil`.
 
 func test_builtin_call_validated(v: Vector2, f):
-	@warning_ignore(return_value_discarded)
+	@warning_ignore("return_value_discarded")
 	v.abs() # Built-in method call validated.
 	assert(not f) # Test unary operator reading from `nil`.
 
 func test_object_call(v, f):
-	@warning_ignore(unsafe_method_access)
+	@warning_ignore("unsafe_method_access")
 	v.get_reference_count() # Native type method call.
 	assert(not f) # Test unary operator reading from `nil`.
 
 func test_object_call_method_bind(v: Resource, f):
-	@warning_ignore(return_value_discarded)
+	@warning_ignore("return_value_discarded")
 	v.duplicate() # Native type method call with MethodBind.
 	assert(not f) # Test unary operator reading from `nil`.
 
 func test_object_call_ptrcall(v: RefCounted, f):
-	@warning_ignore(return_value_discarded)
+	@warning_ignore("return_value_discarded")
 	v.get_reference_count() # Native type method call with ptrcall.
 	assert(not f) # Test unary operator reading from `nil`.

+ 1 - 1
modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.gd

@@ -1,7 +1,7 @@
 # https://github.com/godotengine/godot/issues/71172
 
 func test():
-	@warning_ignore(narrowing_conversion)
+	@warning_ignore("narrowing_conversion")
 	var foo: int = 0.0
 	print(typeof(foo) == TYPE_INT)
 	var dict : Dictionary = {"a":0.0}