Browse Source

GDScript: Allow use local constants as types

Danil Alexeev 2 years ago
parent
commit
68a567bd13

+ 155 - 116
modules/gdscript/gdscript_analyzer.cpp

@@ -613,144 +613,183 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
 		return result;
 		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 {
 			} 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;
 				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;
 			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;
 					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();
 							result = member.get_datatype();
 							found = true;
 							found = true;
 							break;
 							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;
 							found = true;
 							break;
 							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()) {
 	if (!result.is_set()) {
 		push_error(vformat(R"(Could not find type "%s" in the current scope.)", first), p_type);
 		push_error(vformat(R"(Could not find type "%s" in the current scope.)", first), p_type);
 		return bad_type;
 		return bad_type;

+ 1 - 3
modules/gdscript/gdscript_parser.cpp

@@ -2291,9 +2291,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode
 	IdentifierNode *identifier = alloc_node<IdentifierNode>();
 	IdentifierNode *identifier = alloc_node<IdentifierNode>();
 	complete_extents(identifier);
 	complete_extents(identifier);
 	identifier->name = previous.get_identifier();
 	identifier->name = previous.get_identifier();
-#ifdef DEBUG_ENABLED
 	identifier->suite = current_suite;
 	identifier->suite = current_suite;
-#endif
 
 
 	if (current_suite != nullptr && current_suite->has_local(identifier->name)) {
 	if (current_suite != nullptr && current_suite->has_local(identifier->name)) {
 		const SuiteNode::Local &declaration = current_suite->get_local(identifier->name);
 		const SuiteNode::Local &declaration = current_suite->get_local(identifier->name);
@@ -4283,7 +4281,7 @@ String GDScriptParser::SuiteNode::Local::get_name() const {
 		case SuiteNode::Local::FOR_VARIABLE:
 		case SuiteNode::Local::FOR_VARIABLE:
 			return "for loop iterator";
 			return "for loop iterator";
 		case SuiteNode::Local::PATTERN_BIND:
 		case SuiteNode::Local::PATTERN_BIND:
-			return "pattern_bind";
+			return "pattern bind";
 		case SuiteNode::Local::UNDEFINED:
 		case SuiteNode::Local::UNDEFINED:
 			return "<undefined>";
 			return "<undefined>";
 		default:
 		default:

+ 0 - 2
modules/gdscript/gdscript_parser.h

@@ -859,9 +859,7 @@ public:
 
 
 	struct IdentifierNode : public ExpressionNode {
 	struct IdentifierNode : public ExpressionNode {
 		StringName name;
 		StringName name;
-#ifdef DEBUG_ENABLED
 		SuiteNode *suite = nullptr; // The block in which the identifier is used.
 		SuiteNode *suite = nullptr; // The block in which the identifier is used.
-#endif
 
 
 		enum Source {
 		enum Source {
 			UNDEFINED_SOURCE,
 			UNDEFINED_SOURCE,

+ 5 - 0
modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_before_declared.gd

@@ -0,0 +1,5 @@
+enum MyEnum {}
+
+func test():
+	var e: E
+	const E = MyEnum

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_before_declared.out

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Local constant "E" is not resolved at this point.

+ 5 - 0
modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_const.gd

@@ -0,0 +1,5 @@
+enum MyEnum {}
+
+func test():
+	var E = MyEnum
+	var e: E

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_const.out

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Local variable "E" cannot be used as a type.

+ 5 - 0
modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_type.gd

@@ -0,0 +1,5 @@
+enum MyEnum {A}
+
+func test():
+	const E = MyEnum.A
+	var e: E

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/errors/local_const_as_type_use_not_type.out

@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Local constant "E" is not a valid type.

+ 41 - 0
modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.gd

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

+ 6 - 0
modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.notest.gd

@@ -0,0 +1,6 @@
+class InnerClass:
+	enum InnerEnum {A = 20}
+	const INNER_CONST = "OTHER_INNER_CONST"
+
+enum Enum {A = 10}
+const CONST = "OTHER_CONST"

+ 11 - 0
modules/gdscript/tests/scripts/analyzer/features/local_const_as_type.out

@@ -0,0 +1,11 @@
+GDTEST_OK
+INNER_CONST
+2
+2
+1
+OTHER_CONST
+OTHER_CONST
+OTHER_INNER_CONST
+20
+20
+10