Browse Source

Merge pull request #92544 from dalexeev/gds-fix-const-non-metatype-subscript

GDScript: Fix subscript resolution on constant non-metatype GDScript base
Rémi Verschelde 1 year ago
parent
commit
ce82984b5c

+ 39 - 7
modules/gdscript/gdscript_analyzer.cpp

@@ -4347,15 +4347,45 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
 
 		GDScriptParser::DataType base_type = p_subscript->base->get_datatype();
 		bool valid = false;
+
 		// If the base is a metatype, use the analyzer instead.
-		if (p_subscript->base->is_constant && !base_type.is_meta_type && base_type.kind != GDScriptParser::DataType::CLASS) {
-			// Just try to get it.
-			Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid);
-			if (valid) {
-				p_subscript->is_constant = true;
-				p_subscript->reduced_value = value;
-				result_type = type_from_variant(value, p_subscript);
+		if (p_subscript->base->is_constant && !base_type.is_meta_type) {
+			// GH-92534. If the base is a GDScript, use the analyzer instead.
+			bool base_is_gdscript = false;
+			if (p_subscript->base->reduced_value.get_type() == Variant::OBJECT) {
+				Ref<GDScript> gdscript = Object::cast_to<GDScript>(p_subscript->base->reduced_value.get_validated_object());
+				if (gdscript.is_valid()) {
+					base_is_gdscript = true;
+					// Makes a metatype from a constant GDScript, since `base_type` is not a metatype.
+					GDScriptParser::DataType base_type_meta = type_from_variant(gdscript, p_subscript);
+					// First try to reduce the attribute from the metatype.
+					reduce_identifier_from_base(p_subscript->attribute, &base_type_meta);
+					GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype();
+					if (attr_type.is_set()) {
+						valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type;
+						result_type = attr_type;
+						p_subscript->is_constant = p_subscript->attribute->is_constant;
+						p_subscript->reduced_value = p_subscript->attribute->reduced_value;
+					}
+					if (!valid) {
+						// If unsuccessful, reset and return to the normal route.
+						p_subscript->attribute->set_datatype(GDScriptParser::DataType());
+					}
+				}
+			}
+			if (!base_is_gdscript) {
+				// Just try to get it.
+				Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid);
+				if (valid) {
+					p_subscript->is_constant = true;
+					p_subscript->reduced_value = value;
+					result_type = type_from_variant(value, p_subscript);
+				}
 			}
+		}
+
+		if (valid) {
+			// Do nothing.
 		} else if (base_type.is_variant() || !base_type.is_hard_type()) {
 			valid = !base_type.is_pseudo_type || p_can_be_pseudo_type;
 			result_type.kind = GDScriptParser::DataType::VARIANT;
@@ -4393,6 +4423,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
 				mark_node_unsafe(p_subscript);
 			}
 		}
+
 		if (!valid) {
 			GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype();
 			if (!p_can_be_pseudo_type && (attr_type.is_pseudo_type || result_type.is_pseudo_type)) {
@@ -4411,6 +4442,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
 		if (p_subscript->base->is_constant && p_subscript->index->is_constant) {
 			// Just try to get it.
 			bool valid = false;
+			// TODO: Check if `p_subscript->base->reduced_value` is GDScript.
 			Variant value = p_subscript->base->reduced_value.get(p_subscript->index->reduced_value, &valid);
 			if (!valid) {
 				push_error(vformat(R"(Cannot get index "%s" from "%s".)", p_subscript->index->reduced_value, p_subscript->base->reduced_value), p_subscript->index);

+ 12 - 0
modules/gdscript/tests/scripts/runtime/features/metatypes.gd

@@ -25,12 +25,24 @@ func test():
 		if str(property.name).begins_with("test_"):
 			print(Utils.get_property_signature(property))
 
+	print("---")
 	check_gdscript_native_class(test_native)
 	check_gdscript(test_script)
 	check_gdscript(test_class)
 	check_enum(test_enum)
 
+	print("---")
 	print(test_native.stringify([]))
 	print(test_script.TEST)
 	print(test_class.TEST)
 	print(test_enum.keys())
+
+	print("---")
+	# Some users add unnecessary type hints to `const`-`preload`, which removes metatypes.
+	# For **constant** `GDScript` we still check the class members, despite the wider type.
+	const ScriptNoMeta: GDScript = Other
+	const ClassNoMeta: GDScript = MyClass
+	var a := ScriptNoMeta.TEST
+	var b := ClassNoMeta.TEST
+	print(a)
+	print(b)

+ 5 - 0
modules/gdscript/tests/scripts/runtime/features/metatypes.out

@@ -3,11 +3,16 @@ var test_native: GDScriptNativeClass
 var test_script: GDScript
 var test_class: GDScript
 var test_enum: Dictionary
+---
 GDScriptNativeClass
 GDScript
 GDScript
 { "A": 0, "B": 1, "C": 2 }
+---
 []
 100
 10
 ["A", "B", "C"]
+---
+100
+10