Pārlūkot izejas kodu

Add `TypeTools.resolveTypeParameters` (#11053)

* Added `TypeTools.validateTypeParams`

* Added test for `TypeTools.validateTypeParams`

* Wrap `TypeTools.validateTypeParams` with #if macro

TypeTools.validateTypeParams requires TypeTools.map which is wrapped in #if macro compiler conditionals. Therefore, validateTypeParams must also be wrapped.

* Reworked func as `TypeTools.resolveTypeParameters`

Replaced `TypeTools.validateTypeParams` with `TypeTools.resolveTypeParameters`, allowing users to configure the `Type`s that are filled.
RoBBoR 2 gadi atpakaļ
vecāks
revīzija
70d6903527

+ 55 - 0
std/haxe/macro/TypeTools.hx

@@ -397,6 +397,61 @@ class TypeTools {
 		return null;
 		#end
 	}
+
+	/**
+		Calls `f` for each missing `TypeParameter` within Type `type`.
+		The `Type` returned from `f` fills the vacant parameter in a
+		copy returned by the function.
+
+		If `type` does not use type parameters, or all of the type
+		parameters are defined, `type` is returned unchanged.
+
+		Excessive type parameters are truncated.
+
+		If `recursive` is true, all subtypes are resolved.
+
+		The parameters provided to `f` are:
+			- The `TypeParameter` being resolved.
+			- The `Type` missing a type parameter.
+			- The `Int` index of type parameter being resolved.
+
+		Missing type parameters may cause fatal compiler errors.
+		Therefore, this function should be called on user generated
+		`Type`s prior to passing to macro API functions such as
+		`Context.follow` or `Context.unify`.
+	**/
+	public static function resolveTypeParameters(type:Type, recursive:Bool, f:(TypeParameter,Type,Int)->Type):Type {
+		function fillParams(typeParams:Array<TypeParameter>, concreteTypes:Array<Type>): Array<Type>
+			return if (concreteTypes.length > typeParams.length) {
+				concreteTypes.slice(0, typeParams.length);
+			} else {
+				[
+					for (i in 0...typeParams.length)
+						if (i < concreteTypes.length)
+							concreteTypes[i];
+						else
+							f(typeParams[i], type, i)
+				];
+			}
+
+		final result = switch (type) {
+			case TInst(t, params):
+				TInst(t, fillParams(t.get().params, params));
+			case TEnum(t, params):
+				TEnum(t, fillParams(t.get().params, params));
+			case TType(t, params):
+				TType(t, fillParams(t.get().params, params));
+			case TAbstract(t, params):
+				TAbstract(t, fillParams(t.get().params, params));
+			case _:
+				type;
+		}
+
+		return if(recursive)
+			map(result, (t) -> resolveTypeParameters(t, recursive, f));
+		else
+			result;
+	}
 	#end
 
 	/**

+ 111 - 0
tests/misc/projects/Issue10959/Macro.hx

@@ -0,0 +1,111 @@
+package;
+
+#if macro
+
+import haxe.macro.Type;
+
+using haxe.macro.TypeTools;
+
+class Macro {
+	public static function start() {
+		haxe.macro.Context.onGenerate(function(types: Array<haxe.macro.Type>) {
+			// Make sure this actually runs.
+			Sys.println("On Generate");
+
+			for(t in types) {
+				switch (t) {
+					case TAbstract(a, _): {
+						makeMalformedType(a);
+					}
+					case TInst(c, _) if(c.get().name == "TestClass"): {
+						testWithClass(c);
+					}
+					case _: {}
+				}
+			}
+		});
+	}
+
+	// General test that ensures crash doesn't occur
+	static function makeMalformedType(a: Ref<AbstractType>) {
+		// If passed as it is, this will crash the Haxe compiler.
+		// https://github.com/HaxeFoundation/haxe/issues/10959
+		final type = TAbstract(a, []);
+
+		// Use TypeTools.resolveTypeParameters
+		final valid = type.resolveTypeParameters(true, (tp,t,i) -> tp.defaultType ?? tp.t);
+
+		haxe.macro.Context.followWithAbstracts(valid);
+	}
+
+	// Test specific aspects of the resolveTypeParameters function
+	static function testWithClass(c: Ref<ClassType>) {
+		// Various types to use with tests
+		final voidType = haxe.macro.Context.getType("Void");
+		final intType = haxe.macro.Context.getType("Int");
+		final floatType = haxe.macro.Context.getType("Float");
+		final stringType = haxe.macro.Context.getType("String");
+
+		// Print type before and after using "resolveTypeParameters"
+		function print(t:Type, other:Null<Type> = null) {
+			Sys.println("Before: " + t.toString());
+
+			// Automatically resolve to the defaultType or `KTypeParameter` for testing purposes.
+			if(other == null) {
+				other = t.resolveTypeParameters(true, (tp,t,i) -> tp.defaultType ?? tp.t);
+			}
+			Sys.println("After:  " + other.toString());
+
+			Sys.println("");
+		}
+
+		Sys.println("-- Too Few Params --");
+
+		print(TInst(c, []));
+		print(TInst(c, [voidType]));
+
+		Sys.println("-- Too Many Params --");
+
+		print(TInst(c, [voidType, intType, floatType, stringType]));
+
+		Sys.println("-- Correct Number of Params (3rd is optional) --");
+
+		print(TInst(c, [voidType, intType, floatType]));
+		print(TInst(c, [voidType, intType]));
+
+		Sys.println("-- Shouldn't Have Params --");
+
+		switch(voidType) {
+			case TAbstract(absRef, _): {
+				print(TAbstract(absRef, [intType, floatType]));
+			}
+			case _:
+		}
+
+		Sys.println("-- Recursive Test --");
+
+		final inner1 = TInst(c, []);
+		final inner2 = TInst(c, [voidType, intType, floatType, stringType]);
+		print(TInst(c, [inner1, inner2]));
+
+		Sys.println("-- Fill With Specific Type --");
+		
+		print(inner1, inner1.resolveTypeParameters(true, (_,_,_) -> intType));
+		print(inner1, inner1.resolveTypeParameters(true, (_,_,_) -> stringType));
+		print(inner2, inner2.resolveTypeParameters(true, (_,_,_) -> stringType));
+
+		Sys.println("-- Recursive Param OFF --");
+
+		final recursiveType = TInst(c, [inner1, inner2]);
+		print(recursiveType, recursiveType.resolveTypeParameters(false, (_,_,_) -> stringType));
+
+		Sys.println("-- Index Print --");
+
+		recursiveType.resolveTypeParameters(true, function(tp, type, index) {
+			Sys.println(type.toString() + " - " + index);
+			return voidType;
+		});
+	}
+}
+
+#end

+ 6 - 0
tests/misc/projects/Issue10959/Main.hx

@@ -0,0 +1,6 @@
+package;
+
+// Some Haxe code must be present or Context.onGenerate will have no types.
+function main() {
+	trace("Hello world!");
+}

+ 5 - 0
tests/misc/projects/Issue10959/TestClass.hx

@@ -0,0 +1,5 @@
+package;
+
+@:keep
+class TestClass<T, U, V = Void> {
+}

+ 4 - 0
tests/misc/projects/Issue10959/build.hxml

@@ -0,0 +1,4 @@
+--macro Macro.start()
+Macro
+Main
+TestClass

+ 46 - 0
tests/misc/projects/Issue10959/build.hxml.stdout

@@ -0,0 +1,46 @@
+On Generate
+-- Too Few Params --
+Before: TestClass
+After:  TestClass<TestClass.T, TestClass.U, Void>
+
+Before: TestClass<Void>
+After:  TestClass<Void, TestClass.U, Void>
+
+-- Too Many Params --
+Before: TestClass<Void, Int, Float, String>
+After:  TestClass<Void, Int, Float>
+
+-- Correct Number of Params (3rd is optional) --
+Before: TestClass<Void, Int, Float>
+After:  TestClass<Void, Int, Float>
+
+Before: TestClass<Void, Int>
+After:  TestClass<Void, Int, Void>
+
+-- Shouldn't Have Params --
+Before: Void<Int, Float>
+After:  Void
+
+-- Recursive Test --
+Before: TestClass<TestClass, TestClass<Void, Int, Float, String>>
+After:  TestClass<TestClass<TestClass.T, TestClass.U, Void>, TestClass<Void, Int, Float>, Void>
+
+-- Fill With Specific Type --
+Before: TestClass
+After:  TestClass<Int, Int, Int>
+
+Before: TestClass
+After:  TestClass<String, String, String>
+
+Before: TestClass<Void, Int, Float, String>
+After:  TestClass<Void, Int, Float>
+
+-- Recursive Param OFF --
+Before: TestClass<TestClass, TestClass<Void, Int, Float, String>>
+After:  TestClass<TestClass, TestClass<Void, Int, Float, String>, String>
+
+-- Index Print --
+TestClass<TestClass, TestClass<Void, Int, Float, String>> - 2
+TestClass - 0
+TestClass - 1
+TestClass - 2