[nullsafe] Introduce Null nullability type

Summary:
This will allow us tune nullsafe behavior in a more fine-grained way.
See e.g. a follow up diff when we report errors more clearly.

Reviewed By: ngorogiannis

Differential Revision: D18683747

fbshipit-source-id: 7b5c42a03
master
Mitya Lyubarskiy 5 years ago committed by Facebook Github Bot
parent 7d1959a5aa
commit 29ae8086ec

@ -17,7 +17,7 @@ type dereference_type =
let check ~is_strict_mode nullability = let check ~is_strict_mode nullability =
match nullability with match nullability with
| Nullability.Nullable -> | Nullability.Nullable | Nullability.Null ->
Error nullability Error nullability
| Nullability.DeclaredNonnull -> | Nullability.DeclaredNonnull ->
if is_strict_mode then Error nullability else Ok () if is_strict_mode then Error nullability else Ok ()

@ -14,12 +14,10 @@ let create origin = {nullability= TypeOrigin.get_nullability origin; origin}
let get_nullability {nullability} = nullability let get_nullability {nullability} = nullability
let is_nonnull_or_declared_nonnull {nullability} = let is_nonnull_or_declared_nonnull {nullability} =
match nullability with Nullable -> false | DeclaredNonnull -> true | Nonnull -> true match nullability with Nonnull | DeclaredNonnull -> true | _ -> false
let is_nonnull {nullability} = let is_nonnull {nullability} = match nullability with Nonnull -> true | _ -> false
match nullability with Nullable -> false | DeclaredNonnull -> false | Nonnull -> true
let to_string {nullability} = Printf.sprintf "[%s]" (Nullability.to_string nullability) let to_string {nullability} = Printf.sprintf "[%s]" (Nullability.to_string nullability)
@ -33,6 +31,10 @@ let join t1 t2 =
*) *)
let joined_origin = let joined_origin =
match (is_equal_to_t1, is_equal_to_t2) with match (is_equal_to_t1, is_equal_to_t2) with
| _ when Nullability.equal t1.nullability Nullability.Null ->
t1.origin
| _ when Nullability.equal t2.nullability Nullability.Null ->
t2.origin
| true, false -> | true, false ->
(* Nullability was fully determined by t1. *) (* Nullability was fully determined by t1. *)
t1.origin t1.origin

@ -8,6 +8,7 @@
open! IStd open! IStd
type t = type t =
| Null (** The only possible value for that type is null *)
| Nullable (** No guarantees on the nullability *) | Nullable (** No guarantees on the nullability *)
| DeclaredNonnull | DeclaredNonnull
(** The type comes from a signature that is annotated (explicitly or implicitly according to conventions) (** The type comes from a signature that is annotated (explicitly or implicitly according to conventions)
@ -24,6 +25,10 @@ let top = Nullable
let join x y = let join x y =
match (x, y) with match (x, y) with
| Null, Null ->
Null
| Null, _ | _, Null ->
Nullable
| Nullable, _ | _, Nullable -> | Nullable, _ | _, Nullable ->
Nullable Nullable
| DeclaredNonnull, _ | _, DeclaredNonnull -> | DeclaredNonnull, _ | _, DeclaredNonnull ->
@ -34,11 +39,9 @@ let join x y =
let is_subtype ~subtype ~supertype = equal (join subtype supertype) supertype let is_subtype ~subtype ~supertype = equal (join subtype supertype) supertype
let is_strict_subtype ~subtype ~supertype =
is_subtype ~subtype ~supertype && not (equal subtype supertype)
let to_string = function let to_string = function
| Null ->
"Null"
| Nullable -> | Nullable ->
"Nullable" "Nullable"
| DeclaredNonnull -> | DeclaredNonnull ->

@ -17,6 +17,7 @@ open! IStd
*) *)
type t = type t =
| Null (** The only possible value for that type is null *)
| Nullable (** No guarantees on the nullability *) | Nullable (** No guarantees on the nullability *)
| DeclaredNonnull | DeclaredNonnull
(** The type comes from a signature that is annotated (explicitly or implicitly according to conventions) (** The type comes from a signature that is annotated (explicitly or implicitly according to conventions)
@ -36,9 +37,6 @@ val is_subtype : subtype:t -> supertype:t -> bool
(** A is a subtype of B, if all values of A can be represented in B. (** A is a subtype of B, if all values of A can be represented in B.
Subtype relation is reflexive: everything is a subtype of itself. *) Subtype relation is reflexive: everything is a subtype of itself. *)
val is_strict_subtype : subtype:t -> supertype:t -> bool
(** The same as subtype, but non-reflexive version. *)
val join : t -> t -> t val join : t -> t -> t
(** Unique upper bound over two types: the most precise type that is a supertype of both. (** Unique upper bound over two types: the most precise type that is a supertype of both.
Practically, joins occur e.g. when two branches of execution flow are getting merged. *) Practically, joins occur e.g. when two branches of execution flow are getting merged. *)

@ -10,17 +10,18 @@ type violation = {declared_nullability: Nullability.t; can_be_narrowed_to: Nulla
[@@deriving compare] [@@deriving compare]
let check ~what ~by_rhs_upper_bound = let check ~what ~by_rhs_upper_bound =
if match (what, by_rhs_upper_bound) with
Nullability.is_strict_subtype ~subtype:by_rhs_upper_bound ~supertype:what | Nullability.Nullable, Nullability.Nonnull | Nullability.Nullable, Nullability.DeclaredNonnull ->
&& (* Suppress violations for anything apart from Nullable since such Error {declared_nullability= what; can_be_narrowed_to= by_rhs_upper_bound}
issues are not very actionable and/or clear for the user. | Nullability.Null, Nullability.Nonnull | Nullability.Null, Nullability.DeclaredNonnull ->
E.g. we technically can suggest changing [DeclaredNonnull] to [Nonnull], (* Null, Nonnull pais is an edge case that we don't expect to arise in practice for two reasons:
but in practice that requires strictification the code, which is a 1. The only way to declare something as Null is to declare it is java.lang.Void, but nullsafe
separate effort. does not know about it just yet.
*) 2. Even if it knew, such could should not compile: the only way it could compile if it returns `null` directly.
Nullability.equal what Nullable *)
then Error {declared_nullability= what; can_be_narrowed_to= by_rhs_upper_bound} Error {declared_nullability= what; can_be_narrowed_to= by_rhs_upper_bound}
else Ok () | _ ->
Ok ()
type violation_type = type violation_type =

@ -49,7 +49,7 @@ let equal = [%compare.equal: t]
let get_nullability = function let get_nullability = function
| NullConst _ -> | NullConst _ ->
Nullability.Nullable Nullability.Null
| NonnullConst _ | NonnullConst _
| This (* `this` can not be null according to Java rules *) | This (* `this` can not be null according to Java rules *)
| New (* In Java `new` always create a non-null object *) | New (* In Java `new` always create a non-null object *)

Loading…
Cancel
Save