Browse Source

Merge pull request #76020 from dalexeev/gds-warning-ignore-regions

GDScript: Add `@warning_ignore_start` and `@warning_ignore_restore` annotations
Thaddeus Crews 8 months ago
parent
commit
a372214a4a
22 changed files with 219 additions and 87 deletions
  1. 28 1
      modules/gdscript/doc_classes/@GDScript.xml
  2. 4 1
      modules/gdscript/gdscript_editor.cpp
  3. 110 38
      modules/gdscript/gdscript_parser.cpp
  4. 4 2
      modules/gdscript/gdscript_parser.h
  5. 2 2
      modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd
  6. 1 1
      modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd
  7. 2 4
      modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd
  8. 1 3
      modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd
  9. 3 3
      modules/gdscript/tests/scripts/analyzer/features/vararg_call.gd
  10. 1 0
      modules/gdscript/tests/scripts/analyzer/features/warning_ignore_targets.out
  11. 2 3
      modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd
  12. 10 10
      modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out
  13. 5 0
      modules/gdscript/tests/scripts/parser/errors/warning_ignore_extra_start.gd
  14. 2 0
      modules/gdscript/tests/scripts/parser/errors/warning_ignore_extra_start.out
  15. 4 0
      modules/gdscript/tests/scripts/parser/errors/warning_ignore_restore_without_start.gd
  16. 2 0
      modules/gdscript/tests/scripts/parser/errors/warning_ignore_restore_without_start.out
  17. 1 1
      modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd
  18. 2 7
      modules/gdscript/tests/scripts/parser/features/signal_declaration.gd
  19. 26 0
      modules/gdscript/tests/scripts/parser/features/warning_ignore_regions.gd
  20. 6 0
      modules/gdscript/tests/scripts/parser/features/warning_ignore_regions.out
  21. 1 1
      modules/gdscript/tests/scripts/runtime/features/assign_operator.gd
  22. 2 10
      modules/gdscript/tests/scripts/runtime/features/member_info.gd

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

@@ -726,7 +726,7 @@
 				[/codeblock]
 				[/codeblock]
 				[b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported.
 				[b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported.
 				[b]Note:[/b] As annotations describe their subject, the [annotation @icon] annotation must be placed before the class definition and inheritance.
 				[b]Note:[/b] As annotations describe their subject, the [annotation @icon] annotation must be placed before the class definition and inheritance.
-				[b]Note:[/b] Unlike other annotations, the argument of the [annotation @icon] annotation must be a string literal (constant expressions are not supported).
+				[b]Note:[/b] Unlike most other annotations, the argument of the [annotation @icon] annotation must be a string literal (constant expressions are not supported).
 			</description>
 			</description>
 		</annotation>
 		</annotation>
 		<annotation name="@onready">
 		<annotation name="@onready">
@@ -794,6 +794,33 @@
 				    @warning_ignore("unreachable_code")
 				    @warning_ignore("unreachable_code")
 				    print("unreachable")
 				    print("unreachable")
 				[/codeblock]
 				[/codeblock]
+				See also [annotation @warning_ignore_start] and [annotation @warning_ignore_restore].
+			</description>
+		</annotation>
+		<annotation name="@warning_ignore_restore" qualifiers="vararg">
+			<return type="void" />
+			<param index="0" name="warning" type="String" />
+			<description>
+				Stops ignoring the listed warning types after [annotation @warning_ignore_start]. Ignoring the specified warning types will be reset to Project Settings. This annotation can be omitted to ignore the warning types until the end of the file.
+				[b]Note:[/b] Unlike most other annotations, arguments of the [annotation @warning_ignore_restore] annotation must be string literals (constant expressions are not supported).
+			</description>
+		</annotation>
+		<annotation name="@warning_ignore_start" qualifiers="vararg">
+			<return type="void" />
+			<param index="0" name="warning" type="String" />
+			<description>
+				Starts ignoring the listed warning types until the end of the file or the [annotation @warning_ignore_restore] annotation with the given warning type.
+				[codeblock]
+				func test():
+				    var a = 1 # Warning (if enabled in the Project Settings).
+				    @warning_ignore_start("unused_variable")
+				    var b = 2 # No warning.
+				    var c = 3 # No warning.
+				    @warning_ignore_restore("unused_variable")
+				    var d = 4 # Warning (if enabled in the Project Settings).
+				[/codeblock]
+				[b]Note:[/b] To suppress a single warning, use [annotation @warning_ignore] instead.
+				[b]Note:[/b] Unlike most other annotations, arguments of the [annotation @warning_ignore_start] annotation must be string literals (constant expressions are not supported).
 			</description>
 			</description>
 		</annotation>
 		</annotation>
 	</annotations>
 	</annotations>

+ 4 - 1
modules/gdscript/gdscript_editor.cpp

@@ -962,8 +962,11 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a
 				}
 				}
 			} break;
 			} break;
 		}
 		}
