Browse Source

[typer] make `final` fields invariant

closes #7039
Simon Krajewski 7 years ago
parent
commit
660aaafe9a

+ 1 - 0
extra/CHANGES.txt

@@ -2,6 +2,7 @@ XXXX-XX-XX:
 
 
 	General improvements and optimizations:
 	General improvements and optimizations:
 
 
+	all : made `final` on structure fields invariant (#7039)
 	php : Optimized haxe.ds.Vector (VectorData is not Array anymore)
 	php : Optimized haxe.ds.Vector (VectorData is not Array anymore)
 	php : Optimized `Map.copy()` and `Array.copy()`
 	php : Optimized `Map.copy()` and `Array.copy()`
 	php : Support native PHP generators. See `php.Syntax.yield()` and `php.Generator`
 	php : Support native PHP generators. See `php.Syntax.yield()` and `php.Generator`

+ 2 - 0
src/core/error.ml

@@ -66,6 +66,8 @@ let unify_error_msg ctx = function
 		"Constraint check failure for " ^ name
 		"Constraint check failure for " ^ name
 	| Missing_overload (cf, t) ->
 	| Missing_overload (cf, t) ->
 		cf.cf_name ^ " has no overload for " ^ s_type ctx t
 		cf.cf_name ^ " has no overload for " ^ s_type ctx t
+	| FinalInvariance ->
+		"Cannot unify final and non-final fields"
 	| Unify_custom msg ->
 	| Unify_custom msg ->
 		msg
 		msg
 
 

+ 9 - 6
src/core/type.ml

@@ -1670,6 +1670,7 @@ type unify_error =
 	| Invariant_parameter of t * t
 	| Invariant_parameter of t * t
 	| Constraint_failure of string
 	| Constraint_failure of string
 	| Missing_overload of tclass_field * t
 	| Missing_overload of tclass_field * t
+	| FinalInvariance (* nice band name *)
 	| Unify_custom of string
 	| Unify_custom of string
 
 
 exception Unify_error of unify_error list
 exception Unify_error of unify_error list
@@ -2006,7 +2007,7 @@ let rec unify a b =
 					unify_new_monos := !monos @ !unify_new_monos;
 					unify_new_monos := !monos @ !unify_new_monos;
 					rec_stack unify_stack (ft,f2.cf_type)
 					rec_stack unify_stack (ft,f2.cf_type)
 						(fun (a2,b2) -> fast_eq b2 f2.cf_type && fast_eq_mono !unify_new_monos ft a2)
 						(fun (a2,b2) -> fast_eq b2 f2.cf_type && fast_eq_mono !unify_new_monos ft a2)
-						(fun() -> try unify_with_access ft f2 with e -> unify_new_monos := old_monos; raise e)
+						(fun() -> try unify_with_access f1 ft f2 with e -> unify_new_monos := old_monos; raise e)
 						(fun l -> error (invalid_field n :: l));
 						(fun l -> error (invalid_field n :: l));
 					unify_new_monos := old_monos;
 					unify_new_monos := old_monos;
 				| Method MethNormal | Method MethInline | Var { v_write = AccNo } | Var { v_write = AccNever } ->
 				| Method MethNormal | Method MethInline | Var { v_write = AccNo } | Var { v_write = AccNever } ->
@@ -2015,13 +2016,13 @@ let rec unify a b =
 					unify_new_monos := !monos @ !unify_new_monos;
 					unify_new_monos := !monos @ !unify_new_monos;
 					rec_stack unify_stack (f2.cf_type,ft)
 					rec_stack unify_stack (f2.cf_type,ft)
 						(fun(a2,b2) -> fast_eq_mono !unify_new_monos b2 ft && fast_eq f2.cf_type a2)
 						(fun(a2,b2) -> fast_eq_mono !unify_new_monos b2 ft && fast_eq f2.cf_type a2)
-						(fun() -> try unify_with_access ft f2 with e -> unify_new_monos := old_monos; raise e)
+						(fun() -> try unify_with_access f1 ft f2 with e -> unify_new_monos := old_monos; raise e)
 						(fun l -> error (invalid_field n :: l));
 						(fun l -> error (invalid_field n :: l));
 					unify_new_monos := old_monos;
 					unify_new_monos := old_monos;
 				| _ ->
 				| _ ->
 					(* will use fast_eq, which have its own stack *)
 					(* will use fast_eq, which have its own stack *)
 					try
 					try
-						unify_with_access ft f2
+						unify_with_access f1 ft f2
 					with
 					with
 						Unify_error l ->
 						Unify_error l ->
 							error (invalid_field n :: l));
 							error (invalid_field n :: l));
