GDScript: Don't warn on unassigned for builtin-typed variables

If the type of a variable is a built-in Variant type, then it will
automatically be assigned a default value based on the type. This means
that the explicit initialization may be unnecessary. Thus this commit
removes the warning in such case.

This also changes the meaning of the unassigned warning to happen when
the variable is used before being assigned, not when it has zero
assignments.
This commit is contained in:
George Marques 2024-04-09 14:15:51 -03:00
parent a7b860250f
commit 877802e252
No known key found for this signature in database
GPG Key ID: 046BD46A3201E43D
10 changed files with 65 additions and 30 deletions

View File

@ -1973,8 +1973,6 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable
if (p_is_local) {
if (p_variable->usages == 0 && !String(p_variable->identifier->name).begins_with("_")) {
parser->push_warning(p_variable, GDScriptWarning::UNUSED_VARIABLE, p_variable->identifier->name);
} else if (p_variable->assignments == 0) {
parser->push_warning(p_variable, GDScriptWarning::UNASSIGNED_VARIABLE, p_variable->identifier->name);
}
}
is_shadowing(p_variable->identifier, kind, p_is_local);
@ -2615,9 +2613,21 @@ void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNo
}
void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) {
reduce_expression(p_assignment->assignee);
reduce_expression(p_assignment->assigned_value);
#ifdef DEBUG_ENABLED
// Increment assignment count for local variables.
// Before we reduce the assignee because we don't want to warn about not being assigned when performing the assignment.
if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) {
GDScriptParser::IdentifierNode *id = static_cast<GDScriptParser::IdentifierNode *>(p_assignment->assignee);
if (id->source == GDScriptParser::IdentifierNode::LOCAL_VARIABLE && id->variable_source) {
id->variable_source->assignments++;
}
}
#endif
reduce_expression(p_assignment->assignee);
if (p_assignment->assigned_value == nullptr || p_assignment->assignee == nullptr) {
return;
}
@ -2754,6 +2764,14 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
if (assignee_type.is_hard_type() && assignee_type.builtin_type == Variant::INT && assigned_value_type.builtin_type == Variant::FLOAT) {
parser->push_warning(p_assignment->assigned_value, GDScriptWarning::NARROWING_CONVERSION);
}
// Check for assignment with operation before assignment.
if (p_assignment->operation != GDScriptParser::AssignmentNode::OP_NONE && p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) {
GDScriptParser::IdentifierNode *id = static_cast<GDScriptParser::IdentifierNode *>(p_assignment->assignee);
// Use == 1 here because this assignment was already counted in the beginning of the function.
if (id->source == GDScriptParser::IdentifierNode::LOCAL_VARIABLE && id->variable_source && id->variable_source->assignments == 1) {
parser->push_warning(p_assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, id->name);
}
}
#endif
}
@ -3926,6 +3944,11 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
case GDScriptParser::IdentifierNode::LOCAL_VARIABLE:
p_identifier->set_datatype(p_identifier->variable_source->get_datatype());
found_source = true;
#ifdef DEBUG_ENABLED
if (p_identifier->variable_source && p_identifier->variable_source->assignments == 0 && !(p_identifier->get_datatype().is_hard_type() && p_identifier->get_datatype().kind == GDScriptParser::DataType::BUILTIN)) {
parser->push_warning(p_identifier, GDScriptWarning::UNASSIGNED_VARIABLE, p_identifier->name);
}
#endif
break;
case GDScriptParser::IdentifierNode::LOCAL_ITERATOR:
p_identifier->set_datatype(p_identifier->bind_source->get_datatype());

View File

