Skip to content
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

GDScript: Allow using local constants as types #80964

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
271 changes: 155 additions & 116 deletions modules/gdscript/gdscript_analyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -613,144 +613,183 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
return result;
}

StringName first = p_type->type_chain[0]->name;

if (first == SNAME("Variant")) {
if (p_type->type_chain.size() == 2) {
// May be nested enum.
StringName enum_name = p_type->type_chain[1]->name;
StringName qualified_name = String(first) + ENUM_SEPARATOR + String(p_type->type_chain[1]->name);
if (CoreConstants::is_global_enum(qualified_name)) {
result = make_global_enum_type(enum_name, first, true);
return result;
const GDScriptParser::IdentifierNode *first_id = p_type->type_chain[0];
StringName first = first_id->name;
bool type_found = false;

if (first_id->suite && first_id->suite->has_local(first)) {
const GDScriptParser::SuiteNode::Local &local = first_id->suite->get_local(first);
if (local.type == GDScriptParser::SuiteNode::Local::CONSTANT) {
result = local.get_datatype();
if (!result.is_set()) {
// Don't try to resolve it as the constant can be declared below.
push_error(vformat(R"(Local constant "%s" is not resolved at this point.)", first), first_id);
return bad_type;
}
if (result.is_meta_type) {
type_found = true;
} else if (Ref<Script>(local.constant->initializer->reduced_value).is_valid()) {
Ref<GDScript> gdscript = local.constant->initializer->reduced_value;
if (gdscript.is_valid()) {
Ref<GDScriptParserRef> ref = get_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(local.constant->initializer->reduced_value);
}
type_found = true;
} else {
push_error(vformat(R"(Name "%s" is not a nested type of "Variant".)", enum_name), p_type->type_chain[1]);
push_error(vformat(R"(Local constant "%s" is not a valid type.)", first), first_id);
return bad_type;
}
} else if (p_type->type_chain.size() > 2) {
push_error(R"(Variant only contains enum types, which do not have nested types.)", p_type->type_chain[2]);
return bad_type;
}
result.kind = GDScriptParser::DataType::VARIANT;
} else if (first == SNAME("Object")) {
// Object is treated like a native type, not a built-in.
result.kind = GDScriptParser::DataType::NATIVE;
result.builtin_type = Variant::OBJECT;
result.native_type = SNAME("Object");
} else if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) {
// Built-in types.
if (p_type->type_chain.size() > 1) {
push_error(R"(Built-in types don't contain nested types.)", p_type->type_chain[1]);
} else {
push_error(vformat(R"(Local %s "%s" cannot be used as a type.)", local.get_name(), first), first_id);
return bad_type;
}
result.kind = GDScriptParser::DataType::BUILTIN;
result.builtin_type = GDScriptParser::get_builtin_type(first);
}

if (result.builtin_type == Variant::ARRAY) {
GDScriptParser::DataType container_type = type_from_metatype(resolve_datatype(p_type->container_type));
if (container_type.kind != GDScriptParser::DataType::VARIANT) {
container_type.is_constant = false;
result.set_container_element_type(container_type);
}
}
} else if (class_exists(first)) {
// Native engine classes.
result.kind = GDScriptParser::DataType::NATIVE;
result.builtin_type = Variant::OBJECT;
result.native_type = first;
} else if (ScriptServer::is_global_class(first)) {
if (parser->script_path == ScriptServer::get_global_class_path(first)) {
result = parser->head->get_datatype();
} else {
String path = ScriptServer::get_global_class_path(first);
String ext = path.get_extension();
if (ext == GDScriptLanguage::get_singleton()->get_extension()) {
Ref<GDScriptParserRef> ref = get_parser_for(path);
if (!ref.is_valid() || ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
push_error(vformat(R"(Could not parse global class "%s" from "%s".)", first, ScriptServer::get_global_class_path(first)), p_type);
if (!type_found) {
if (first == SNAME("Variant")) {
if (p_type->type_chain.size() == 2) {
// May be nested enum.
StringName enum_name = p_type->type_chain[1]->name;
StringName qualified_name = String(first) + ENUM_SEPARATOR + String(p_type->type_chain[1]->name);
if (CoreConstants::is_global_enum(qualified_name)) {
result = make_global_enum_type(enum_name, first, true);
return result;
} else {
push_error(vformat(R"(Name "%s" is not a nested type of "Variant".)", enum_name), p_type->type_chain[1]);
return bad_type;
}
result = ref->get_parser()->head->get_datatype();
} else {
result = make_script_meta_type(ResourceLoader::load(path, "Script"));
} else if (p_type->type_chain.size() > 2) {
push_error(R"(Variant only contains enum types, which do not have nested types.)", p_type->type_chain[2]);
return bad_type;
}
}
} else if (ProjectSettings::get_singleton()->has_autoload(first) && ProjectSettings::get_singleton()->get_autoload(first).is_singleton) {
const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(first);
Ref<GDScriptParserRef> ref = get_parser_for(autoload.path);
if (ref.is_null()) {
push_error(vformat(R"(The referenced autoload "%s" (from "%s") could not be loaded.)", first, autoload.path), p_type);
return bad_type;
}
if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
push_error(vformat(R"(Could not parse singleton "%s" from "%s".)", first, autoload.path), p_type);
return bad_type;
}
result = ref->get_parser()->head->get_datatype();
} else if (ClassDB::has_enum(parser->current_class->base_type.native_type, first)) {
// Native enum in current class.
result = make_native_enum_type(first, parser->current_class->base_type.native_type);
} else if (CoreConstants::is_global_enum(first)) {
if (p_type->type_chain.size() > 1) {
push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[1]);
return bad_type;
}
result = make_global_enum_type(first, StringName());
} else {
// Classes in current scope.
List<GDScriptParser::ClassNode *> script_classes;
bool found = false;
get_class_node_current_scope_classes(parser->current_class, &script_classes);
for (GDScriptParser::ClassNode *script_class : script_classes) {
if (found) {
break;
result.kind = GDScriptParser::DataType::VARIANT;
} else if (first == SNAME("Object")) {
// Object is treated like a native type, not a built-in.
result.kind = GDScriptParser::DataType::NATIVE;
result.builtin_type = Variant::OBJECT;
result.native_type = SNAME("Object");
} else if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) {
// Built-in types.
if (p_type->type_chain.size() > 1) {
push_error(R"(Built-in types don't contain nested types.)", p_type->type_chain[1]);
return bad_type;
}
result.kind = GDScriptParser::DataType::BUILTIN;
result.builtin_type = GDScriptParser::get_builtin_type(first);

if (script_class->identifier && script_class->identifier->name == first) {
result = script_class->get_datatype();
break;
if (result.builtin_type == Variant::ARRAY) {
GDScriptParser::DataType container_type = type_from_metatype(resolve_datatype(p_type->container_type));
if (container_type.kind != GDScriptParser::DataType::VARIANT) {
container_type.is_constant = false;
result.set_container_element_type(container_type);
}
}
} else if (class_exists(first)) {
// Native engine classes.
result.kind = GDScriptParser::DataType::NATIVE;
result.builtin_type = Variant::OBJECT;
result.native_type = first;
} else if (ScriptServer::is_global_class(first)) {
if (parser->script_path == ScriptServer::get_global_class_path(first)) {
result = parser->head->get_datatype();
} else {
String path = ScriptServer::get_global_class_path(first);
String ext = path.get_extension();
if (ext == GDScriptLanguage::get_singleton()->get_extension()) {
Ref<GDScriptParserRef> ref = get_parser_for(path);
if (!ref.is_valid() || ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
push_error(vformat(R"(Could not parse global class "%s" from "%s".)", first, ScriptServer::get_global_class_path(first)), p_type);
return bad_type;
}
result = ref->get_parser()->head->get_datatype();
} else {
result = make_script_meta_type(ResourceLoader::load(path, "Script"));
}
}
} else if (ProjectSettings::get_singleton()->has_autoload(first) && ProjectSettings::get_singleton()->get_autoload(first).is_singleton) {
const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(first);
Ref<GDScriptParserRef> ref = get_parser_for(autoload.path);
if (ref.is_null()) {
push_error(vformat(R"(The referenced autoload "%s" (from "%s") could not be loaded.)", first, autoload.path), p_type);
return bad_type;
}
if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) {
push_error(vformat(R"(Could not parse singleton "%s" from "%s".)", first, autoload.path), p_type);
return bad_type;
}
result = ref->get_parser()->head->get_datatype();
} else if (ClassDB::has_enum(parser->current_class->base_type.native_type, first)) {
// Native enum in current class.
result = make_native_enum_type(first, parser->current_class->base_type.native_type);
} else if (CoreConstants::is_global_enum(first)) {
if (p_type->type_chain.size() > 1) {
push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[1]);
return bad_type;
}
if (script_class->members_indices.has(first)) {
resolve_class_member(script_class, first, p_type);
result = make_global_enum_type(first, StringName());
} else {
// Classes in current scope.
List<GDScriptParser::ClassNode *> script_classes;
bool found = false;
get_class_node_current_scope_classes(parser->current_class, &script_classes);
for (GDScriptParser::ClassNode *script_class : script_classes) {
if (found) {
break;
}

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) {
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;
} 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 = get_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);
}
case GDScriptParser::ClassNode::Member::ENUM:
result = member.get_datatype();
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;
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 = get_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;
}
}
}
}
}

