Browse Source

Fix constant base typing in extended GDScript class

Adam Scott 2 years ago
parent
commit
65a49bad5a

+ 57 - 44
modules/gdscript/gdscript_analyzer.cpp

@@ -219,6 +219,22 @@ Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::C
 	return OK;
 	return OK;
 }
 }
 
 
+void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list) {
+	if (p_list->find(p_node) != nullptr) {
+		return;
+	}
+	p_list->push_back(p_node);
+
+	// Prioritize node base type over its outer class
+	if (p_node->base_type.class_type != nullptr) {
+		get_class_node_current_scope_classes(p_node->base_type.class_type, p_list);
+	}
+
+	if (p_node->outer != nullptr) {
+		get_class_node_current_scope_classes(p_node->outer, p_list);
+	}
+}
+
 Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) {
 Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) {
 	if (p_class->base_type.is_set()) {
 	if (p_class->base_type.is_set()) {
 		// Already resolved
 		// Already resolved
@@ -327,9 +343,10 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
 				base.native_type = name;
 				base.native_type = name;
 			} else {
 			} else {
 				// Look for other classes in script.
 				// Look for other classes in script.
-				GDScriptParser::ClassNode *look_class = p_class;
 				bool found = false;
 				bool found = false;
-				while (look_class != nullptr) {
+				List<GDScriptParser::ClassNode *> script_classes;
+				get_class_node_current_scope_classes(p_class, &script_classes);
+				for (GDScriptParser::ClassNode *look_class : script_classes) {
 					if (look_class->identifier && look_class->identifier->name == name) {
 					if (look_class->identifier && look_class->identifier->name == name) {
 						if (!look_class->get_datatype().is_set()) {
 						if (!look_class->get_datatype().is_set()) {
 							Error err = resolve_inheritance(look_class, false);
 							Error err = resolve_inheritance(look_class, false);
@@ -353,7 +370,6 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
 						found = true;
 						found = true;
 						break;
 						break;
 					}
 					}
-					look_class = look_class->outer;
 				}
 				}
 
 
 				if (!found) {
 				if (!found) {
@@ -517,12 +533,11 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
 		result = make_native_enum_type(parser->current_class->base_type.native_type, first);
 		result = make_native_enum_type(parser->current_class->base_type.native_type, first);
 	} else {
 	} else {
 		// Classes in current scope.
 		// Classes in current scope.
-		GDScriptParser::ClassNode *script_class = parser->current_class;
-		bool found = false;
-		while (!found && script_class != nullptr) {
+		List<GDScriptParser::ClassNode *> script_classes;
+		get_class_node_current_scope_classes(parser->current_class, &script_classes);
+		for (GDScriptParser::ClassNode *script_class : script_classes) {
 			if (script_class->identifier && script_class->identifier->name == first) {
 			if (script_class->identifier && script_class->identifier->name == first) {
 				result = script_class->get_datatype();
 				result = script_class->get_datatype();
-				found = true;
 				break;
 				break;
 			}
 			}
 			if (script_class->members_indices.has(first)) {
 			if (script_class->members_indices.has(first)) {
@@ -530,17 +545,14 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
 				switch (member.type) {
 				switch (member.type) {
 					case GDScriptParser::ClassNode::Member::CLASS:
 					case GDScriptParser::ClassNode::Member::CLASS:
 						result = member.m_class->get_datatype();
 						result = member.m_class->get_datatype();
-						found = true;
 						break;
 						break;
 					case GDScriptParser::ClassNode::Member::ENUM:
 					case GDScriptParser::ClassNode::Member::ENUM:
 						result = member.m_enum->get_datatype();
 						result = member.m_enum->get_datatype();
-						found = true;
 						break;
 						break;
 					case GDScriptParser::ClassNode::Member::CONSTANT:
 					case GDScriptParser::ClassNode::Member::CONSTANT:
 						if (member.constant->get_datatype().is_meta_type) {
 						if (member.constant->get_datatype().is_meta_type) {
 							result = member.constant->get_datatype();
 							result = member.constant->get_datatype();
 							result.is_meta_type = false;
 							result.is_meta_type = false;
-							found = true;
 							break;
 							break;
 						} else if (Ref<Script>(member.constant->initializer->reduced_value).is_valid()) {
 						} else if (Ref<Script>(member.constant->initializer->reduced_value).is_valid()) {
 							Ref<GDScript> gdscript = member.constant->initializer->reduced_value;
 							Ref<GDScript> gdscript = member.constant->initializer->reduced_value;
@@ -569,7 +581,6 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
 						return GDScriptParser::DataType();
 						return GDScriptParser::DataType();
 				}
 				}
 			}
 			}
-			script_class = script_class->outer;
 		}
 		}
 	}
 	}
 	if (!result.is_set()) {
 	if (!result.is_set()) {
@@ -2891,41 +2902,43 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
 		}
 		}
 		// Check outer constants.
 		// Check outer constants.
 		// TODO: Allow outer static functions.
 		// TODO: Allow outer static functions.
-		GDScriptParser::ClassNode *outer = base_class->outer;
-		while (outer != nullptr) {
-			if (outer->has_member(name)) {
-				const GDScriptParser::ClassNode::Member &member = outer->get_member(name);
-				switch (member.type) {
-					case GDScriptParser::ClassNode::Member::CONSTANT: {
-						// TODO: Make sure loops won't cause problem. And make special error message for those.
-						// For out-of-order resolution:
-						reduce_expression(member.constant->initializer);
-						p_identifier->set_datatype(member.get_datatype());
-						p_identifier->is_constant = true;
-						p_identifier->reduced_value = member.constant->initializer->reduced_value;
-						return;
-					} break;
-					case GDScriptParser::ClassNode::Member::ENUM_VALUE: {
-						p_identifier->set_datatype(member.get_datatype());
-						p_identifier->is_constant = true;
-						p_identifier->reduced_value = member.enum_value.value;
-						return;
-					} break;
-					case GDScriptParser::ClassNode::Member::ENUM: {
-						p_identifier->set_datatype(member.get_datatype());
-						p_identifier->is_constant = false;
-						return;
-					} break;
-					case GDScriptParser::ClassNode::Member::CLASS: {
-						resolve_class_interface(member.m_class);
-						p_identifier->set_datatype(member.m_class->get_datatype());
-						return;
-					} break;
-					default:
-						break;
+		if (base_class->outer != nullptr) {
+			List<GDScriptParser::ClassNode *> script_classes;
+			get_class_node_current_scope_classes(parser->current_class, &script_classes);
+			for (GDScriptParser::ClassNode *script_class : script_classes) {
+				if (script_class->has_member(name)) {
+					const GDScriptParser::ClassNode::Member &member = script_class->get_member(name);
+					switch (member.type) {
+						case GDScriptParser::ClassNode::Member::CONSTANT: {
+							// TODO: Make sure loops won't cause problem. And make special error message for those.
+							// For out-of-order resolution:
+							reduce_expression(member.constant->initializer);
+							p_identifier->set_datatype(member.get_datatype());
+							p_identifier->is_constant = true;
+							p_identifier->reduced_value = member.constant->initializer->reduced_value;
+							return;
+						} break;
+						case GDScriptParser::ClassNode::Member::ENUM_VALUE: {
+							p_identifier->set_datatype(member.get_datatype());
+							p_identifier->is_constant = true;
+							p_identifier->reduced_value = member.enum_value.value;
+							return;
+						} break;
+						case GDScriptParser::ClassNode::Member::ENUM: {
+							p_identifier->set_datatype(member.get_datatype());
+							p_identifier->is_constant = false;
+							return;
+						} break;
+						case GDScriptParser::ClassNode::Member::CLASS: {
+							resolve_class_interface(member.m_class);
+							p_identifier->set_datatype(member.m_class->get_datatype());
+							return;
+						} break;
+						default:
+							break;
+					}
 				}
 				}
 			}
 			}
-			outer = outer->outer;
 		}
 		}
 
 
 		base_class = base_class->base_type.class_type;
 		base_class = base_class->base_type.class_type;

+ 2 - 0
modules/gdscript/gdscript_analyzer.h

@@ -50,6 +50,8 @@ class GDScriptAnalyzer {
 	Error check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string);
 	Error check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string);
 	Error check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node);
 	Error check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node);
 
 
+	void get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list);
+
 	Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true);
 	Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true);
 	GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type);
 	GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type);
 
 

