Bläddra i källkod

Better null safety error range for anon fields (#12188)

* Better null safety error range for anon fields

* dots
RblSb 3 månader sedan
förälder
incheckning
4cf1f81f96

+ 16 - 16
src/typing/nullSafety.ml

@@ -1072,8 +1072,10 @@ class expr_checker mode immediate_execution report =
 					is_nullable_type e.etype && not (local_safety#is_safe e)
 		(**
 			Check if `expr` can be passed to a place where `to_type` is expected.
-			This method has side effect: it logs an error if `expr` has a type parameter incompatible with the type parameter of `to_type`.
-			E.g.: `Array<Null<String>>` vs `Array<String>` returns `true`, but also adds a compilation error.
+			This method has side effects:
+			- it logs an error if `expr` has a type parameter incompatible with the type parameter of `to_type`.
+				E.g.: `Array<Null<String>>` vs `Array<String>` returns `true`, but also adds a compilation error.
+			- it logs an error on anon structure field nullability mismatch to report specific field error and returns `true`.
 		*)
 		method can_pass_expr expr to_type p =
 			let try_unify expr to_type =
@@ -1093,28 +1095,26 @@ class expr_checker mode immediate_execution report =
 							fail ~msg:"Null safety unification failure" expr.epos __POS__
 				end
 			in
+			let check_anon_fields fields to_type =
+				List.fold_left (fun acc ((name, _, _), field_expr) ->
+					try
+						let field_to_type = PMap.find name to_type.a_fields in
+						let field_pos = field_expr.epos in
+						if not (self#can_pass_expr field_expr field_to_type.cf_type field_pos) then
+							self#error "Cannot assign nullable value here." [field_pos];
+						acc && true
+					with Not_found -> false) true fields
+			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) ->
-							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
+					check_anon_fields fields to_type
 				| 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
+								check_anon_fields fields to_type
 							| _ -> try_unify expr to_type
 					)
 				| _, _ -> try_unify expr to_type

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

@@ -1,4 +1,4 @@
-Main.hx:11: characters 3-39 : Null safety: Cannot assign nullable value here.
+Main.hx:11: characters 33-37 : 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>

+ 33 - 2
tests/nullsafety/src/cases/TestStrict.hx

@@ -30,6 +30,11 @@ typedef AnonAsStruct = {
 	?optional:String
 }
 
+typedef AnonDefaultNever = {
+	var name(default, never):String;
+	var version(default, never):String;
+}
+
 /** Test `@:nullSafety(Off)` is respected on fields */
 class UnsafeFields {
 	@:nullSafety(Off) var unsafeVar:String = null;
@@ -662,10 +667,10 @@ class TestStrict {
 	}
 
 	static function objectDecl_passObjWithNullabelFieldToObjWithNotNullableField_shouldFail(?a:String) {
-		shouldFail(var o:{field:String} = {field:a});
+		var o:{field:String} = {field:shouldFail(a)};
 		shouldFail(o = new TestStrict('')); //Test has `field:Null<String>`
 		var arr = (['', a]:Array<Null<String>>);
-		shouldFail(var q:{field:Array<String>} = {field:arr});
+		var q:{field:Array<String>} = {field:shouldFail(arr)};
 		shouldFail(var v:{value:Array<String>} = new Generic(arr));
 	}
 
@@ -1040,6 +1045,32 @@ class TestStrict {
 	}
 }
 
+private class AnonFields {
+	var nullableStr:Null<String> = "";
+
+	final anon:AnonDefaultNever = {
+		name: "",
+		// TODO should fail
+		version: null
+	};
+	final anon2:{name:String, version:String} = {
+		name: "",
+		version: shouldFail(null)
+	};
+
+	function reportNullableFieldValues():Null<AnonDefaultNever> {
+		final nullStr:Null<String> = null;
+		final object:{field:String} = {field: shouldFail(null)};
+		final object:{field:String} = {field: shouldFail(nullStr)};
+		final object:{field:String} = {field: shouldFail(nullableStr)};
+		final obj:AnonDefaultNever = {
+			name: "name",
+			version: shouldFail(null),
+		}
+		return {name: "foo", version: shouldFail(null)};
+	}
+}
+
 private class FinalNullableFields {
 	static public final staticVar:Null<String> = "hello";
 	public final instanceVar:Null<String> = "world";