@ -162,14 +162,6 @@ let check_field_assignment tenv find_canonical_duplicate curr_pdesc node instr_r
( Some instr_ref ) loc curr_pdesc
let is_nullable AnnotatedField . { annotated_type } =
match annotated_type . nullability with
| AnnotatedNullability . Nullable _ ->
true
| AnnotatedNullability . Nonnull _ ->
false
let is_nonnull AnnotatedField . { annotated_type } =
match annotated_type . nullability with
| AnnotatedNullability . Nullable _ ->
@ -178,14 +170,6 @@ let is_nonnull AnnotatedField.{annotated_type} =
true
(* Do we have evidence that the field is annotated as nullable? *)
let is_field_annotated_as_nullable annotated_field_opt =
(* If the field is not present, we optimistically assume it is not nullable.
TODO ( T54687014 ) investigate if this leads to unsoundness issues in practice
* )
Option . exists annotated_field_opt ~ f : is_nullable
(* Do we have evidence that the field is annotated as non-nullable? *)
let is_field_annotated_as_nonnull annotated_field_opt =
(* If the field is not present, we optimistically assume it is not nullable.
@ -211,9 +195,29 @@ let predicate_holds_for_some_typestate typestate_list field_name ~predicate =
List . exists typestate_list ~ f : ( convert_predicate predicate field_name )
(* Given a list of typestates, does a given predicate about the field hold for all of them? *)
let predicate_holds_for_all_typestates typestate_list field_name ~ predicate =
List . for_all typestate_list ~ f : ( convert_predicate predicate field_name )
(* Given the typestate and the field, what is the upper bound of field nullability in this typestate?
* )
let get_nullability_upper_bound_for_typestate proc_name field_name typestate =
let range_for_field = lookup_field_in_typestate proc_name field_name typestate in
match range_for_field with
| None ->
(* There is no information about the field type in typestate ( field was not assigned in all paths ) .
It gives the most generic upper bound .
* )
Nullability . top
(* We were able to lookup the field. Its nullability gives precise upper bound. *)
| Some ( _ , inferred_nullability , _ ) ->
InferredNullability . get_nullability inferred_nullability
(* Given the list of typestates ( each corresponding to the final result of executing of some function ) ,
and the field , what is the upper bound of field nullability joined over all typestates ?
* )
let get_nullability_upper_bound field_name typestate_list =
(* Join upper bounds for all typestates in the list *)
List . fold typestate_list ~ init : Nullability . Nonnull ~ f : ( fun acc ( proc_name , typestate ) ->
Nullability . join acc
( get_nullability_upper_bound_for_typestate proc_name field_name typestate ) )
(* * Check field initialization for a given constructor *)
@ -256,11 +260,9 @@ let check_constructor_initialization tenv find_canonical_duplicate curr_construc
(* TODO ( T54584721 ) This check is completely independent of the current constuctor we check.
This check should be moved out of this function . Until it is done ,
we issue one over - annotated warning per constructor , which does not make a lot of sense . * )
let is_field_assigned_to_nonnull_in_all_constructors () =
predicate_holds_for_all_typestates
( Lazy . force typestates_for_all_constructors_incl_current ) field_name
~ predicate : ( fun ( _ , nullability , _ ) -> InferredNullability . is_nonnull nullability
)
let field_nullability_upper_bound_over_all_typestates () =
get_nullability_upper_bound field_name
( Lazy . force typestates_for_all_constructors_incl_current )
in
let should_check_field_initialization =
let in_current_class =
@ -283,14 +285,21 @@ let check_constructor_initialization tenv find_canonical_duplicate curr_construc
( TypeErr . Field_not_initialized ( field_name , curr_constructor_pname ) )
None loc curr_constructor_pdesc ;
(* Check if field is over-annotated. *)
if
Config . eradicate_field_over_annotated
&& is_field_annotated_as_nullable annotated_field
&& is_field_assigned_to_nonnull_in_all_constructors ()
then
report_error tenv find_canonical_duplicate
( TypeErr . Field_over_annotated ( field_name , curr_constructor_pname ) )
None loc curr_constructor_pdesc )
match annotated_field with
| None ->
()
| Some annotated_field ->
if
Config . eradicate_field_over_annotated
&& NullsafeRules . is_overannotated
~ lhs :
( AnnotatedNullability . get_nullability
annotated_field . annotated_type . nullability )
~ rhs_upper_bound : ( field_nullability_upper_bound_over_all_typestates () )
then
report_error tenv find_canonical_duplicate
( TypeErr . Field_over_annotated ( field_name , curr_constructor_pname ) )
None loc curr_constructor_pdesc )
in
List . iter ~ f : do_field fields
| None ->
@ -311,18 +320,16 @@ let check_return_not_nullable tenv find_canonical_duplicate loc curr_pname curr_
None loc curr_pdesc
(* TODO ( T54308240 ) Consolidate return over annotated checks in NullsafeRules *)
let check_return_overrannotated tenv find_canonical_duplicate loc curr_pname curr_pdesc
( ret_signature : AnnotatedSignature . ret_signature ) ret_inferred_nullability =
let is_ret_inferred_nonnull = InferredNullability . is_nonnull ret_inferred_nullability in
let is_ret_annotated_nullable =
match ret_signature . ret_annotated_type . nullability with
| AnnotatedNullability . Nonnull _ ->
false
| AnnotatedNullability . Nullable _ ->
true
in
if is_ret_inferred_nonnull && is_ret_annotated_nullable then
(* Returning from a function is essentially an assignment the actual return value to the formal `return` *)
let lhs = AnnotatedNullability . get_nullability ret_signature . ret_annotated_type . nullability in
(* In our CFG implementation, there is only one place where we return from a function
( all execution flow joins are already made ) , hence inferreed nullability of returns gives us
correct upper bound .
* )
let rhs_upper_bound = InferredNullability . get_nullability ret_inferred_nullability in
if NullsafeRules . is_overannotated ~ lhs ~ rhs_upper_bound then
report_error tenv find_canonical_duplicate ( TypeErr . Return_over_annotated curr_pname ) None loc
curr_pdesc