Skip to content

Commit

Permalink
[flow] Refine null/undefined/maybe into opaque types
Browse files Browse the repository at this point in the history
Summary: When refining an opaque type by `maybe`, we always just produce null+undefined today. In reality, we can do better by recursing into the definition of the opaque type (something that we currently do inconsistently for some refinements but not others.

Reviewed By: panagosg7

Differential Revision: D69076822

fbshipit-source-id: a0508324cbb17d71966e2c5432f59f6ffe3809c2
  • Loading branch information
mvitousek authored and facebook-github-bot committed Feb 4, 2025
1 parent 2807d4b commit 1baf490
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 27 deletions.
15 changes: 8 additions & 7 deletions src/typing/type_filter.ml
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ let rec not_truthy cx t =
| t -> unchanged_result t

let rec maybe cx = function
| OpaqueT (r, _) -> UnionT (r, UnionRep.make (NullT.why r) (VoidT.why r) []) |> changed_result
| OpaqueT (r, opq) -> filter_opaque (maybe cx) r opq
| UnionT (r, rep) -> recurse_into_union cx maybe (r, UnionRep.members rep)
| MaybeT (r, _) -> UnionT (r, UnionRep.make (NullT.why r) (VoidT.why r) []) |> changed_result
| DefT (r, MixedT Mixed_everything) ->
Expand Down Expand Up @@ -217,8 +217,8 @@ let rec not_maybe cx = function
DefT (r, MixedT Mixed_non_maybe) |> changed_result
| t -> unchanged_result t

let null = function
| OpaqueT (r, _)
let rec null = function
| OpaqueT (r, opq) -> filter_opaque null r opq
| OptionalT { reason = _; type_ = MaybeT (r, _); use_desc = _ }
| MaybeT (r, _) ->
NullT.why r |> changed_result
Expand All @@ -233,6 +233,7 @@ let null = function
EmptyT.why reason |> changed_result

let rec not_null cx = function
| OpaqueT (r, opq) -> filter_opaque (not_null cx) r opq
| MaybeT (r, t) -> UnionT (r, UnionRep.make (VoidT.why r) t []) |> changed_result
| OptionalT { reason; type_ = t; use_desc } ->
let (TypeFilterResult { type_ = t; changed }) = not_null cx t in
Expand All @@ -243,10 +244,9 @@ let rec not_null cx = function
| DefT (r, MixedT Mixed_non_void) -> DefT (r, MixedT Mixed_non_maybe) |> changed_result
| t -> unchanged_result t

let undefined = function
| OpaqueT (r, _)
| MaybeT (r, _) ->
VoidT.why r |> changed_result
let rec undefined = function
| OpaqueT (r, opq) -> filter_opaque undefined r opq
| MaybeT (r, _) -> VoidT.why r |> changed_result
| DefT (_, VoidT) as t -> unchanged_result t
| OptionalT { reason = r; type_ = _; use_desc } ->
VoidT.why_with_use_desc ~use_desc r |> changed_result
Expand All @@ -260,6 +260,7 @@ let undefined = function
EmptyT.why reason |> changed_result

let rec not_undefined cx = function
| OpaqueT (r, opq) -> filter_opaque (not_undefined cx) r opq
| MaybeT (r, t) -> UnionT (r, UnionRep.make (NullT.why r) t []) |> changed_result
| OptionalT { reason = _; type_ = t; use_desc = _ } -> not_undefined cx t
| UnionT (r, rep) -> recurse_into_union cx not_undefined (r, UnionRep.members rep)
Expand Down
11 changes: 11 additions & 0 deletions tests/refinements/opaque_type.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,14 @@ function test_refine_to_nullish() {
foo as empty; // error
}
}

{
declare opaque type ME: number
declare let elem: ME;

if (elem !== null) {
elem = elem;
}

elem as ME;
}
37 changes: 17 additions & 20 deletions tests/refinements/refinements.exp
Original file line number Diff line number Diff line change
Expand Up @@ -11149,24 +11149,7 @@ References:

Error ---------------------------------------------------------------------------------------------- opaque_type.js:24:5

Cannot cast `foo` to empty because null [1] is incompatible with empty [2]. [incompatible-cast]

opaque_type.js:24:5
24| foo as empty; // error
^^^

References:
opaque_type.js:18:23
18| declare const foo: ?Foo;
^^^ [1]
opaque_type.js:24:12
24| foo as empty; // error
^^^^^ [2]


Error ---------------------------------------------------------------------------------------------- opaque_type.js:24:5

Cannot cast `foo` to empty because undefined [1] is incompatible with empty [2]. [incompatible-cast]
Cannot cast `foo` to empty because `Foo` [1] is incompatible with empty [2]. [incompatible-cast]

opaque_type.js:24:5
24| foo as empty; // error
Expand Down Expand Up @@ -11228,7 +11211,7 @@ References:

Error ---------------------------------------------------------------------------------------------- opaque_type.js:28:5

Cannot cast `foo` to empty because undefined [1] is incompatible with empty [2]. [incompatible-cast]
Cannot cast `foo` to empty because `Foo` [1] is incompatible with empty [2]. [incompatible-cast]

opaque_type.js:28:5
28| foo as empty; // error
Expand Down Expand Up @@ -11290,7 +11273,7 @@ References:

Error ---------------------------------------------------------------------------------------------- opaque_type.js:32:5

Cannot cast `foo` to empty because null [1] is incompatible with empty [2]. [incompatible-cast]
Cannot cast `foo` to empty because `Foo` [1] is incompatible with empty [2]. [incompatible-cast]

opaque_type.js:32:5
32| foo as empty; // error
Expand Down Expand Up @@ -11319,6 +11302,20 @@ References:
^^^^^^^^^^^^ [1]


Error ---------------------------------------------------------------------------------------------- opaque_type.js:44:3

Refined at [1]

opaque_type.js:44:3
44| elem as ME;
^^^^

References:
opaque_type.js:40:7
40| if (elem !== null) {
^^^^^^^^^^^^^ [1]


Error ------------------------------------------------------------------------------------------------- property.js:5:12

Refined at [1]
Expand Down

0 comments on commit 1baf490

Please sign in to comment.