Skip to content

Commit a372214

Browse files
committed
Merge pull request #76020 from dalexeev/gds-warning-ignore-regions
GDScript: Add `@warning_ignore_start` and `@warning_ignore_restore` annotations
2 parents b4bd384 + 7d65d0a commit a372214

22 files changed

+219
-87
lines changed

modules/gdscript/doc_classes/@GDScript.xml

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,7 @@
726726
[/codeblock]
727727
[b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported.
728728
[b]Note:[/b] As annotations describe their subject, the [annotation @icon] annotation must be placed before the class definition and inheritance.
729-
[b]Note:[/b] Unlike other annotations, the argument of the [annotation @icon] annotation must be a string literal (constant expressions are not supported).
729+
[b]Note:[/b] Unlike most other annotations, the argument of the [annotation @icon] annotation must be a string literal (constant expressions are not supported).
730730
</description>
731731
</annotation>
732732
<annotation name="@onready">
@@ -794,6 +794,33 @@
794794
@warning_ignore("unreachable_code")
795795
print("unreachable")
796796
[/codeblock]
797+
See also [annotation @warning_ignore_start] and [annotation @warning_ignore_restore].
798+
</description>
799+
</annotation>
800+
<annotation name="@warning_ignore_restore" qualifiers="vararg">
801+
<return type="void" />
802+
<param index="0" name="warning" type="String" />
803+
<description>
804+
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.
805+
[b]Note:[/b] Unlike most other annotations, arguments of the [annotation @warning_ignore_restore] annotation must be string literals (constant expressions are not supported).
806+
</description>
807+
</annotation>
808+
<annotation name="@warning_ignore_start" qualifiers="vararg">
809+
<return type="void" />
810+
<param index="0" name="warning" type="String" />
811+
<description>
812+
Starts ignoring the listed warning types until the end of the file or the [annotation @warning_ignore_restore] annotation with the given warning type.
813+
[codeblock]
814+
func test():
815+
var a = 1 # Warning (if enabled in the Project Settings).
816+
@warning_ignore_start("unused_variable")
817+
var b = 2 # No warning.
818+
var c = 3 # No warning.
819+
@warning_ignore_restore("unused_variable")
820+
var d = 4 # Warning (if enabled in the Project Settings).
821+
[/codeblock]
822+
[b]Note:[/b] To suppress a single warning, use [annotation @warning_ignore] instead.
823+
[b]Note:[/b] Unlike most other annotations, arguments of the [annotation @warning_ignore_start] annotation must be string literals (constant expressions are not supported).
797824
</description>
798825
</annotation>
799826
</annotations>

modules/gdscript/gdscript_editor.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -962,8 +962,11 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a
962962
}
963963
} break;
964964
}
965-
} else if (p_annotation->name == SNAME("@warning_ignore")) {
965+
} else if (p_annotation->name == SNAME("@warning_ignore") || p_annotation->name == SNAME("@warning_ignore_start") || p_annotation->name == SNAME("@warning_ignore_restore")) {
966966
for (int warning_code = 0; warning_code < GDScriptWarning::WARNING_MAX; warning_code++) {
967+
if (warning_code == GDScriptWarning::RENAMED_IN_GODOT_4_HINT) {
968+
continue;
969+
}
967970
ScriptLanguage::CodeCompletionOption warning(GDScriptWarning::get_name_from_code((GDScriptWarning::Code)warning_code).to_lower(), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
968971
warning.insert_text = warning.display.quote(p_quote_style);
969972
r_result.insert(warning.display, warning);

modules/gdscript/gdscript_parser.cpp

Lines changed: 110 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,11 @@ bool GDScriptParser::annotation_exists(const String &p_annotation_name) const {
9494
GDScriptParser::GDScriptParser() {
9595
// Register valid annotations.
9696
if (unlikely(valid_annotations.is_empty())) {
97+
// Script annotations.
9798
register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation);
9899
register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation);
99100
register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation);
100-
101+
// Onready annotation.
101102
register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
102103
// Export annotations.
103104
register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>);
@@ -128,13 +129,18 @@ GDScriptParser::GDScriptParser() {
128129
register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray(""));
129130
register_annotation(MethodInfo("@export_subgroup", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_SUBGROUP>, varray(""));
130131
// Warning annotations.
131-
register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS_LEVEL | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true);
132+
register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS_LEVEL | AnnotationInfo::STATEMENT, &GDScriptParser::warning_ignore_annotation, varray(), true);
133+
register_annotation(MethodInfo("@warning_ignore_start", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::STANDALONE, &GDScriptParser::warning_ignore_region_annotations, varray(), true);
134+
register_annotation(MethodInfo("@warning_ignore_restore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::STANDALONE, &GDScriptParser::warning_ignore_region_annotations, varray(), true);
132135
// Networking.
133136
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));
134137
}
135138

136139
#ifdef DEBUG_ENABLED
137140
is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable");
141+
for (int i = 0; i < GDScriptWarning::WARNING_MAX; i++) {
142+
warning_ignore_start_lines[i] = INT_MAX;
143+
}
138144
#endif
139145

140146
#ifdef TOOLS_ENABLED
@@ -214,6 +220,9 @@ void GDScriptParser::apply_pending_warnings() {
214220
if (warning_ignored_lines[pw.code].has(pw.source->start_line)) {
215221
continue;
216222
}
223+
if (warning_ignore_start_lines[pw.code] <= pw.source->start_line) {
224+
continue;
225+
}
217226

218227
GDScriptWarning warning;
219228
warning.code = pw.code;
@@ -625,7 +634,7 @@ void GDScriptParser::parse_program() {
625634
} else if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
626635
PUSH_PENDING_ANNOTATIONS_TO_HEAD;
627636
if (annotation->name == SNAME("@tool") || annotation->name == SNAME("@icon")) {
628-
// Some annotations need to be resolved in the parser.
637+
// Some annotations need to be resolved and applied in the parser.
629638
annotation->apply(this, head, nullptr); // `head->outer == nullptr`.
630639
} else {
631640
head->annotations.push_back(annotation);
@@ -640,8 +649,10 @@ void GDScriptParser::parse_program() {
640649
// so we stop looking for script-level stuff.
641650
can_have_class_or_extends = false;
642651
break;
652+
} else if (annotation->name == SNAME("@warning_ignore_start") || annotation->name == SNAME("@warning_ignore_restore")) {
653+
// Some annotations need to be resolved and applied in the parser.
654+
annotation->apply(this, nullptr, nullptr);
643655
} else {
644-
// For potential non-group standalone annotations.
645656
push_error(R"(Unexpected standalone annotation.)");
646657
}
647658
} else {
@@ -1030,8 +1041,10 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
10301041
}
10311042
if (annotation->name == SNAME("@export_category") || annotation->name == SNAME("@export_group") || annotation->name == SNAME("@export_subgroup")) {
10321043
current_class->add_member_group(annotation);
1044+
} else if (annotation->name == SNAME("@warning_ignore_start") || annotation->name == SNAME("@warning_ignore_restore")) {
1045+
// Some annotations need to be resolved and applied in the parser.
1046+
annotation->apply(this, nullptr, nullptr);
10331047
} else {
1034-
// For potential non-group standalone annotations.
10351048
push_error(R"(Unexpected standalone annotation.)");
10361049
}
10371050
} else { // `AnnotationInfo::CLASS_LEVEL`.
@@ -1896,9 +1909,21 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
18961909
break;
18971910
case GDScriptTokenizer::Token::ANNOTATION: {
18981911
advance();
1899-
AnnotationNode *annotation = parse_annotation(AnnotationInfo::STATEMENT);
1912+
AnnotationNode *annotation = parse_annotation(AnnotationInfo::STATEMENT | AnnotationInfo::STANDALONE);
19001913
if (annotation != nullptr) {
1901-
annotation_stack.push_back(annotation);
1914+
if (annotation->applies_to(AnnotationInfo::STANDALONE)) {
1915+
if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
1916+
push_error(R"(Expected newline after a standalone annotation.)");
1917+
}
1918+
if (annotation->name == SNAME("@warning_ignore_start") || annotation->name == SNAME("@warning_ignore_restore")) {
1919+
// Some annotations need to be resolved and applied in the parser.
1920+
annotation->apply(this, nullptr, nullptr);
1921+
} else {
1922+
push_error(R"(Unexpected standalone annotation.)");
1923+
}
1924+
} else {
1925+
annotation_stack.push_back(annotation);
1926+
}
19021927
}
19031928
break;
19041929
}
@@ -4096,23 +4121,25 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation)
40964121
return false;
40974122
}
40984123

