Skip to content

Commit

Permalink
[flow] refine opaque type bounds in type guard refinement
Browse files Browse the repository at this point in the history
Summary:
Before this change opaque types were not treated in any special way during type guard refinement. This meant that we would get stuck with some awkward intersections with the guard type, which would very frequently lead to unexpected errors.

This diff applies a similar approach to the one followed for refinement of generics, where we refine the bound of the generic instead. In the case of opaque types we refine the super and underlying type instead. If there is no super type, we refine `mixed` which is equivalent to using the guard type as super type.

Changelog: [fix] refining type guards with opaque types will now refine the general type (example of code that used to fail before, but now passes: [try-Flow](https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkO4AX34kA0SQAOhJWT5VgACACCAB4ACoAPm5AF5ucBuQBqAAkspWxmQ3IAjLxuU1cDJlYLufTuQAfSUy+WK7DKgBM6s12u5uvpAG5OWbuQAhIWiiUCkVOqAaJ5UbDc2EYACOMm5LoA8h7fS6AMIe8VupNGmM+zmc-24QPcmAPZRwaDc2nukUACkgIkYqXN3MTIoAlMqqzWFiWSCmM1BOfnHvCoJH2hIVeWbXXE0USmxhc2NRA4GxJZzuSWYNzy6Wx9wZI3G8vB6vubZ7Nz8Crk+PuRhO+6p6VhQ7VwB6Z-cylLtLcnDBrZ0FdHieEhnhayp3sUD6Xju2BPtyr7clAEDAcC3ImJQECUNeUC0NyIgYdgAH0pyRE9rABYDkO9gWuW+7AABq4ugAylIFQIPGyaJvYphQAgj70ZGPLMdxCCusm7pcaxfGHqufaFsWaDblqdZCax8a0fxR5rhuW7jnuB6aQZgHQKe55QUp16dipPGurB8GIchg5oRhWE4XhLAaYZx7GcB+CgdyVkiWZMi2W+9lYah1DOcCrn4R5q4kZpJH0rkIANCYJBFlASQNAADFYFoAKwAGxWDlID0kAA))

Reviewed By: SamChou19815

Differential Revision: D67264811

fbshipit-source-id: d185c9f6084cd08873f5039eba4c2f9589d15641
  • Loading branch information
panagosg7 authored and facebook-github-bot committed Dec 16, 2024
1 parent 3a13953 commit 5f4e844
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 3 deletions.
17 changes: 14 additions & 3 deletions src/typing/predicate_kit.ml
Original file line number Diff line number Diff line change
Expand Up @@ -482,9 +482,20 @@ and intersect cx t1 t2 =
t1
else if speculative_subtyping_succeeds cx t2 t1 then
t2
else
let r = update_desc_reason invalidate_rtype_alias (TypeUtil.reason_of_t t1) in
IntersectionT (r, InterRep.make t2 t1 [])
else (
match t1 with
| OpaqueT (r, ({ super_t; underlying_t; _ } as opaquetype)) ->
(* Apply the refinement on super and underlying type of opaque type.
* Preserve opaque_id to retain compatibility with original type. *)
let super_t =
Some (Base.Option.value_map super_t ~default:t2 ~f:(fun t -> intersect cx t t2))
in
let underlying_t = Base.Option.map ~f:(fun t -> intersect cx t t2) underlying_t in
OpaqueT (r, { opaquetype with underlying_t; super_t })
| _ ->
let r = update_desc_reason invalidate_rtype_alias (TypeUtil.reason_of_t t1) in
IntersectionT (r, InterRep.make t2 t1 [])
)

(* This utility is expected to be used when negating the refinement of a type [t1]
* with a type guard `x is t2`. The only case considered here is that of t1 <: t2.
Expand Down
26 changes: 26 additions & 0 deletions tests/opaque_refinements/type_guards.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// @flow

type A<T> = { +$$type: 1, value: T } | { +$$type: 2, value: T };
type B<T> = A<T>;
declare opaque type O<T>;
type C<T> = B<T> | O<T>;

declare function isB<T>(compute: C<T>): compute is B<T>;

function test1(value: C<mixed>): void {
if (isB(value)) {
const _1 = value as B<mixed>;
const _2: B<mixed> = value;
}
}

function test2() {
type StringC = C<string>;
type StringB = B<string>;
function b(value: StringC) {
if (isB(value)) {
const _1 = value as StringB;
const _2: StringB = value;
}
}
}

0 comments on commit 5f4e844

Please sign in to comment.