-	} else if (p_annotation->name == SNAME("@warning_ignore")) {
+	} else if (p_annotation->name == SNAME("@warning_ignore") || p_annotation->name == SNAME("@warning_ignore_start") || p_annotation->name == SNAME("@warning_ignore_restore")) {
 		for (int warning_code = 0; warning_code < GDScriptWarning::WARNING_MAX; warning_code++) {
 		for (int warning_code = 0; warning_code < GDScriptWarning::WARNING_MAX; warning_code++) {
+			if (warning_code == GDScriptWarning::RENAMED_IN_GODOT_4_HINT) {
+				continue;
+			}
 			ScriptLanguage::CodeCompletionOption warning(GDScriptWarning::get_name_from_code((GDScriptWarning::Code)warning_code).to_lower(), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
 			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);
 			warning.insert_text = warning.display.quote(p_quote_style);
 			r_result.insert(warning.display, warning);
 			r_result.insert(warning.display, warning);

+ 110 - 38
modules/gdscript/gdscript_parser.cpp

@@ -94,10 +94,11 @@ bool GDScriptParser::annotation_exists(const String &p_annotation_name) const {
 GDScriptParser::GDScriptParser() {
 GDScriptParser::GDScriptParser() {
 	// Register valid annotations.
 	// Register valid annotations.
 	if (unlikely(valid_annotations.is_empty())) {
 	if (unlikely(valid_annotations.is_empty())) {
+		// Script annotations.
 		register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation);
 		register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation);
 		register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);
 		register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);
 		register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation);
 		register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation);
-
+		// Onready annotation.
 		register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
 		register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
 		// Export annotations.
 		// Export annotations.
 		register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>);
 		register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>);
@@ -128,13 +129,18 @@ GDScriptParser::GDScriptParser() {
 		register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray(""));
 		register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray(""));
 		register_annotation(MethodInfo("@export_subgroup", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_SUBGROUP>, varray(""));
 		register_annotation(MethodInfo("@export_subgroup", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_SUBGROUP>, varray(""));
 		// Warning annotations.
 		// Warning annotations.
-		register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS_LEVEL | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true);
+		register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS_LEVEL | AnnotationInfo::STATEMENT, &GDScriptParser::warning_ignore_annotation, varray(), true);
+		register_annotation(MethodInfo("@warning_ignore_start", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::STANDALONE, &GDScriptParser::warning_ignore_region_annotations, varray(), true);
+		register_annotation(MethodInfo("@warning_ignore_restore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::STANDALONE, &GDScriptParser::warning_ignore_region_annotations, varray(), true);
 		// Networking.
 		// Networking.
 		register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0));
 		register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0));
 	}
 	}
 
 
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
 	is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable");
 	is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable");
