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

GDScript: Add `@warning_ignore_start` and `@warning_ignore_restore` annotations
This commit is contained in:
Thaddeus Crews 2024-12-09 14:33:38 -06:00
commit a372214a4a
No known key found for this signature in database
GPG Key ID: 62181B86FE9E5D84
22 changed files with 221 additions and 89 deletions

View File

@ -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>

View File

@ -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);

View File

@ -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,10 +1909,22 @@ 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) {
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); annotation_stack.push_back(annotation);
} }
}
break; break;
} }
default: { default: {
@ -4096,24 +4121,26 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation)
return false; return false;
} }
// Some annotations need to be resolved in the parser. // Some annotations need to be resolved and applied in the parser.
if (p_annotation->name == SNAME("@icon")) { if (p_annotation->name == SNAME("@icon") || p_annotation->name == SNAME("@warning_ignore_start") || p_annotation->name == SNAME("@warning_ignore_restore")) {
ExpressionNode *argument = p_annotation->arguments[0]; for (int i = 0; i < p_annotation->arguments.size(); i++) {
ExpressionNode *argument = p_annotation->arguments[i];
if (argument->type != Node::LITERAL) { if (argument->type != Node::LITERAL) {
push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)", argument); push_error(vformat(R"(Argument %d of annotation "%s" must be a string literal.)", i + 1, p_annotation->name), argument);
return false; return false;
} }
Variant value = static_cast<LiteralNode *>(argument)->value; Variant value = static_cast<LiteralNode *>(argument)->value;
if (value.get_type() != Variant::STRING) { if (value.get_type() != Variant::STRING) {
push_error(R"(Argument 1 of annotation "@icon" must be a string literal.)", argument); push_error(vformat(R"(Argument %d of annotation "%s" must be a string literal.)", i + 1, p_annotation->name), argument);
return false; 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) { bool GDScriptParser::warning_ignore_annotation(AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) {
#ifndef DEBUG_ENABLED #ifdef DEBUG_ENABLED
// Only available in debug builds.
return true;
#else // 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. FunctionNode *function = static_cast<FunctionNode *>(p_target);
// It's better not to remove it for now, while there is no way to mass-ignore warnings. 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:

View File

@ -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);

View File

@ -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")

View File

@ -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")

View File

@ -20,9 +20,7 @@ class Members:
Utils.check(str(two) == '[486]') Utils.check(str(two) == '[486]')
return true return true
@warning_ignore_start('unsafe_method_access', 'return_value_discarded')
@warning_ignore("unsafe_method_access")
@warning_ignore("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)

View File

@ -21,9 +21,7 @@ class Members:
return true return true
@warning_ignore("unsafe_method_access") @warning_ignore_start("unsafe_method_access", "return_value_discarded")
@warning_ignore("assert_always_true")
@warning_ignore("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 }')

View File

@ -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')) ok.connect(func(): print("ok"))
emit_signal(&'ok') emit_signal(&"ok")

View File

@ -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.

View File

@ -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_start('unused_variable', 'unused_local_constant')
@warning_ignore("unused_local_constant")
func test(): func test():
var Array := 'Array' var Array := 'Array'
var Node := 'Node' var Node := 'Node'

View File

@ -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 12: (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 13: (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 14: (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 15: (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 16: (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 17: (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 18: (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 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 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 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 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 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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = {

View File

@ -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")

View File

@ -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)

View File

@ -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).

View File

@ -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

View File

@ -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])
@warning_ignore_restore("unused_signal")
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()
func test(): func test():
var script: Script = get_script() var script: Script = get_script()