4099-
// Some annotations need to be resolved in the parser.
4100-
if (p_annotation->name == SNAME("@icon")) {
4101-
ExpressionNode *argument = p_annotation->arguments[0];
4124+
// Some annotations need to be resolved and applied in the parser.
4125+
if (p_annotation->name == SNAME("@icon") || p_annotation->name == SNAME("@warning_ignore_start") || p_annotation->name == SNAME("@warning_ignore_restore")) {
4126+
for (int i = 0; i < p_annotation->arguments.size(); i++) {
4127+
ExpressionNode *argument = p_annotation->arguments[i];
41024128

4103-
if (argument->type != Node::LITERAL) {
4104-
push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)", argument);
4105-
return false;
4106-
}
4129+
if (argument->type != Node::LITERAL) {
4130+
push_error(vformat(R"(Argument %d of annotation "%s" must be a string literal.)", i + 1, p_annotation->name), argument);
4131+
return false;
4132+
}
41074133

4108-
Variant value = static_cast<LiteralNode *>(argument)->value;
4134+
Variant value = static_cast<LiteralNode *>(argument)->value;
41094135

4110-
if (value.get_type() != Variant::STRING) {
4111-
push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)", argument);
4112-
return false;
4113-
}
4136+
if (value.get_type() != Variant::STRING) {
4137+
push_error(vformat(R"(Argument %d of annotation "%s" must be a string literal.)", i + 1, p_annotation->name), argument);
4138+
return false;
4139+
}
41144140

4115-
p_annotation->resolved_arguments.push_back(value);
4141+
p_annotation->resolved_arguments.push_back(value);
4142+
}
41164143
}
41174144