+	for (int i = 0; i < GDScriptWarning::WARNING_MAX; i++) {
+		warning_ignore_start_lines[i] = INT_MAX;
+	}
 #endif
 #endif
 
 
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
@@ -214,6 +220,9 @@ void GDScriptParser::apply_pending_warnings() {
 		if (warning_ignored_lines[pw.code].has(pw.source->start_line)) {
 		if (warning_ignored_lines[pw.code].has(pw.source->start_line)) {
 			continue;
 			continue;
 		}
 		}
+		if (warning_ignore_start_lines[pw.code] <= pw.source->start_line) {
+			continue;
+		}
 
 
 		GDScriptWarning warning;
 		GDScriptWarning warning;
 		warning.code = pw.code;
 		warning.code = pw.code;
@@ -625,7 +634,7 @@ void GDScriptParser::parse_program() {
 				} else if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
 				} else if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
 					PUSH_PENDING_ANNOTATIONS_TO_HEAD;
 					PUSH_PENDING_ANNOTATIONS_TO_HEAD;
 					if (annotation->name == SNAME("@tool") || annotation->name == SNAME("@icon")) {
 					if (annotation->name == SNAME("@tool") || annotation->name == SNAME("@icon")) {
-						// Some annotations need to be resolved in the parser.
+						// Some annotations need to be resolved and applied in the parser.
 						annotation->apply(this, head, nullptr); // `head->outer == nullptr`.
 						annotation->apply(this, head, nullptr); // `head->outer == nullptr`.
 					} else {
 					} else {
 						head->annotations.push_back(annotation);
 						head->annotations.push_back(annotation);
@@ -640,8 +649,10 @@ void GDScriptParser::parse_program() {
 						// so we stop looking for script-level stuff.
 						// so we stop looking for script-level stuff.
 						can_have_class_or_extends = false;
 						can_have_class_or_extends = false;
 						break;
 						break;
+					} else if (annotation->name == SNAME("@warning_ignore_start") || annotation->name == SNAME("@warning_ignore_restore")) {
+						// Some annotations need to be resolved and applied in the parser.
+						annotation->apply(this, nullptr, nullptr);
 					} else {
 					} else {
-						// For potential non-group standalone annotations.
 						push_error(R"(Unexpected standalone annotation.)");
 						push_error(R"(Unexpected standalone annotation.)");
 					}
 					}
 				} else {
 				} else {
@@ -1030,8 +1041,10 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
 						}
 						}
 						if (annotation->name == SNAME("@export_category") || annotation->name == SNAME("@export_group") || annotation->name == SNAME("@export_subgroup")) {
 						if (annotation->name == SNAME("@export_category") || annotation->name == SNAME("@export_group") || annotation->name == SNAME("@export_subgroup")) {
 							current_class->add_member_group(annotation);
 							current_class->add_member_group(annotation);
+						} else if (annotation->name == SNAME("@warning_ignore_start") || annotation->name == SNAME("@warning_ignore_restore")) {
+							// Some annotations need to be resolved and applied in the parser.
+							annotation->apply(this, nullptr, nullptr);
 						} else {
 						} else {
-							// For potential non-group standalone annotations.
 							push_error(R"(Unexpected standalone annotation.)");
 							push_error(R"(Unexpected standalone annotation.)");
 						}
 						}
 					} else { // `AnnotationInfo::CLASS_LEVEL`.
 					} else { // `AnnotationInfo::CLASS_LEVEL`.
@@ -1896,9 +1909,21 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
 			break;
 			break;
 		case GDScriptTokenizer::Token::ANNOTATION: {
 		case GDScriptTokenizer::Token::ANNOTATION: {
 			advance();
 			advance();
-			AnnotationNode *annotation = parse_annotation(AnnotationInfo::STATEMENT);
+			AnnotationNode *annotation = parse_annotation(AnnotationInfo::STATEMENT | AnnotationInfo::STANDALONE);
 			if (annotation != nullptr) {
 			if (annotation != nullptr) {
-				annotation_stack.push_back(annotation);
+				if (annotation->applies_to(AnnotationInfo::STANDALONE)) {
+					if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
+						push_error(R"(Expected newline after a standalone annotation.)");
+					}
+					if (annotation->name == SNAME("@warning_ignore_start") || annotation->name == SNAME("@warning_ignore_restore")) {
+						// Some annotations need to be resolved and applied in the parser.
+						annotation->apply(this, nullptr, nullptr);
+					} else {
+						push_error(R"(Unexpected standalone annotation.)");
+					}
+				} else {
+					annotation_stack.push_back(annotation);
+				}
 			}
 			}
 			break;
 			break;
 		}
 		}
@@ -4096,23 +4121,25 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation)
 		return false;
 		return false;
 	}
 	}
 
 
-	// Some annotations need to be resolved in the parser.
-	if (p_annotation->name == SNAME("@icon")) {
-		ExpressionNode *argument = p_annotation->arguments[0];
+	// Some annotations need to be resolved and applied in the parser.
+	if (p_annotation->name == SNAME("@icon") || p_annotation->name == SNAME("@warning_ignore_start") || p_annotation->name == SNAME("@warning_ignore_restore")) {
+		for (int i = 0; i < p_annotation->arguments.size(); i++) {
+			ExpressionNode *argument = p_annotation->arguments[i];
 
 
-		if (argument->type != Node::LITERAL) {
-			push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)", argument);
-			return false;
-		}
+			if (argument->type != Node::LITERAL) {
+				push_error(vformat(R"(Argument %d of annotation "%s" must be a string literal.)", i + 1, p_annotation->name), argument);
+				return false;
+			}
 
 
-		Variant value = static_cast<LiteralNode *>(argument)->value;
+			Variant value = static_cast<LiteralNode *>(argument)->value;
 
 
-		if (value.get_type() != Variant::STRING) {
-			push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)", argument);
-			return false;
-		}
+			if (value.get_type() != Variant::STRING) {
+				push_error(vformat(R"(Argument %d of annotation "%s" must be a string literal.)", i + 1, p_annotation->name), argument);
+				return false;
+			}
 
 
-		p_annotation->resolved_arguments.push_back(value);
+			p_annotation->resolved_arguments.push_back(value);
+		}
 	}
 	}
 
 
 	// For other annotations, see `GDScriptAnalyzer::resolve_annotation()`.
 	// For other annotations, see `GDScriptAnalyzer::resolve_annotation()`.
