Fix analyzer pushing SHADOWED_VARIABLE warning for members shadowed in subclasses

This fixes a bug in the analyzer where it did not push the SHADOWED_VARIABLE_BASE_CLASS
warning for members shadowed by variable in subclass. It does this by comparing the class
which contains the shadowed member with the class containing the variable, and pushing
SHADOWED_VARIABLE only if the classes are the same. Additionally, SHADOWED_VARIABLE_BASE_CLASS
can take an extra symbol which helps to specify the line for non native base class.
This commit is contained in:
girdenis-p 2024-11-06 00:37:18 +00:00
parent 87318a2fb7
commit 413490c270
15 changed files with 86 additions and 45 deletions

View File

@ -561,10 +561,10 @@
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when defining a local or member variable, signal, or enum that would have the same name as a built-in function or global class name, thus shadowing it. When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when defining a local or member variable, signal, or enum that would have the same name as a built-in function or global class name, thus shadowing it.
</member> </member>
<member name="debug/gdscript/warnings/shadowed_variable" type="int" setter="" getter="" default="1"> <member name="debug/gdscript/warnings/shadowed_variable" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when defining a local or member variable that would shadow a member variable that the class defines. When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a local variable or local constant shadows a member declared in the current class.
</member> </member>
<member name="debug/gdscript/warnings/shadowed_variable_base_class" type="int" setter="" getter="" default="1"> <member name="debug/gdscript/warnings/shadowed_variable_base_class" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when defining a local or subclass member variable that would shadow a variable that is inherited from a parent class. When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a local variable or local constant shadows a member declared in a base class.
</member> </member>
<member name="debug/gdscript/warnings/standalone_expression" type="int" setter="" getter="" default="1"> <member name="debug/gdscript/warnings/standalone_expression" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when calling an expression that may have no effect on the surrounding code, such as writing [code]2 + 2[/code] as a statement. When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when calling an expression that may have no effect on the surrounding code, such as writing [code]2 + 2[/code] as a statement.

View File

@ -5809,8 +5809,6 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
#ifdef DEBUG_ENABLED #ifdef DEBUG_ENABLED
void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope) { void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope) {
const StringName &name = p_identifier->name; const StringName &name = p_identifier->name;
GDScriptParser::DataType base = parser->current_class->get_datatype();
GDScriptParser::ClassNode *base_class = base.class_type;
{ {
List<MethodInfo> gdscript_funcs; List<MethodInfo> gdscript_funcs;
@ -5838,37 +5836,53 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier
} }
} }
const GDScriptParser::DataType current_class_type = parser->current_class->get_datatype();
if (p_in_local_scope) { if (p_in_local_scope) {
while (base_class != nullptr) { GDScriptParser::ClassNode *base_class = current_class_type.class_type;
if (base_class != nullptr) {
if (base_class->has_member(name)) { if (base_class->has_member(name)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE, p_context, p_identifier->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line())); parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE, p_context, p_identifier->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line()));
return; return;
} }
base_class = base_class->base_type.class_type; base_class = base_class->base_type.class_type;
} }
while (base_class != nullptr) {
if (base_class->has_member(name)) {
String base_class_name = base_class->get_global_name();
if (base_class_name.is_empty()) {
base_class_name = base_class->fqcn;
}
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, base_class->get_member(name).get_type_name(), itos(base_class->get_member(name).get_line()), base_class_name);
return;
}
base_class = base_class->base_type.class_type;
}
} }
StringName parent = base.native_type; StringName native_base_class = current_class_type.native_type;
while (parent != StringName()) { while (native_base_class != StringName()) {
ERR_FAIL_COND_MSG(!class_exists(parent), "Non-existent native base class."); ERR_FAIL_COND_MSG(!class_exists(native_base_class), "Non-existent native base class.");
if (ClassDB::has_method(parent, name, true)) { if (ClassDB::has_method(native_base_class, name, true)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "method", parent); parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "method", native_base_class);
return; return;
} else if (ClassDB::has_signal(parent, name, true)) { } else if (ClassDB::has_signal(native_base_class, name, true)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "signal", parent); parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "signal", native_base_class);
return; return;
} else if (ClassDB::has_property(parent, name, true)) { } else if (ClassDB::has_property(native_base_class, name, true)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "property", parent); parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "property", native_base_class);
return; return;
} else if (ClassDB::has_integer_constant(parent, name, true)) { } else if (ClassDB::has_integer_constant(native_base_class, name, true)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "constant", parent); parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "constant", native_base_class);
return; return;
} else if (ClassDB::has_enum(parent, name, true)) { } else if (ClassDB::has_enum(native_base_class, name, true)) {
parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "enum", parent); parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_identifier->name, "enum", native_base_class);
return; return;
} }
parent = ClassDB::get_parent_class(parent); native_base_class = ClassDB::get_parent_class(native_base_class);
} }
} }
#endif // DEBUG_ENABLED #endif // DEBUG_ENABLED

