Skip to content

GDScript: Be lenient with member names shadowing native classes #108108

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

vnen
Copy link
Member

@vnen vnen commented Jun 29, 2025

This improves backward compatibility, since if we introduce a new class that matches an existing member, it will still work.

Also fix an issue where the native class is still considered in the compiler even when shadowed. And prevent non-exposed classes from triggering the warning about shadowing.

Fix #96743
Fix #105362

@vnen vnen added this to the 4.5 milestone Jun 29, 2025
@vnen vnen requested review from a team as code owners June 29, 2025 14:47
@HolonProduction
Copy link
Member

Doesn't work properly with variable type hints:

extends Node


const Node3D = preload("res://a.gd")
var a: Node3D = Node3D.new() # Incompatible, typehint is the native type

var Node2D = Viewport
var b: Node2D # Should throw an errors since the variable is not constant, but does not

This improves backward compatibility, since if we introduce a new class
that matches an existing member, it will still work.

Also fix an issue where the native class is still considered in the
compiler even when shadowed. And prevent non-exposed classes from
triggering the warning about shadowing.
@vnen vnen force-pushed the gdscript-fix-member-shadowing-native branch from cb82517 to b735b7e Compare July 2, 2025 14:18
@vnen
Copy link
Member Author

vnen commented Jul 2, 2025

@HolonProduction added support for that (and tests for it).

Comment on lines +718 to +756
if (!type_found && parser->current_class->has_member(first_id->name)) {
GDScriptParser::ClassNode::Member member = parser->current_class->get_member(first_id->name);
switch (member.type) {
case GDScriptParser::ClassNode::Member::CONSTANT: {
result = member.constant->get_datatype();
if (!result.is_set()) {
resolve_constant(member.constant, false);
result = member.constant->get_datatype();
}
if (result.is_meta_type) {
type_found = true;
} else if (Ref<Script>(member.constant->initializer->reduced_value).is_valid()) {
Ref<GDScript> gdscript = member.constant->initializer->reduced_value;
if (gdscript.is_valid()) {
Ref<GDScriptParserRef> ref = parser->get_depended_parser_for(gdscript->get_script_path());
if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), first_id);
return bad_type;
}
result = ref->get_parser()->head->get_datatype();
} else {
result = make_script_meta_type(member.constant->initializer->reduced_value);
}
type_found = true;
} else {
push_error(vformat(R"(Constant "%s" is not a valid type.)", first), first_id);
return bad_type;
}
} break;
case GDScriptParser::ClassNode::Member::VARIABLE:
case GDScriptParser::ClassNode::Member::FUNCTION:
case GDScriptParser::ClassNode::Member::SIGNAL:
case GDScriptParser::ClassNode::Member::ENUM_VALUE:
push_error(vformat(R"(Member %s "%s" cannot be used as a type.)", member.get_type_name(), first), first_id);
return bad_type;
default:
break;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this duplicate the logic from here:

// Classes in current scope.
List<GDScriptParser::ClassNode *> script_classes;
bool found = false;
get_class_node_current_scope_classes(parser->current_class, &script_classes, p_type);
for (GDScriptParser::ClassNode *script_class : script_classes) {
if (found) {
break;
}
if (script_class->identifier && script_class->identifier->name == first) {
result = script_class->get_datatype();
break;
}
if (script_class->members_indices.has(first)) {
resolve_class_member(script_class, first, p_type);
GDScriptParser::ClassNode::Member member = script_class->get_member(first);
switch (member.type) {
case GDScriptParser::ClassNode::Member::CLASS:
result = member.get_datatype();
found = true;
break;
case GDScriptParser::ClassNode::Member::ENUM:
result = member.get_datatype();
found = true;
break;
case GDScriptParser::ClassNode::Member::CONSTANT:
if (member.get_datatype().is_meta_type) {
result = member.get_datatype();
found = true;
break;
} else if (Ref<Script>(member.constant->initializer->reduced_value).is_valid()) {
Ref<GDScript> gdscript = member.constant->initializer->reduced_value;
if (gdscript.is_valid()) {
Ref<GDScriptParserRef> ref = parser->get_depended_parser_for(gdscript->get_script_path());
if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), p_type);
return bad_type;
}
result = ref->get_parser()->head->get_datatype();
} else {
result = make_script_meta_type(member.constant->initializer->reduced_value);
}
found = true;
break;
}
[[fallthrough]];
default:
push_error(vformat(R"("%s" is a %s but does not contain a type.)", first, member.get_type_name()), p_type);
return bad_type;
}
}
}

Shouldn't we move the entire linked code block up here? The way it is right now, we have the constant resolving implemented twice and there are some inconsistencies. E.g. shadowing works fine for constants but not for enums. Also shadowing with constants does not work properly when inheritance is involved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Editor crash when using a variable with the same name as an internal class in C++ code Editor crash while importing resources.
2 participants