[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 Error {declared_nullability= what; can_be_narrowed_to= by_rhs_upper_bound}
then 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