Selaa lähdekoodia

Detect nulls in structs (#11099)

* Detect nulls in structs

* Move to loose

* Improve issue test
RblSb 4 kuukautta sitten
vanhempi
commit
fae6f1a9cc

+ 32 - 16
src/typing/nullSafety.ml

@@ -1076,8 +1076,26 @@ class expr_checker mode immediate_execution report =
 			E.g.: `Array<Null<String>>` vs `Array<String>` returns `true`, but also adds a compilation error.
 		*)
 		method can_pass_expr expr to_type p =
+			let try_unify expr to_type =
+				if self#is_nullable_expr expr && not (is_nullable_type ~dynamic_is_nullable:true to_type) then
+					false
+				else begin
+					let expr_type = unfold_null expr.etype in
+					try
+						new unificator#unify expr_type to_type;
+						true
+					with
+						| Safety_error err ->
+							self#error ("Cannot unify " ^ (str_type expr_type) ^ " with " ^ (str_type to_type)) [p; expr.epos];
+							(* returning `true` because error is already logged in the line above *)
+							true
+						| e ->
+							fail ~msg:"Null safety unification failure" expr.epos __POS__
+				end
+			in
 			match expr.eexpr, to_type with
 				| TLocal v, _ when contains_unsafe_meta v.v_meta -> true
+				| TObjectDecl fields, TAbstract ({ a_path = ([],"Null") }, [TAnon to_type])
 				| TObjectDecl fields, TAnon to_type ->
 					List.for_all
 						(fun ((name, _, _), field_expr) ->
@@ -1086,22 +1104,20 @@ class expr_checker mode immediate_execution report =
 								self#can_pass_expr field_expr field_to_type.cf_type p
 							with Not_found -> false)
 						fields
-				| _, _ ->
-					if self#is_nullable_expr expr && not (is_nullable_type ~dynamic_is_nullable:true to_type) then
-						false
-					else begin
-						let expr_type = unfold_null expr.etype in
-						try
-							new unificator#unify expr_type to_type;
-							true
-						with
-							| Safety_error err ->
-								self#error ("Cannot unify " ^ (str_type expr_type) ^ " with " ^ (str_type to_type)) [p; expr.epos];
-								(* returning `true` because error is already logged in the line above *)
-								true
-							| e ->
-								fail ~msg:"Null safety unification failure" expr.epos __POS__
-					end
+				| TObjectDecl fields, TAbstract ({ a_path = ([],"Null") }, [TType (t,tl)])
+				| TObjectDecl fields, TType (t,tl) ->
+					(match follow_without_null t.t_type with
+							| TAnon to_type ->
+								List.for_all
+									(fun ((name, _, _), field_expr) ->
+										try
+											let field_to_type = PMap.find name to_type.a_fields in
+											self#can_pass_expr field_expr field_to_type.cf_type p
+										with Not_found -> false)
+									fields
+							| _ -> try_unify expr to_type
+					)
+				| _, _ -> try_unify expr to_type
 		(**
 			Should be called for the root expressions of a method or for then initialization expressions of fields.
 		*)

+ 1 - 1
tests/misc/projects/Issue10147/Main.hx

@@ -5,7 +5,7 @@ typedef HasANullString = {
 	text:Null<String>
 }
 
-@:nullSafety(StrictThreaded)
+@:nullSafety
 class Main {
 	static function main() {
 		final has:HasAString = {text: null};

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

@@ -1,3 +1,4 @@
+Main.hx:11: characters 3-39 : Null safety: Cannot assign nullable value here.
 Main.hx:14: characters 3-32 : Null safety: Cannot unify { text : Null<String> } with HasAString
 Main.hx:17: characters 3-35 : Null safety: Cannot unify { text : Null<String> } with { text : String }
 Main.hx:20: characters 30-38 : Null safety: Cannot use nullable value of Null<String> as an item in Array<String>

+ 32 - 0
tests/nullsafety/src/cases/TestLoose.hx

@@ -2,6 +2,12 @@ package cases;
 
 import Validator.shouldFail;
 
+typedef NotNullAnon = {
+	a:String
+}
+
+typedef NotNullAnonRef = NotNullAnon;
+
 class TestLoose {
 	static var staticVar:Null<String>;
 	var instanceVar:Null<String>;
@@ -85,6 +91,32 @@ class TestLoose {
 		}
 	}
 
+	static function objectDecl_nullInField_shouldFail():Void {
+		shouldFail({
+			final value:Null<{a:String}> = {a: null};
+		});
+		final value:Map<Int, Null<{a:String}>> = [
+			1 => shouldFail({a: null})
+		];
+	}
+
+	static function objectDecl_nullInTypedef_shouldFail():Void {
+		shouldFail({
+			final value:NotNullAnon = {a: null};
+		});
+		final value:Map<Int, Null<NotNullAnon>> = [
+			1 => shouldFail({a: null})
+		];
+		var value:NotNullAnon = {a: ""};
+		shouldFail(value = {a: null});
+
+		final value:Map<Int, Null<NotNullAnonRef>> = [
+			1 => shouldFail({a: null})
+		];
+		var value:Null<NotNullAnonRef> = {a: ""};
+		shouldFail(value = {a: null});
+	}
+
 	static function testIssue8442() {
 		function from(array: Array<Float>) {
 			return array.length;