41184145
// For other annotations, see `GDScriptAnalyzer::resolve_annotation()`.
@@ -4162,6 +4189,17 @@ bool GDScriptParser::icon_annotation(AnnotationNode *p_annotation, Node *p_targe
41624189
return true;
41634190
}
41644191

4192+
bool GDScriptParser::static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
4193+
ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, vformat(R"("%s" annotation can only be applied to classes.)", p_annotation->name));
4194+
ClassNode *class_node = static_cast<ClassNode *>(p_target);
4195+
if (class_node->annotated_static_unload) {
4196+
push_error(vformat(R"("%s" annotation can only be used once per script.)", p_annotation->name), p_annotation);
4197+
return false;
4198+
}
4199+
class_node->annotated_static_unload = true;
4200+
return true;
4201+
}
4202+
41654203
bool GDScriptParser::onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
41664204
ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)");
41674205

@@ -4756,11 +4794,8 @@ bool GDScriptParser::export_group_annotations(AnnotationNode *p_annotation, Node
47564794
return true;
47574795
}
47584796

4759-
bool GDScriptParser::warning_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
4760-
#ifndef DEBUG_ENABLED
4761-
// Only available in debug builds.
4762-
return true;
4763-
#else // DEBUG_ENABLED
4797+
bool GDScriptParser::warning_ignore_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
4798+
#ifdef DEBUG_ENABLED
47644799
if (is_ignoring_warnings) {
47654800
return true; // We already ignore all warnings, let's optimize it.
47664801
}
@@ -4805,8 +4840,14 @@ bool GDScriptParser::warning_annotations(AnnotationNode *p_annotation, Node *p_t
48054840
} break;
48064841

48074842
case Node::FUNCTION: {
4808-
// `@warning_ignore` on function has a controversial feature that is used in tests.
4809-
// It's better not to remove it for now, while there is no way to mass-ignore warnings.
4843+
FunctionNode *function = static_cast<FunctionNode *>(p_target);
4844+
end_line = function->start_line;
4845+
for (int i = 0; i < function->parameters.size(); i++) {
4846+
end_line = MAX(end_line, function->parameters[i]->end_line);
4847+
if (function->parameters[i]->initializer != nullptr) {
4848+
end_line = MAX(end_line, function->parameters[i]->initializer->end_line);
4849+
}
4850+
}
48104851
} break;
48114852

48124853
case Node::MATCH_BRANCH: {
@@ -4828,6 +4869,48 @@ bool GDScriptParser::warning_annotations(AnnotationNode *p_annotation, Node *p_t
48284869
}
48294870
}
48304871
return !has_error;
4872+
#else // !DEBUG_ENABLED
4873+
// Only available in debug builds.
4874+
return true;
4875+
#endif // DEBUG_ENABLED
4876+
}
4877+
4878+
bool GDScriptParser::warning_ignore_region_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
4879+
#ifdef DEBUG_ENABLED
4880+
bool has_error = false;
4881+
const bool is_start = p_annotation->name == SNAME("@warning_ignore_start");
4882+
for (const Variant &warning_name : p_annotation->resolved_arguments) {
4883+
GDScriptWarning::Code warning_code = GDScriptWarning::get_code_from_name(String(warning_name).to_upper());
4884+
if (warning_code == GDScriptWarning::WARNING_MAX) {
4885+
push_error(vformat(R"(Invalid warning name: "%s".)", warning_name), p_annotation);
4886+
has_error = true;
4887+
continue;
4888+
}
4889+
if (is_start) {
4890+
if (warning_ignore_start_lines[warning_code] != INT_MAX) {
4891+
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);
4892+
has_error = true;
4893+
continue;
4894+
}
4895+
warning_ignore_start_lines[warning_code] = p_annotation->start_line;
4896+
} else {
4897+
if (warning_ignore_start_lines[warning_code] == INT_MAX) {
4898+
push_error(vformat(R"(Warning "%s" is not being ignored by "@warning_ignore_start".)", String(warning_name).to_upper()), p_annotation);
4899+
has_error = true;
4900+
continue;
4901+
}
4902+
const int start_line = warning_ignore_start_lines[warning_code];
4903+
const int end_line = MAX(start_line, p_annotation->start_line); // Prevent infinite loop.
4904+
for (int i = start_line; i <= end_line; i++) {
4905+
warning_ignored_lines[warning_code].insert(i);
4906+
}
4907+
warning_ignore_start_lines[warning_code] = INT_MAX;
4908+
}
4909+
}
4910+
return !has_error;
4911+
#else // !DEBUG_ENABLED
4912+
// Only available in debug builds.
4913+
return true;
48314914
#endif // DEBUG_ENABLED
48324915
}
48334916