@ -2769,10 +2769,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
return parse_expression(false); // Return the following expression.
}
#ifdef DEBUG_ENABLED
VariableNode *source_variable = nullptr;
#endif
switch (p_previous_operand->type) {
case Node::IDENTIFIER: {
#ifdef DEBUG_ENABLED
@ -2781,8 +2777,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
IdentifierNode *id = static_cast<IdentifierNode *>(p_previous_operand);
switch (id->source) {
case IdentifierNode::LOCAL_VARIABLE:
source_variable = id->variable_source;
id->variable_source->usages--;
break;
case IdentifierNode::LOCAL_CONSTANT:
@ -2813,16 +2807,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
update_extents(assignment);
make_completion_context(COMPLETION_ASSIGN, assignment);
#ifdef DEBUG_ENABLED
bool has_operator = true;
#endif
switch (previous.type) {
case GDScriptTokenizer::Token::EQUAL:
assignment->operation = AssignmentNode::OP_NONE;
assignment->variant_op = Variant::OP_MAX;
#ifdef DEBUG_ENABLED
has_operator = false;
#endif
break;
case GDScriptTokenizer::Token::PLUS_EQUAL:
assignment->operation = AssignmentNode::OP_ADDITION;
@ -2878,16 +2866,6 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode
}
complete_extents(assignment);
#ifdef DEBUG_ENABLED
if (source_variable != nullptr) {
if (has_operator && source_variable->assignments == 0) {
push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name);
}
source_variable->assignments += 1;
}
#endif
return assignment;
}

View File

@ -40,7 +40,7 @@ String GDScriptWarning::get_message() const {
switch (code) {
case UNASSIGNED_VARIABLE:
CHECK_SYMBOLS(1);
return vformat(R"(The variable "%s" was used but never assigned a value.)", symbols[0]);
return vformat(R"(The variable "%s" was used before being assigned a value.)", symbols[0]);
case UNASSIGNED_VARIABLE_OP_ASSIGN:
CHECK_SYMBOLS(1);
return vformat(R"(Using assignment with operation but the variable "%s" was not previously assigned a value.)", symbols[0]);

View File

@ -11,6 +11,7 @@ class InnerClass:
var e2: InnerClass.MyEnum
var e3: EnumTypecheckOuterClass.InnerClass.MyEnum
@warning_ignore("unassigned_variable")
print("Self ", e1, e2, e3)
e1 = MyEnum.V1
e2 = MyEnum.V1
@ -48,6 +49,7 @@ func test_outer_from_outer():
var e1: MyEnum
var e2: EnumTypecheckOuterClass.MyEnum
@warning_ignore("unassigned_variable")
print("Self ", e1, e2)
e1 = MyEnum.V1
e2 = MyEnum.V1
@ -66,6 +68,7 @@ func test_inner_from_outer():
var e1: InnerClass.MyEnum
var e2: EnumTypecheckOuterClass.InnerClass.MyEnum
@warning_ignore("unassigned_variable")
print("Inner ", e1, e2)
e1 = InnerClass.MyEnum.V1
e2 = InnerClass.MyEnum.V1

View File

@ -0,0 +1,7 @@
# GH-88117, GH-85796
func test():
var array: Array
# Should not emit unassigned warning because the Array type has a default value.
array.assign([1, 2, 3])
print(array)

View File

@ -0,0 +1,2 @@
GDTEST_OK
[1, 2, 3]

View File

@ -31,8 +31,8 @@ func int_func() -> int:
func test_warnings(unused_private_class_variable):
var t = 1
@warning_ignore("unassigned_variable")
var unassigned_variable
@warning_ignore("unassigned_variable")
print(unassigned_variable)
var _unassigned_variable_op_assign

View File

@ -1,2 +1,11 @@
func test():
var __
var unassigned
print(unassigned)
unassigned = "something" # Assigned only after use.
var a
print(a) # Unassigned, warn.
if a: # Still unassigned, warn.
a = 1
print(a) # Assigned (dead code), don't warn.
print(a) # "Maybe" assigned, don't warn.

View File

@ -1,5 +1,16 @@
GDTEST_OK
>> WARNING
>> Line: 2
>> Line: 3
>> UNASSIGNED_VARIABLE
>> The variable "__" was used but never assigned a value.
>> The variable "unassigned" was used before being assigned a value.
>> WARNING
>> Line: 7
>> UNASSIGNED_VARIABLE
>> The variable "a" was used before being assigned a value.
>> WARNING
>> Line: 8
>> UNASSIGNED_VARIABLE
>> The variable "a" was used before being assigned a value.
<null>
<null>
<null>

View File

@ -7,6 +7,7 @@ func test():
var b
if true:
var c
@warning_ignore("unassigned_variable")
prints("Begin:", i, a, b, c)
a = 1
b = 1
@ -20,6 +21,7 @@ func test():
var b
if true:
var c
@warning_ignore("unassigned_variable")
prints("Begin:", j, a, b, c)
a = 1
b = 1