+ 14 - 0
modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.gd

@@ -0,0 +1,14 @@
+const A: = preload("base_outer_resolution_a.notest.gd")
+const B: = preload("base_outer_resolution_b.notest.gd")
+const C: = preload("base_outer_resolution_c.notest.gd")
+
+const Extend: = preload("base_outer_resolution_extend.notest.gd")
+
+func test() -> void:
+	Extend.test_a(A.new())
+	Extend.test_b(B.new())
+	Extend.InnerClass.test_c(C.new())
+	Extend.InnerClass.InnerInnerClass.test_a_b_c(A.new(), B.new(), C.new())
+	Extend.InnerClass.InnerInnerClass.test_enum(C.TestEnum.HELLO_WORLD)
+	Extend.InnerClass.InnerInnerClass.test_a_prime(A.APrime.new())
+

+ 7 - 0
modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.out

@@ -0,0 +1,7 @@
+GDTEST_OK
+true
+true
+true
+true
+true
+true

+ 2 - 0
modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_a.notest.gd

@@ -0,0 +1,2 @@
+class APrime:
+	pass

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


+ 4 - 0
modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_base.notest.gd

@@ -0,0 +1,4 @@
+const A: = preload("base_outer_resolution_a.notest.gd")
+
+class InnerClassInBase:
+	const C: = preload("base_outer_resolution_c.notest.gd")

+ 3 - 0
modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_c.notest.gd

@@ -0,0 +1,3 @@
+enum TestEnum {
+	HELLO_WORLD
+}

+ 23 - 0
modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_extend.notest.gd

@@ -0,0 +1,23 @@
+extends "base_outer_resolution_base.notest.gd"
+
+const B: = preload("base_outer_resolution_b.notest.gd")
+
+static func test_a(a: A) -> void:
+	print(a is A)
+
+static func test_b(b: B) -> void:
+	print(b is B)
+
+class InnerClass extends InnerClassInBase:
+	static func test_c(c: C) -> void:
+		print(c is C)
+
+	class InnerInnerClass:
+		static func test_a_b_c(a: A, b: B, c: C) -> void:
+			print(a is A and b is B and c is C)
+
+		static func test_enum(test_enum: C.TestEnum) -> void:
+			print(test_enum == C.TestEnum.HELLO_WORLD)
+
+		static func test_a_prime(a_prime: A.APrime) -> void:
+			print(a_prime is A.APrime)