@@ -4892,17 +4975,6 @@ bool GDScriptParser::rpc_annotation(AnnotationNode *p_annotation, Node *p_target
48924975
return true;
48934976
}
48944977

4895-
bool GDScriptParser::static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
4896-
ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, vformat(R"("%s" annotation can only be applied to classes.)", p_annotation->name));
4897-
ClassNode *class_node = static_cast<ClassNode *>(p_target);
4898-
if (class_node->annotated_static_unload) {
4899-
push_error(vformat(R"("%s" annotation can only be used once per script.)", p_annotation->name), p_annotation);
4900-
return false;
4901-
}
4902-
class_node->annotated_static_unload = true;
4903-
return true;
4904-
}
4905-
49064978
GDScriptParser::DataType GDScriptParser::SuiteNode::Local::get_datatype() const {
49074979
switch (type) {
49084980
case CONSTANT:

modules/gdscript/gdscript_parser.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1358,6 +1358,7 @@ class GDScriptParser {
13581358
List<GDScriptWarning> warnings;
13591359
List<PendingWarning> pending_warnings;
13601360
HashSet<int> warning_ignored_lines[GDScriptWarning::WARNING_MAX];
1361+
int warning_ignore_start_lines[GDScriptWarning::WARNING_MAX];
13611362
HashSet<int> unsafe_lines;
13621363
#endif
13631364

@@ -1506,6 +1507,7 @@ class GDScriptParser {
15061507
void clear_unused_annotations();
15071508
bool tool_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
15081509
bool icon_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
1510+
bool static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
15091511
bool onready_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
15101512
template <PropertyHint t_hint, Variant::Type t_type>
15111513
bool export_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
@@ -1514,9 +1516,9 @@ class GDScriptParser {
15141516
bool export_tool_button_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
15151517
template <PropertyUsageFlags t_usage>
15161518
bool export_group_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
1517-
bool warning_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
1519+
bool warning_ignore_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
1520+
bool warning_ignore_region_annotations(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
15181521
bool rpc_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
1519-
bool static_unload_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class);
15201522
// Statements.
15211523
Node *parse_statement();
15221524
VariableNode *parse_variable(bool p_is_static);

modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ func param_inferred(param := variant()) -> void: print(param)
1313
func return_untyped(): return variant()
1414
func return_typed() -> Variant: return variant()
1515

16-
@warning_ignore("unused_variable", "inference_on_variant")
16+
@warning_ignore_start("unused_variable", "inference_on_variant")
1717
func test() -> void:
1818
var weak = variant()
1919
var typed: Variant = variant()
@@ -32,4 +32,4 @@ func test() -> void:
3232
if typed != null: pass
3333
if typed is Node: pass
3434

35-
print('ok')
35+
print("ok")

modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,4 @@ func test():
123123
Utils.check((const_null is A) == false)
124124
Utils.check(is_instance_of(const_null, A) == false)
125125

126-
print('ok')
126+
print("ok")

modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ class Members:
2020
Utils.check(str(two) == '[486]')
2121
return true
2222

23-
24-
@warning_ignore("unsafe_method_access")
25-
@warning_ignore("return_value_discarded")
23+
@warning_ignore_start('unsafe_method_access', 'return_value_discarded')
2624
func test():
2725
var untyped_basic = [459]
2826
Utils.check(str(untyped_basic) == '[459]')
@@ -207,7 +205,7 @@ func test():
207205

208206
var a := A.new()
209207
var typed_natives: Array[RefCounted] = [a]
210-
var typed_scripts = Array(typed_natives, TYPE_OBJECT, "RefCounted", A)
208+
var typed_scripts = Array(typed_natives, TYPE_OBJECT, 'RefCounted', A)
211209
Utils.check(typed_scripts[0] == a)
212210

213211

modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ class Members:
2121
return true
2222

2323

24-
@warning_ignore("unsafe_method_access")
25-
@warning_ignore("assert_always_true")
26-
@warning_ignore("return_value_discarded")
24+
@warning_ignore_start("unsafe_method_access", "return_value_discarded")
2725
func test():
2826
var untyped_basic = { 459: 954 }
2927
Utils.check(str(untyped_basic) == '{ 459: 954 }')

0 commit comments

Comments
 (0)