Răsfoiți Sursa

[nullsafety] check if a getter/setter for non-nullable property return safe values

Alexander Kuzmenko 6 ani în urmă
părinte
comite
8aaf780b2a
2 a modificat fișierele cu 40 adăugiri și 6 ștergeri
  1. 32 5
      src/typing/nullSafety.ml
  2. 8 1
      tests/nullsafety/src/cases/Test.hx

+ 32 - 5
src/typing/nullSafety.ml

@@ -1213,18 +1213,45 @@ class class_checker cls immediate_execution report  =
 				Option.may (fun f -> Option.may (fun e -> print_endline (s_expr (fun t -> "") e)) f.cf_expr) cls.cl_constructor; *)
 			if is_safe_class && (not cls.cl_extern) && (not cls.cl_interface) then
 				self#check_var_fields;
-			let check_field f =
+			let check_field is_static f =
 				if self#is_in_safety f then begin
 					(* if f.cf_name = "return_assignNonNullable_shouldPass" then
 						Option.may (fun e -> print_endline (s_expr str_type e)) f.cf_expr; *)
-					Option.may checker#check_root_expr f.cf_expr
+					Option.may checker#check_root_expr f.cf_expr;
+					self#check_accessors is_static f
 				end
 			in
 			if is_safe_class then
 				Option.may checker#check_root_expr cls.cl_init;
-			Option.may check_field cls.cl_constructor;
-			List.iter check_field cls.cl_ordered_fields;
-			List.iter check_field cls.cl_ordered_statics;
+			Option.may (check_field false) cls.cl_constructor;
+			List.iter (check_field false) cls.cl_ordered_fields;
+			List.iter (check_field true) cls.cl_ordered_statics;
+		(**
+			Check if a getter/setter for non-nullable property return safe values.
+			E.g.
+			```
+			var str(get,never):String;
+			function get_str() return (null:Null<String>); //should fail null safety check
+			```
+		*)
+		method private check_accessors is_static field =
+			match field.cf_kind with
+				| Var { v_read = read_access; v_write = write_access } when not (is_nullable_type field.cf_type) ->
+					let fields = if is_static then cls.cl_statics else cls.cl_fields in
+					let check_accessor prefix =
+						let accessor =
+							try Some (PMap.find (prefix ^ field.cf_name) fields)
+							with Not_found -> None
+						in
+						match accessor with
+							| Some { cf_expr = Some ({ eexpr = TFunction fn } as accessor_expr) } ->
+								let fn = { fn with tf_type = field.cf_type } in
+								checker#check_root_expr { accessor_expr with eexpr = TFunction fn }
+							| _ -> ()
+					in
+					if read_access = AccCall then check_accessor "get_";
+					if write_access = AccCall then check_accessor "set_"
+				| _ -> ()
 		(**
 			Check if field should be checked by null safety
 		*)

+ 8 - 1
tests/nullsafety/src/cases/Test.hx

@@ -112,6 +112,14 @@ class Test {
 	var initializedInAllBranchesOfConstructor:String;
 	@:shouldFail var initializedInSomeBranchesOfConstructor:String;
 
+	var str(get,set):String;
+	function get_str() {
+		shouldFail(return (null:Null<String>));
+	}
+	function set_str(v) {
+		shouldFail(return (v:Null<String>));
+	}
+
 	/**
 	 *  Null safety should work in __init__ functions
 	 */
@@ -787,7 +795,6 @@ class Test {
 		shouldFail(var s:String = FinalNullableFields.staticVar);
 	}
 
-	// static var n:Null<String>;
 	static function return_assignNonNullable_shouldPass(?n:String):String {
 		return n = 'hello';
 	}