if (!result.is_set()) {
push_error(vformat(R"(Could not find type "%s" in the current scope.)", first), p_type);
return bad_type;
Expand Down
4 changes: 1 addition & 3 deletions modules/gdscript/gdscript_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2291,9 +2291,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode
IdentifierNode *identifier = alloc_node<IdentifierNode>();
complete_extents(identifier);
identifier->name = previous.get_identifier();
#ifdef DEBUG_ENABLED
identifier->suite = current_suite;
#endif

if (current_suite != nullptr && current_suite->has_local(identifier->name)) {
const SuiteNode::Local &declaration = current_suite->get_local(identifier->name);
Expand Down Expand Up @@ -4283,7 +4281,7 @@ String GDScriptParser::SuiteNode::Local::get_name() const {
case SuiteNode::Local::FOR_VARIABLE:
return "for loop iterator";
case SuiteNode::Local::PATTERN_BIND:
return "pattern_bind";
return "pattern bind";
case SuiteNode::Local::UNDEFINED:
return "<undefined>";
default:
Expand Down
2 changes: 0 additions & 2 deletions modules/gdscript/gdscript_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -859,9 +859,7 @@ class GDScriptParser {

struct IdentifierNode : public ExpressionNode {
StringName name;
#ifdef DEBUG_ENABLED
SuiteNode *suite = nullptr; // The block in which the identifier is used.
#endif

enum Source {
UNDEFINED_SOURCE,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
enum MyEnum {}

func test():
var e: E
const E = MyEnum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Local constant "E" is not resolved at this point.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
enum MyEnum {}

func test():
var E = MyEnum
var e: E
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Local variable "E" cannot be used as a type.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
enum MyEnum {A}

func test():
const E = MyEnum.A
var e: E
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Local constant "E" is not a valid type.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
class InnerClass:
enum InnerEnum {A = 2}
const INNER_CONST = "INNER_CONST"

enum Enum {A = 1}

const Other = preload("./local_const_as_type.notest.gd")

func test():
const IC = InnerClass
const IE = IC.InnerEnum
const E = Enum
# Doesn't work in CI, but works in the editor. Looks like an unrelated bug. TODO: Investigate it.
# Error: Invalid call. Nonexistent function 'new' in base 'GDScript'.
var a1: IC = null # IC.new()
var a2: IE = IE.A
var a3: IC.InnerEnum = IE.A
var a4: E = E.A
print(a1.INNER_CONST)
print(a2)
print(a3)
print(a4)

const O = Other
const OV: Variant = Other # Removes metatype.
const OIC = O.InnerClass
const OIE = OIC.InnerEnum
const OE = O.Enum
var b: O = O.new()
@warning_ignore("unsafe_method_access")
var bv: OV = OV.new()
var b1: OIC = OIC.new()
var b2: OIE = OIE.A
var b3: O.InnerClass.InnerEnum = OIE.A
var b4: OE = OE.A
print(b.CONST)
print(bv.CONST)
print(b1.INNER_CONST)
print(b2)
print(b3)
print(b4)
Loading