View File

@ -61,10 +61,13 @@ String GDScriptWarning::get_message() const {
return vformat(R"(The signal "%s" is declared but never explicitly used in the class.)", symbols[0]); return vformat(R"(The signal "%s" is declared but never explicitly used in the class.)", symbols[0]);
case SHADOWED_VARIABLE: case SHADOWED_VARIABLE:
CHECK_SYMBOLS(4); CHECK_SYMBOLS(4);
return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s.)", symbols[0], symbols[1], symbols[2], symbols[3]); return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s in the current class.)", symbols[0], symbols[1], symbols[2], symbols[3]);
case SHADOWED_VARIABLE_BASE_CLASS: case SHADOWED_VARIABLE_BASE_CLASS:
CHECK_SYMBOLS(4); CHECK_SYMBOLS(4);
return vformat(R"(The local %s "%s" is shadowing an already-declared %s at the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3]); if (symbols.size() > 4) {
return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s in the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3], symbols[4]);
}
return vformat(R"(The local %s "%s" is shadowing an already-declared %s in the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3]);
case SHADOWED_GLOBAL_IDENTIFIER: case SHADOWED_GLOBAL_IDENTIFIER:
CHECK_SYMBOLS(3); CHECK_SYMBOLS(3);
return vformat(R"(The %s "%s" has the same name as a %s.)", symbols[0], symbols[1], symbols[2]); return vformat(R"(The %s "%s" has the same name as a %s.)", symbols[0], symbols[1], symbols[2]);

View File

@ -53,8 +53,8 @@ public:
UNUSED_PRIVATE_CLASS_VARIABLE, // Class variable is declared private ("_" prefix) but never used in the class. UNUSED_PRIVATE_CLASS_VARIABLE, // Class variable is declared private ("_" prefix) but never used in the class.
UNUSED_PARAMETER, // Function parameter is never used. UNUSED_PARAMETER, // Function parameter is never used.
UNUSED_SIGNAL, // Signal is defined but never explicitly used in the class. UNUSED_SIGNAL, // Signal is defined but never explicitly used in the class.
SHADOWED_VARIABLE, // Variable name shadowed by other variable in same class. SHADOWED_VARIABLE, // A local variable/constant shadows a current class member.
SHADOWED_VARIABLE_BASE_CLASS, // Variable name shadowed by other variable in some base class. SHADOWED_VARIABLE_BASE_CLASS, // A local variable/constant shadows a base class member.
SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable. SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable.
UNREACHABLE_CODE, // Code after a return statement. UNREACHABLE_CODE, // Code after a return statement.
UNREACHABLE_PATTERN, // Pattern in a match statement after a catch all pattern (wildcard or bind). UNREACHABLE_PATTERN, // Pattern in a match statement after a catch all pattern (wildcard or bind).

View File

@ -6,6 +6,6 @@ GDTEST_OK
>> WARNING >> WARNING
>> Line: 5 >> Line: 5
>> SHADOWED_VARIABLE >> SHADOWED_VARIABLE
>> The local variable "a" is shadowing an already-declared variable at line 1. >> The local variable "a" is shadowing an already-declared variable at line 1 in the current class.
1 1
2 2

View File

@ -10,6 +10,6 @@ GDTEST_OK
>> WARNING >> WARNING
>> Line: 5 >> Line: 5
>> SHADOWED_VARIABLE >> SHADOWED_VARIABLE
>> The local variable "a" is shadowing an already-declared variable at line 1. >> The local variable "a" is shadowing an already-declared variable at line 1 in the current class.
1 1
2 2

View File

@ -6,7 +6,7 @@ GDTEST_OK
>> WARNING >> WARNING
>> Line: 6 >> Line: 6
>> SHADOWED_VARIABLE >> SHADOWED_VARIABLE
>> The local variable "a" is shadowing an already-declared variable at line 1. >> The local variable "a" is shadowing an already-declared variable at line 1 in the current class.
1 1
2 2
1 1

View File

@ -2,5 +2,5 @@ GDTEST_OK
>> WARNING >> WARNING
>> Line: 4 >> Line: 4
>> SHADOWED_VARIABLE >> SHADOWED_VARIABLE
>> The local function parameter "shadow" is shadowing an already-declared variable at line 1. >> The local function parameter "shadow" is shadowing an already-declared variable at line 1 in the current class.
shadow shadow

View File

@ -0,0 +1,7 @@
class_name ShadowingBase
const base_const_member = 1
var base_variable_member
func base_function_member():
pass

View File

@ -1,4 +1,5 @@
class_name ShadowedClass class_name ShadowedClass
extends ShadowingBase
var member: int = 0 var member: int = 0
@ -7,6 +8,7 @@ var print_debug := 'print_debug'
var print := 'print' var print := 'print'
@warning_ignore("unused_variable") @warning_ignore("unused_variable")
@warning_ignore("unused_local_constant")
func test(): func test():
var Array := 'Array' var Array := 'Array'
var Node := 'Node' var Node := 'Node'
@ -15,5 +17,8 @@ func test():
var member := 'member' var member := 'member'
var reference := 'reference' var reference := 'reference'
var ShadowedClass := 'ShadowedClass' var ShadowedClass := 'ShadowedClass'
var base_variable_member
const base_function_member = 1
var base_const_member
print('warn') print('warn')

View File

@ -1,34 +1,46 @@
GDTEST_OK GDTEST_OK
>> WARNING >> WARNING
>> Line: 5 >> Line: 6
>> SHADOWED_GLOBAL_IDENTIFIER >> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "print_debug" has the same name as a built-in function. >> The variable "print_debug" has the same name as a built-in function.
>> WARNING >> WARNING
>> Line: 11 >> Line: 13
>> SHADOWED_GLOBAL_IDENTIFIER >> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "Array" has the same name as a built-in type. >> The variable "Array" has the same name as a built-in type.
>> WARNING >> WARNING
>> Line: 12 >> Line: 14
>> SHADOWED_GLOBAL_IDENTIFIER >> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "Node" has the same name as a native class. >> The variable "Node" has the same name as a native class.
>> WARNING >> WARNING
>> Line: 13 >> Line: 15
>> SHADOWED_GLOBAL_IDENTIFIER >> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "is_same" has the same name as a built-in function. >> The variable "is_same" has the same name as a built-in function.
>> WARNING >> WARNING
>> Line: 14 >> Line: 16
>> SHADOWED_GLOBAL_IDENTIFIER >> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "sqrt" has the same name as a built-in function. >> The variable "sqrt" has the same name as a built-in function.
>> WARNING >> WARNING
>> Line: 15
>> SHADOWED_VARIABLE
>> The local variable "member" is shadowing an already-declared variable at line 3.
>> WARNING
>> Line: 16
>> SHADOWED_VARIABLE_BASE_CLASS
>> The local variable "reference" is shadowing an already-declared method at the base class "RefCounted".
>> WARNING
>> Line: 17 >> Line: 17
>> SHADOWED_VARIABLE
>> The local variable "member" is shadowing an already-declared variable at line 4 in the current class.
>> WARNING
>> Line: 18
>> SHADOWED_VARIABLE_BASE_CLASS
>> The local variable "reference" is shadowing an already-declared method in the base class "RefCounted".
>> WARNING
>> Line: 19
>> SHADOWED_GLOBAL_IDENTIFIER >> SHADOWED_GLOBAL_IDENTIFIER
>> The variable "ShadowedClass" has the same name as a global class defined in "shadowning.gd". >> The variable "ShadowedClass" has the same name as a global class defined in "shadowning.gd".
>> WARNING
>> 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
>> 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
>> 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".
warn warn

View File

@ -6,4 +6,4 @@ GDTEST_OK
>> WARNING >> WARNING
>> Line: 8 >> Line: 8
>> SHADOWED_VARIABLE >> SHADOWED_VARIABLE
>> The local constant "TEST" is shadowing an already-declared constant at line 2. >> The local constant "TEST" is shadowing an already-declared constant at line 2 in the current class.

View File

@ -6,4 +6,4 @@ GDTEST_OK
>> WARNING >> WARNING
>> Line: 8 >> Line: 8
>> SHADOWED_VARIABLE >> SHADOWED_VARIABLE
>> The local variable "foo" is shadowing an already-declared variable at line 1. >> The local variable "foo" is shadowing an already-declared variable at line 1 in the current class.

View File

@ -6,4 +6,4 @@ GDTEST_OK
>> WARNING >> WARNING
>> Line: 2 >> Line: 2
>> SHADOWED_VARIABLE >> SHADOWED_VARIABLE
>> The local variable "test" is shadowing an already-declared function at line 1. >> The local variable "test" is shadowing an already-declared function at line 1 in the current class.

View File

@ -2,11 +2,11 @@ GDTEST_OK
>> WARNING >> WARNING
>> Line: 5 >> Line: 5
>> SHADOWED_VARIABLE >> SHADOWED_VARIABLE
>> The local function parameter "a" is shadowing an already-declared variable at line 3. >> The local function parameter "a" is shadowing an already-declared variable at line 3 in the current class.
>> WARNING >> WARNING
>> Line: 15 >> Line: 15
>> SHADOWED_VARIABLE >> SHADOWED_VARIABLE
>> The local function parameter "v" is shadowing an already-declared variable at line 13. >> The local function parameter "v" is shadowing an already-declared variable at line 13 in the current class.
a a
1 1
b b