@@ -4162,6 +4189,17 @@ bool GDScriptParser::icon_annotation(AnnotationNode *p_annotation, Node *p_targe
 	return true;
 	return true;
 }
 }
 
 
+bool GDScriptParser::static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+	ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, vformat(R"("%s" annotation can only be applied to classes.)", p_annotation->name));
+	ClassNode *class_node = static_cast<ClassNode *>(p_target);
+	if (class_node->annotated_static_unload) {
+		push_error(vformat(R"("%s" annotation can only be used once per script.)", p_annotation->name), p_annotation);
+		return false;
+	}
+	class_node->annotated_static_unload = true;
+	return true;
+}
+
 bool GDScriptParser::onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
 bool GDScriptParser::onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
 	ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)");
 	ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)");
 
 
@@ -4756,11 +4794,8 @@ bool GDScriptParser::export_group_annotations(AnnotationNode *p_annotation, Node
 	return true;
 	return true;
 }
 }
 
 
-bool GDScriptParser::warning_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
-#ifndef DEBUG_ENABLED
-	// Only available in debug builds.
-	return true;
-#else // DEBUG_ENABLED
+bool GDScriptParser::warning_ignore_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+#ifdef DEBUG_ENABLED
 	if (is_ignoring_warnings) {
 	if (is_ignoring_warnings) {
 		return true; // We already ignore all warnings, let's optimize it.
 		return true; // We already ignore all warnings, let's optimize it.
 	}
 	}
@@ -4805,8 +4840,14 @@ bool GDScriptParser::warning_annotations(AnnotationNode *p_annotation, Node *p_t
 				} break;
 				} break;
 
 
 				case Node::FUNCTION: {
 				case Node::FUNCTION: {
-					// `@warning_ignore` on function has a controversial feature that is used in tests.
-					// It's better not to remove it for now, while there is no way to mass-ignore warnings.
+					FunctionNode *function = static_cast<FunctionNode *>(p_target);
+					end_line = function->start_line;
+					for (int i = 0; i < function->parameters.size(); i++) {
+						end_line = MAX(end_line, function->parameters[i]->end_line);
+						if (function->parameters[i]->initializer != nullptr) {
+							end_line = MAX(end_line, function->parameters[i]->initializer->end_line);
+						}
+					}
 				} break;
 				} break;
 
 
 				case Node::MATCH_BRANCH: {
 				case Node::MATCH_BRANCH: {
@@ -4828,6 +4869,48 @@ bool GDScriptParser::warning_annotations(AnnotationNode *p_annotation, Node *p_t
 		}
 		}
 	}
 	}
 	return !has_error;
 	return !has_error;
+#else // !DEBUG_ENABLED
+	// Only available in debug builds.
+	return true;
+#endif // DEBUG_ENABLED
+}
+
+bool GDScriptParser::warning_ignore_region_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
+#ifdef DEBUG_ENABLED
+	bool has_error = false;
+	const bool is_start = p_annotation->name == SNAME("@warning_ignore_start");
+	for (const Variant &warning_name : p_annotation->resolved_arguments) {
+		GDScriptWarning::Code warning_code = GDScriptWarning::get_code_from_name(String(warning_name).to_upper());
+		if (warning_code == GDScriptWarning::WARNING_MAX) {
+			push_error(vformat(R"(Invalid warning name: "%s".)", warning_name), p_annotation);
+			has_error = true;
+			continue;
+		}
+		if (is_start) {
+			if (warning_ignore_start_lines[warning_code] != INT_MAX) {
+				push_error(vformat(R"(Warning "%s" is already being ignored by "@warning_ignore_start" at line %d.)", String(warning_name).to_upper(), warning_ignore_start_lines[warning_code]), p_annotation);
+				has_error = true;
+				continue;
+			}
+			warning_ignore_start_lines[warning_code] = p_annotation->start_line;
+		} else {
+			if (warning_ignore_start_lines[warning_code] == INT_MAX) {
+				push_error(vformat(R"(Warning "%s" is not being ignored by "@warning_ignore_start".)", String(warning_name).to_upper()), p_annotation);
+				has_error = true;
+				continue;
+			}
+			const int start_line = warning_ignore_start_lines[warning_code];
+			const int end_line = MAX(start_line, p_annotation->start_line); // Prevent infinite loop.
+			for (int i = start_line; i <= end_line; i++) {
+				warning_ignored_lines[warning_code].insert(i);
+			}
+			warning_ignore_start_lines[warning_code] = INT_MAX;
+		}
+	}
+	return !has_error;
+#else // !DEBUG_ENABLED
+	// Only available in debug builds.
+	return true;
 #endif // DEBUG_ENABLED
 #endif // DEBUG_ENABLED
 }
 }
 
 