@@ -2152,7 +2153,7 @@ and unify_anons a b a1 a2 =
 				| _ -> error [invalid_kind n f1.cf_kind f2.cf_kind]);
 				| _ -> error [invalid_kind n f1.cf_kind f2.cf_kind]);
 			if f2.cf_public && not f1.cf_public then error [invalid_visibility n];
 			if f2.cf_public && not f1.cf_public then error [invalid_visibility n];
 			try
 			try
-				unify_with_access (field_type f1) f2;
+				unify_with_access f1 (field_type f1) f2;
 				(match !(a1.a_status) with
 				(match !(a1.a_status) with
 				| Statics c when not (Meta.has Meta.MaybeUsed f1.cf_meta) -> f1.cf_meta <- (Meta.MaybeUsed,[],f1.cf_pos) :: f1.cf_meta
 				| Statics c when not (Meta.has Meta.MaybeUsed f1.cf_meta) -> f1.cf_meta <- (Meta.MaybeUsed,[],f1.cf_pos) :: f1.cf_meta
 				| _ -> ());
 				| _ -> ());
@@ -2291,12 +2292,14 @@ and with_variance f t1 t2 =
 	with Unify_error _ ->
 	with Unify_error _ ->
 		raise (Unify_error l)
 		raise (Unify_error l)
 
 
-and unify_with_access t1 f2 =
+and unify_with_access f1 t1 f2 =
 	match f2.cf_kind with
 	match f2.cf_kind with
 	(* write only *)
 	(* write only *)
 	| Var { v_read = AccNo } | Var { v_read = AccNever } -> unify f2.cf_type t1
 	| Var { v_read = AccNo } | Var { v_read = AccNever } -> unify f2.cf_type t1
 	(* read only *)
 	(* read only *)
-	| Method MethNormal | Method MethInline | Var { v_write = AccNo } | Var { v_write = AccNever } -> unify t1 f2.cf_type
+	| Method MethNormal | Method MethInline | Var { v_write = AccNo } | Var { v_write = AccNever } ->
+		if f1.cf_final <> f2.cf_final then raise (Unify_error [FinalInvariance]);
+		unify t1 f2.cf_type
 	(* read/write *)
 	(* read/write *)
 	| _ -> with_variance (type_eq EqBothDynamic) t1 f2.cf_type
 	| _ -> with_variance (type_eq EqBothDynamic) t1 f2.cf_type
 
 

+ 4 - 4
std/haxe/Unserializer.hx

@@ -476,14 +476,14 @@ class Unserializer {
 
 
 private class DefaultResolver {
 private class DefaultResolver {
 	public function new() {}
 	public function new() {}
-	@:final public inline function resolveClass(name:String):Class<Dynamic> return Type.resolveClass(name);
-	@:final public inline function resolveEnum(name:String):Enum<Dynamic> return Type.resolveEnum(name);
+	public inline function resolveClass(name:String):Class<Dynamic> return Type.resolveClass(name);
+	public inline function resolveEnum(name:String):Enum<Dynamic> return Type.resolveEnum(name);
 }
 }
 
 
 private class NullResolver {
 private class NullResolver {
 	function new() {}
 	function new() {}
-	@:final public inline function resolveClass(name:String):Class<Dynamic> return null;
-	@:final public inline function resolveEnum(name:String):Enum<Dynamic> return null;
+	public inline function resolveClass(name:String):Class<Dynamic> return null;
+	public inline function resolveEnum(name:String):Enum<Dynamic> return null;
 	public static var instance(get,null):NullResolver;
 	public static var instance(get,null):NullResolver;
 	inline static function get_instance():NullResolver {
 	inline static function get_instance():NullResolver {
 		if (instance == null) instance = new NullResolver();
 		if (instance == null) instance = new NullResolver();

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

@@ -0,0 +1,6 @@
+class Main {
+	static public function main() {
+		var o = { foo: 4 }
+		var o2:{ final foo:Int; } = o;
+	}
+}

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

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

+ 3 - 0
tests/misc/projects/Issue7039/compile-fail.hxml.stderr

@@ -0,0 +1,3 @@
+Main.hx:4: characters 3-33 : { foo : Int } should be { foo : Int }
+Main.hx:4: characters 3-33 : Invalid type for field foo :
+Main.hx:4: characters 3-33 : Cannot unify final and non-final fields