Browse Source

Ensure macro "from" method returns compatible type (#8463)

* ensure macro "from" method returns compatible type

* try to unify return type of @:from macro with an expected abstract type

* also pass expected type to type_expr

* nothing to see here

* set the expected type to macro-generated expression to make it compatible with that type for the outer-world

* no need to unify a monomorph produced by a macro @:from

* Insert a cast

* make `@:from macro` behave just like normal `@:from`
Aleksandr Kuzmenko 6 years ago
parent
commit
8a2c508391

+ 17 - 6
src/context/abstractCast.ml

@@ -7,25 +7,36 @@ open Error
 
 
 let cast_stack = new_rec_stack()
 let cast_stack = new_rec_stack()
 
 
-let make_static_call ctx c cf a pl args t p =
+let rec make_static_call ctx c cf a pl args t p =
 	if cf.cf_kind = Method MethMacro then begin
 	if cf.cf_kind = Method MethMacro then begin
 		match args with
 		match args with
 			| [e] ->
 			| [e] ->
 				let e,f = push_this ctx e in
 				let e,f = push_this ctx e in
 				ctx.with_type_stack <- (WithType.with_type t) :: ctx.with_type_stack;
 				ctx.with_type_stack <- (WithType.with_type t) :: ctx.with_type_stack;
 				let e = match ctx.g.do_macro ctx MExpr c.cl_path cf.cf_name [e] p with
 				let e = match ctx.g.do_macro ctx MExpr c.cl_path cf.cf_name [e] p with
-					| Some e -> type_expr ctx e WithType.value
+					| Some e -> type_expr ctx e (WithType.with_type t)
 					| None ->  type_expr ctx (EConst (Ident "null"),p) WithType.value
 					| None ->  type_expr ctx (EConst (Ident "null"),p) WithType.value
 				in
 				in
+				let e = cast_or_unify ctx t e p in
 				ctx.with_type_stack <- List.tl ctx.with_type_stack;
 				ctx.with_type_stack <- List.tl ctx.with_type_stack;
 				f();
 				f();
 				e
 				e
 			| _ -> assert false
 			| _ -> assert false
 	end else
 	end else
-		make_static_call ctx c cf (apply_params a.a_params pl) args t p
+		Typecore.make_static_call ctx c cf (apply_params a.a_params pl) args t p
 
 
-let do_check_cast ctx tleft eright p =
+and do_check_cast ctx tleft eright p =
 	let recurse cf f =
 	let recurse cf f =
+		(*
+			Without this special check for macro @:from methods we will always get "Recursive implicit cast" error
+			unlike non-macro @:from methods, which generate unification errors if no other @:from methods are involved.
+		*)
+		if cf.cf_kind = Method MethMacro then begin
+			match cast_stack.rec_stack with
+			| previous_from :: _ when previous_from == cf ->
+				raise (Error (Unify [cannot_unify eright.etype tleft], eright.epos));
+			| _ -> ()
+		end;
 		if cf == ctx.curfield || rec_stack_memq cf cast_stack then error "Recursive implicit cast" p;
 		if cf == ctx.curfield || rec_stack_memq cf cast_stack then error "Recursive implicit cast" p;
 		rec_stack_loop cast_stack cf f ()
 		rec_stack_loop cast_stack cf f ()
 	in
 	in
@@ -80,7 +91,7 @@ let do_check_cast ctx tleft eright p =
 		loop [] tleft eright.etype
 		loop [] tleft eright.etype
 	end
 	end
 
 
-let cast_or_unify_raise ctx tleft eright p =
+and cast_or_unify_raise ctx tleft eright p =
 	try
 	try
 		(* can't do that anymore because this might miss macro calls (#4315) *)
 		(* can't do that anymore because this might miss macro calls (#4315) *)
 		(* if ctx.com.display <> DMNone then raise Not_found; *)
 		(* if ctx.com.display <> DMNone then raise Not_found; *)
@@ -89,7 +100,7 @@ let cast_or_unify_raise ctx tleft eright p =
 		unify_raise ctx eright.etype tleft p;
 		unify_raise ctx eright.etype tleft p;
 		eright
 		eright
 
 
-let cast_or_unify ctx tleft eright p =
+and cast_or_unify ctx tleft eright p =
 	try
 	try
 		cast_or_unify_raise ctx tleft eright p
 		cast_or_unify_raise ctx tleft eright p
 	with Error (Unify l,p) ->
 	with Error (Unify l,p) ->

+ 18 - 0
tests/misc/projects/Issue5880/Main.hx

@@ -0,0 +1,18 @@
+abstract Ref<T>({ v : T }) {
+
+	@:from macro static function from(v) {
+		return macro { somethingNotV : $v };
+	}
+
+}
+
+class Main {
+
+	static function main() {
+		#if !macro
+		var r : Ref<Int> = 10;
+		trace(r);
+		#end
+	}
+
+}

+ 18 - 0
tests/misc/projects/Issue5880/Main2.hx

@@ -0,0 +1,18 @@
+abstract Ref2<T>(T) {
+
+	@:from macro static function from(v) {
+		return v;
+	}
+
+}
+
+class Main2 {
+
+	static function main() {
+		#if !macro
+		var r : Ref2<String> = 10;
+		trace(r);
+		#end
+	}
+
+}

+ 17 - 0
tests/misc/projects/Issue5880/Main3.hx

@@ -0,0 +1,17 @@
+abstract Ref3(Bool) {
+
+	@:from macro static function from(v) {
+		return v;
+	}
+
+}
+
+class Main3 {
+
+	static function main() {
+		#if !macro
+		var r : Ref3 = 10;
+		#end
+	}
+
+}

+ 18 - 0
tests/misc/projects/Issue5880/Main4.hx

@@ -0,0 +1,18 @@
+abstract Ref(Array<Dynamic>) {
+
+	@:from macro static function from(v) {
+		return v;
+	}
+
+}
+
+class Main4 {
+
+	static function main() {
+		#if !macro
+		var r : Ref = ["foo", 12];
+		trace(r);
+		#end
+	}
+
+}

+ 21 - 0
tests/misc/projects/Issue5880/Main5.hx

@@ -0,0 +1,21 @@
+abstract Ref<T>({ v : T }) {
+#if !macro
+	@:from static public function fromV<T>(v:{v:T}):Ref<T> {
+		return [v];
+	}
+#end
+
+	@:from macro static function from(v) {
+		return macro { v : $v };
+	}
+}
+
+class Main5 {
+
+	static function main() {
+		#if !macro
+		var r : Ref<Int> = 10;
+		#end
+	}
+
+}

+ 21 - 0
tests/misc/projects/Issue5880/MainSuccess.hx

@@ -0,0 +1,21 @@
+abstract Ref<T>({ v : T }) {
+
+	@:from macro static function from(v) {
+		return macro new Ref($v);
+	}
+
+	public function new(v:T) {
+		this = { v : v }
+	}
+}
+
+class MainSuccess {
+
+	static function main() {
+		#if !macro
+		var r : Ref<Int> = 10;
+		trace(r);
+		#end
+	}
+
+}

+ 1 - 0
tests/misc/projects/Issue5880/compile-fail.hxml

@@ -0,0 +1 @@
+--main Main

+ 2 - 0
tests/misc/projects/Issue5880/compile-fail.hxml.stderr

@@ -0,0 +1,2 @@
+Main.hx:4: characters 16-38 : { somethingNotV : Int } should be Ref<Int>
+Main.hx:9: lines 9-18 : Defined in this class

+ 1 - 0
tests/misc/projects/Issue5880/compile.hxml

@@ -0,0 +1 @@
+--main MainSuccess

+ 1 - 0
tests/misc/projects/Issue5880/compile2-fail.hxml

@@ -0,0 +1 @@
+--main Main2

+ 1 - 0
tests/misc/projects/Issue5880/compile2-fail.hxml.stderr

@@ -0,0 +1 @@
+Main2.hx:13: characters 26-28 : Int should be Ref2<String>

+ 1 - 0
tests/misc/projects/Issue5880/compile3-fail.hxml

@@ -0,0 +1 @@
+--main Main3

+ 1 - 0
tests/misc/projects/Issue5880/compile3-fail.hxml.stderr

@@ -0,0 +1 @@
+Main3.hx:13: characters 18-20 : Int should be Ref3

+ 1 - 0
tests/misc/projects/Issue5880/compile4-fail.hxml

@@ -0,0 +1 @@
+--main Main4

+ 2 - 0
tests/misc/projects/Issue5880/compile4-fail.hxml.stderr

@@ -0,0 +1,2 @@
+Main4.hx:13: characters 25-27 : Arrays of mixed types are only allowed if the type is forced to Array<Dynamic>
+Main4.hx:13: characters 25-27 : Int should be String

+ 1 - 0
tests/misc/projects/Issue5880/compile5-fail.hxml

@@ -0,0 +1 @@
+--main Main5

+ 2 - 0
tests/misc/projects/Issue5880/compile5-fail.hxml.stderr

@@ -0,0 +1,2 @@
+Main5.hx:4: characters 10-13 : Array<{ v : fromV.T }> should be fromV.T
+Main5.hx:4: characters 3-13 : Recursive implicit cast

+ 15 - 3
tests/unit/src/unit/issues/misc/Issue4306Macro.hx

@@ -7,21 +7,33 @@ import haxe.macro.Type;
 using haxe.macro.Tools;
 using haxe.macro.Tools;
 
 
 abstract TypeName(String) to String {
 abstract TypeName(String) to String {
+	public inline function new(s:String) {
+		this = s;
+	}
+
 	@:from static macro function fromE(e:Expr) {
 	@:from static macro function fromE(e:Expr) {
-		return macro $v{Context.typeof(e).toString()};
+		return macro new TypeName($v{Context.typeof(e).toString()});
 	}
 	}
 }
 }
 
 
 abstract Arity(Int) to Int {
 abstract Arity(Int) to Int {
+	public inline function new(i:Int) {
+		this = i;
+	}
+
 	@:from static macro function fromE(e:Expr) {
 	@:from static macro function fromE(e:Expr) {
 		return switch (Context.typeof(e).follow()) {
 		return switch (Context.typeof(e).follow()) {
-			case TFun(tl, _): macro $v{tl.length};
+			case TFun(tl, _): macro new Arity($v{tl.length});
 			case _: Context.error("Expected function type", e.pos);
 			case _: Context.error("Expected function type", e.pos);
 		}
 		}
 	}
 	}
 }
 }
 
 
 abstract EnumListener<T>(String) to String {
 abstract EnumListener<T>(String) to String {
+	public inline function new(s:String) {
+		this = s;
+	}
+
 	@:from static macro function fromE(e:Expr) {
 	@:from static macro function fromE(e:Expr) {
 		var e = Context.typeExpr(e);
 		var e = Context.typeExpr(e);
 		var ef = switch (e.expr) {
 		var ef = switch (e.expr) {
@@ -39,6 +51,6 @@ abstract EnumListener<T>(String) to String {
 			case _: Context.error("Something went wrong, again", e.pos);
 			case _: Context.error("Something went wrong, again", e.pos);
 		}
 		}
 		Context.unify(t, t2);
 		Context.unify(t, t2);
-		return macro $v{ef.name};
+		return macro new EnumListener($v{ef.name});
 	}
 	}
 }
 }