@@ -4892,17 +4975,6 @@ bool GDScriptParser::rpc_annotation(AnnotationNode *p_annotation, Node *p_target
 	return true;
 	return true;
 }
 }
 
 
-bool GDScriptParser::static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
-	ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, vformat(R"("%s" annotation can only be applied to classes.)", p_annotation->name));
-	ClassNode *class_node = static_cast<ClassNode *>(p_target);
-	if (class_node->annotated_static_unload) {
-		push_error(vformat(R"("%s" annotation can only be used once per script.)", p_annotation->name), p_annotation);
-		return false;
-	}
-	class_node->annotated_static_unload = true;
-	return true;
-}
-
 GDScriptParser::DataType GDScriptParser::SuiteNode::Local::get_datatype() const {
 GDScriptParser::DataType GDScriptParser::SuiteNode::Local::get_datatype() const {
 	switch (type) {
 	switch (type) {
 		case CONSTANT:
 		case CONSTANT:

+ 4 - 2
modules/gdscript/gdscript_parser.h

@@ -1358,6 +1358,7 @@ private:
 	List<GDScriptWarning> warnings;
 	List<GDScriptWarning> warnings;
 	List<PendingWarning> pending_warnings;
 	List<PendingWarning> pending_warnings;
 	HashSet<int> warning_ignored_lines[GDScriptWarning::WARNING_MAX];
 	HashSet<int> warning_ignored_lines[GDScriptWarning::WARNING_MAX];
+	int warning_ignore_start_lines[GDScriptWarning::WARNING_MAX];
 	HashSet<int> unsafe_lines;
 	HashSet<int> unsafe_lines;
 #endif
 #endif
 
 
@@ -1506,6 +1507,7 @@ private:
 	void clear_unused_annotations();
 	void clear_unused_annotations();
 	bool tool_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
 	bool tool_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
 	bool icon_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
 	bool icon_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+	bool static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
 	bool onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
 	bool onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
 	template <PropertyHint t_hint, Variant::Type t_type>
 	template <PropertyHint t_hint, Variant::Type t_type>
 	bool export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
 	bool export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
@@ -1514,9 +1516,9 @@ private:
 	bool export_tool_button_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
 	bool export_tool_button_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
 	template <PropertyUsageFlags t_usage>
 	template <PropertyUsageFlags t_usage>
 	bool export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
 	bool export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
-	bool warning_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+	bool warning_ignore_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
+	bool warning_ignore_region_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
 	bool rpc_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
 	bool rpc_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
-	bool static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
 	// Statements.
 	// Statements.
 	Node *parse_statement();
 	Node *parse_statement();
 	VariableNode *parse_variable(bool p_is_static);
 	VariableNode *parse_variable(bool p_is_static);

+ 2 - 2
modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd

@@ -13,7 +13,7 @@ func param_inferred(param := variant()) -> void: print(param)
 func return_untyped(): return variant()
 func return_untyped(): return variant()
 func return_typed() -> Variant: return variant()
 func return_typed() -> Variant: return variant()
 
 
-@warning_ignore("unused_variable", "inference_on_variant")
+@warning_ignore_start("unused_variable", "inference_on_variant")
 func test() -> void:
 func test() -> void:
 	var weak = variant()
 	var weak = variant()
 	var typed: Variant = variant()
 	var typed: Variant = variant()
@@ -32,4 +32,4 @@ func test() -> void:
 	if typed != null: pass
 	if typed != null: pass
 	if typed is Node: pass
 	if typed is Node: pass
 
 
-	print('ok')
+	print("ok")

+ 1 - 1
modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd

@@ -123,4 +123,4 @@ func test():
 	Utils.check((const_null is A) == false)
 	Utils.check((const_null is A) == false)
 	Utils.check(is_instance_of(const_null, A) == false)
 	Utils.check(is_instance_of(const_null, A) == false)
 
 
-	print('ok')
+	print("ok")

+ 2 - 4
modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd

@@ -20,9 +20,7 @@ class Members:
 		Utils.check(str(two) == '[486]')
 		Utils.check(str(two) == '[486]')
 		return true
 		return true
 
 
-
-@warning_ignore("unsafe_method_access")
-@warning_ignore("return_value_discarded")
+@warning_ignore_start('unsafe_method_access', 'return_value_discarded')
 func test():
 func test():
 	var untyped_basic = [459]
 	var untyped_basic = [459]
 	Utils.check(str(untyped_basic) == '[459]')
 	Utils.check(str(untyped_basic) == '[459]')
@@ -207,7 +205,7 @@ func test():
 
 
 	var a := A.new()
 	var a := A.new()
 	var typed_natives: Array[RefCounted] = [a]
 	var typed_natives: Array[RefCounted] = [a]
-	var typed_scripts = Array(typed_natives, TYPE_OBJECT, "RefCounted", A)
+	var typed_scripts = Array(typed_natives, TYPE_OBJECT, 'RefCounted', A)
 	Utils.check(typed_scripts[0] == a)
 	Utils.check(typed_scripts[0] == a)
 
 
 
 

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

@@ -21,9 +21,7 @@ class Members:
 		return true
 		return true
 
 
 
 
-@warning_ignore("unsafe_method_access")
-@warning_ignore("assert_always_true")
-@warning_ignore("return_value_discarded")
+@warning_ignore_start("unsafe_method_access", "return_value_discarded")
 func test():
 func test():
 	var untyped_basic = { 459: 954 }
 	var untyped_basic = { 459: 954 }
 	Utils.check(str(untyped_basic) == '{ 459: 954 }')
 	Utils.check(str(untyped_basic) == '{ 459: 954 }')

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

@@ -1,6 +1,6 @@
 signal ok()
 signal ok()
 
 
-@warning_ignore("return_value_discarded")
+@warning_ignore_start("return_value_discarded")
 func test():
 func test():
-	ok.connect(func(): print('ok'))
-	emit_signal(&'ok')
+	ok.connect(func(): print("ok"))
+	emit_signal(&"ok")

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

@@ -1,6 +1,7 @@
 GDTEST_OK
 GDTEST_OK
 ~~ WARNING at line 3: (CONFUSABLE_IDENTIFIER) The identifier "my_vАr" has misleading characters and might be confused with something else.
 ~~ WARNING at line 3: (CONFUSABLE_IDENTIFIER) The identifier "my_vАr" has misleading characters and might be confused with something else.
 ~~ WARNING at line 8: (NARROWING_CONVERSION) Narrowing conversion (float is converted to int and loses precision).
 ~~ WARNING at line 8: (NARROWING_CONVERSION) Narrowing conversion (float is converted to int and loses precision).
+~~ WARNING at line 14: (NARROWING_CONVERSION) Narrowing conversion (float is converted to int and loses precision).
 ~~ WARNING at line 19: (NARROWING_CONVERSION) Narrowing conversion (float is converted to int and loses precision).
 ~~ WARNING at line 19: (NARROWING_CONVERSION) Narrowing conversion (float is converted to int and loses precision).
 ~~ WARNING at line 24: (NARROWING_CONVERSION) Narrowing conversion (float is converted to int and loses precision).
 ~~ WARNING at line 24: (NARROWING_CONVERSION) Narrowing conversion (float is converted to int and loses precision).
 ~~ WARNING at line 27: (CONFUSABLE_IDENTIFIER) The identifier "_my_vАr" has misleading characters and might be confused with something else.
 ~~ WARNING at line 27: (CONFUSABLE_IDENTIFIER) The identifier "_my_vАr" has misleading characters and might be confused with something else.

+ 2 - 3
modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd

@@ -4,11 +4,10 @@ extends ShadowingBase
 var member: int = 0
 var member: int = 0
 
 
 var print_debug := 'print_debug'
 var print_debug := 'print_debug'
-@warning_ignore("shadowed_global_identifier")
+@warning_ignore('shadowed_global_identifier')
 var print := 'print'
 var print := 'print'
 
 
-@warning_ignore("unused_variable")
-@warning_ignore("unused_local_constant")
+@warning_ignore_start('unused_variable', 'unused_local_constant')
 func test():
 func test():
 	var Array := 'Array'
 	var Array := 'Array'
 	var Node := 'Node'
 	var Node := 'Node'

+ 10 - 10
modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out

@@ -1,13 +1,13 @@
 GDTEST_OK
 GDTEST_OK
 ~~ WARNING at line 6: (SHADOWED_GLOBAL_IDENTIFIER) The variable "print_debug" has the same name as a built-in function.
 ~~ WARNING at line 6: (SHADOWED_GLOBAL_IDENTIFIER) The variable "print_debug" has the same name as a built-in function.
-~~ WARNING at line 13: (SHADOWED_GLOBAL_IDENTIFIER) The variable "Array" has the same name as a built-in type.
-~~ WARNING at line 14: (SHADOWED_GLOBAL_IDENTIFIER) The variable "Node" has the same name as a native class.
-~~ WARNING at line 15: (SHADOWED_GLOBAL_IDENTIFIER) The variable "is_same" has the same name as a built-in function.
-~~ WARNING at line 16: (SHADOWED_GLOBAL_IDENTIFIER) The variable "sqrt" has the same name as a built-in function.
-~~ WARNING at line 17: (SHADOWED_VARIABLE) The local variable "member" is shadowing an already-declared variable at line 4 in the current class.
-~~ WARNING at line 18: (SHADOWED_VARIABLE_BASE_CLASS) The local variable "reference" is shadowing an already-declared method in the base class "RefCounted".
-~~ WARNING at line 19: (SHADOWED_GLOBAL_IDENTIFIER) The variable "ShadowedClass" has the same name as a global class defined in "shadowning.gd".
-~~ WARNING at line 20: (SHADOWED_VARIABLE_BASE_CLASS) The local variable "base_variable_member" is shadowing an already-declared variable at line 4 in the base class "ShadowingBase".
-~~ WARNING at line 21: (SHADOWED_VARIABLE_BASE_CLASS) The local constant "base_function_member" is shadowing an already-declared function at line 6 in the base class "ShadowingBase".
-~~ WARNING at line 22: (SHADOWED_VARIABLE_BASE_CLASS) The local variable "base_const_member" is shadowing an already-declared constant at line 3 in the base class "ShadowingBase".
+~~ WARNING at line 12: (SHADOWED_GLOBAL_IDENTIFIER) The variable "Array" has the same name as a built-in type.
+~~ WARNING at line 13: (SHADOWED_GLOBAL_IDENTIFIER) The variable "Node" has the same name as a native class.
+~~ WARNING at line 14: (SHADOWED_GLOBAL_IDENTIFIER) The variable "is_same" has the same name as a built-in function.
+~~ WARNING at line 15: (SHADOWED_GLOBAL_IDENTIFIER) The variable "sqrt" has the same name as a built-in function.
+~~ WARNING at line 16: (SHADOWED_VARIABLE) The local variable "member" is shadowing an already-declared variable at line 4 in the current class.
+~~ WARNING at line 17: (SHADOWED_VARIABLE_BASE_CLASS) The local variable "reference" is shadowing an already-declared method in the base class "RefCounted".
+~~ WARNING at line 18: (SHADOWED_GLOBAL_IDENTIFIER) The variable "ShadowedClass" has the same name as a global class defined in "shadowning.gd".
+~~ WARNING at line 19: (SHADOWED_VARIABLE_BASE_CLASS) The local variable "base_variable_member" is shadowing an already-declared variable at line 4 in the base class "ShadowingBase".
+~~ WARNING at line 20: (SHADOWED_VARIABLE_BASE_CLASS) The local constant "base_function_member" is shadowing an already-declared function at line 6 in the base class "ShadowingBase".
+~~ WARNING at line 21: (SHADOWED_VARIABLE_BASE_CLASS) The local variable "base_const_member" is shadowing an already-declared constant at line 3 in the base class "ShadowingBase".
 warn
 warn

+ 5 - 0
modules/gdscript/tests/scripts/parser/errors/warning_ignore_extra_start.gd

@@ -0,0 +1,5 @@
+@warning_ignore_start("unreachable_code")
+@warning_ignore_start("unreachable_code")
+
+func test():
+	pass

+ 2 - 0
modules/gdscript/tests/scripts/parser/errors/warning_ignore_extra_start.out

@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Warning "UNREACHABLE_CODE" is already being ignored by "@warning_ignore_start" at line 1.

+ 4 - 0
modules/gdscript/tests/scripts/parser/errors/warning_ignore_restore_without_start.gd

@@ -0,0 +1,4 @@
+@warning_ignore_restore("unreachable_code")
+
+func test():
+	pass

+ 2 - 0
modules/gdscript/tests/scripts/parser/errors/warning_ignore_restore_without_start.out

@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Warning "UNREACHABLE_CODE" is not being ignored by "@warning_ignore_start".

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

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

+ 2 - 7
modules/gdscript/tests/scripts/parser/features/signal_declaration.gd

@@ -1,3 +1,5 @@
+@warning_ignore_start("unused_signal")
+
 # No parentheses.
 # No parentheses.
 signal a
 signal a
 
 
@@ -17,12 +19,5 @@ signal d(
 # With type hints.
 # With type hints.
 signal e(a: int, b: Variant, c: Node)
 signal e(a: int, b: Variant, c: Node)
 
 
-func no_exec():
-	a.emit()
-	b.emit()
-	c.emit()
-	d.emit()
-	e.emit()
-
 func test():
 func test():
 	print("Ok")
 	print("Ok")

+ 26 - 0
modules/gdscript/tests/scripts/parser/features/warning_ignore_regions.gd

@@ -0,0 +1,26 @@
+@warning_ignore_start("unreachable_code", "narrowing_conversion")
+
+var _a = 1
+@warning_ignore_start("unused_private_class_variable")
+var _b = 2
+var _c = 3
+@warning_ignore_restore("unused_private_class_variable")
+var _d = 4
+
+func test():
+	return
+
+	var a = 1
+	@warning_ignore_start("unused_variable")
+	var b = 2
+	var c = 3
+	@warning_ignore_restore("unused_variable")
+	var d = 4
+
+	var _x: int = 1.0
+	@warning_ignore_restore("narrowing_conversion")
+	var _y: int = 1.0
+
+func test_2():
+	return
+	print(42)

+ 6 - 0
modules/gdscript/tests/scripts/parser/features/warning_ignore_regions.out

@@ -0,0 +1,6 @@
+GDTEST_OK
+~~ WARNING at line 3: (UNUSED_PRIVATE_CLASS_VARIABLE) The class variable "_a" is declared but never used in the class.
+~~ WARNING at line 8: (UNUSED_PRIVATE_CLASS_VARIABLE) The class variable "_d" is declared but never used in the class.
+~~ WARNING at line 13: (UNUSED_VARIABLE) The local variable "a" is declared but never used in the block. If this is intended, prefix it with an underscore: "_a".
+~~ WARNING at line 18: (UNUSED_VARIABLE) The local variable "d" is declared but never used in the block. If this is intended, prefix it with an underscore: "_d".
+~~ WARNING at line 22: (NARROWING_CONVERSION) Narrowing conversion (float is converted to int and loses precision).

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

@@ -1,6 +1,6 @@
 # https://github.com/godotengine/godot/issues/75832
 # https://github.com/godotengine/godot/issues/75832
 
 
-@warning_ignore("narrowing_conversion")
+@warning_ignore_start("narrowing_conversion")
 func test():
 func test():
 	var hf := 2.0
 	var hf := 2.0
 	var sf = 2.0
 	var sf = 2.0

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

@@ -56,6 +56,7 @@ func test_func_hard_int() -> int: return 1
 func test_func_args_1(_a: int, _b: Array[int], _c: Dictionary[int, int], _d: int = 1, _e = 2): pass
 func test_func_args_1(_a: int, _b: Array[int], _c: Dictionary[int, int], _d: int = 1, _e = 2): pass
 func test_func_args_2(_a = 1, _b = _a, _c = [2], _d = 3): pass
 func test_func_args_2(_a = 1, _b = _a, _c = [2], _d = 3): pass
 
 
+@warning_ignore_start("unused_signal")
 signal test_signal_1()
 signal test_signal_1()
 signal test_signal_2(a: Variant, b)
 signal test_signal_2(a: Variant, b)
 signal test_signal_3(a: int, b: Array[int], c: Dictionary[int, int])
 signal test_signal_3(a: int, b: Array[int], c: Dictionary[int, int])
@@ -64,16 +65,7 @@ signal test_signal_5(a: MyEnum, b: Array[MyEnum], c: Dictionary[MyEnum, MyEnum])
 signal test_signal_6(a: Resource, b: Array[Resource], c: Dictionary[Resource, Resource])
 signal test_signal_6(a: Resource, b: Array[Resource], c: Dictionary[Resource, Resource])
 signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo], c: Dictionary[TestMemberInfo, TestMemberInfo])
 signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo], c: Dictionary[TestMemberInfo, TestMemberInfo])
 signal test_signal_8(a: MyClass, b: Array[MyClass], c: Dictionary[MyClass, MyClass])
 signal test_signal_8(a: MyClass, b: Array[MyClass], c: Dictionary[MyClass, MyClass])
-
-func no_exec():
-	test_signal_1.emit()
-	test_signal_2.emit()
-	test_signal_3.emit()
-	test_signal_4.emit()
-	test_signal_5.emit()
-	test_signal_6.emit()
-	test_signal_7.emit()
-	test_signal_8.emit()
+@warning_ignore_restore("unused_signal")
 
 
 func test():
 func test():
 	var script: Script = get_script()
 	var script: Script = get_script()