Skip to content

Commit

Permalink
Don't mutate literal object types during subtyping
Browse files Browse the repository at this point in the history
Summary:
Changelog:
[errors] We will now emit new errors in places where an object literal has literal types, but doesn't have an annotation. You can usually fix these errors by adding annotations to object literals.
[lib] Improved libdef for fs.readdir and fs.readdirSync for better overload resolution

This behavior was introduced to catch the following cases:

```
var a = { p: 0 };
var b: { p: number | string }; // a.p <- number | string
b.p = "";
(a.p : number); // at runtime, a.p is a string due to aliasing
```

or, similarly with optional properties:

```
var a = { p: 0 };
var b: { p?: number } = a; // a.p <- optional number
b.p = undefined;
(a.p : number); // at runtime, a.p is void due to aliasing
```

While this does introduce some measure of safety in these examples, it also
introduces order dependence to constraints, which violates a fundamental
assumption of the type checker.

LTI will not require this hacky approximation. Instead, we will rely on
contextual typing to provide precise inference for literal types and require
annotations where context is unavailable.

Removing this behavior now will remove some safety, but unblocks development of
LTI. Part of LTI changes the order in which constraints are visited by the
solver, which causes spurious errors due to this change.

Reviewed By: samwgoldman

Differential Revision: D37501336

fbshipit-source-id: 382de86b99d33e3e7fdc75b2260a405e513a5ec5
  • Loading branch information
SamChou19815 authored and facebook-github-bot committed Jul 5, 2022
1 parent b026691 commit 0b9c9bb
Show file tree
Hide file tree
Showing 14 changed files with 208 additions and 924 deletions.
4 changes: 2 additions & 2 deletions lib/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -1085,7 +1085,7 @@ declare module "fs" {
): void;
declare function readdir(
path: string,
options: { encoding?: string, withFileTypes?: true, ... },
options: { encoding?: string, withFileTypes: true, ... },
callback: (err: ?ErrnoError, files: Array<Dirent>) => void
): void;
declare function readdir(
Expand All @@ -1098,7 +1098,7 @@ declare module "fs" {
): Array<string>;
declare function readdirSync(
path: string,
options?: string | { encoding?: string, withFileTypes?: true, ... }
options?: string | { encoding?: string, withFileTypes: true, ... }
): Array<Dirent>;
declare function close(fd: number, callback: (err: ?ErrnoError) => void): void;
declare function closeSync(fd: number): void;
Expand Down
18 changes: 6 additions & 12 deletions src/typing/subtyping_kit.ml
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,12 @@ module Make (Flow : INPUT) : OUTPUT = struct
in
match (Context.get_prop cx lflds s, ldict) with
| (Some lp, _) ->
if lit then (
(* prop from unaliased LB: check <:, then make exact *)
(match (Property.read_t lp, Property.read_t up) with
if lit then
(* prop from unaliased LB: check <: *)
match (Property.read_t lp, Property.read_t up) with
| (Some lt, Some ut) -> rec_flow cx trace (lt, UseT (use_op, ut))
| _ -> ());
speculative_object_write cx lflds s up
) else
| _ -> ()
else
(* prop from aliased LB *)
rec_flow_p cx ~trace ~use_op lreason ureason propref (lp, up)
| (None, Some { key; value; dict_polarity; _ }) when not (is_dictionary_exempt s) ->
Expand All @@ -287,12 +286,7 @@ module Make (Flow : INPUT) : OUTPUT = struct
| _ ->
(* property doesn't exist in inflowing type *)
(match up with
| Field (_, OptionalT _, _) when lit ->
(* if property is marked optional or otherwise has a maybe type,
and if inflowing type is a literal (i.e., it is not an
annotation), then we add it to the inflowing type as
an optional property *)
speculative_object_write cx lflds s up
| Field (_, OptionalT _, _) when lit -> ()
| Field (_, OptionalT _, Polarity.Positive)
when Obj_type.is_exact_or_sealed ureason lflags.obj_kind ->
rec_flow
Expand Down
14 changes: 7 additions & 7 deletions tests/new_merge/new_merge.exp
Original file line number Diff line number Diff line change
Expand Up @@ -406,20 +406,20 @@ References:

Error ----------------------------------------------------------------------------------------------------- main.js:85:2

Cannot cast `y19` to object type because undefined [1] is incompatible with number [2] in property `a`.
[incompatible-cast]
Cannot cast `y19` to object type because property `a` is missing in object literal [1] but exists in object type [2].
[prop-missing]

main.js:85:2
85| (y19: { a: number }); // error undefined ~> number
^^^

References:
main.js:84:13
84| (y19: { a?: number }); // okay
^^^^^^ [1]
main.js:85:12
spread.js:10:18
10| export const y = { ...t };
^^^^^^^^ [1]
main.js:85:7
85| (y19: { a: number }); // error undefined ~> number
^^^^^^ [2]
^^^^^^^^^^^^^ [2]


Error ----------------------------------------------------------------------------------------------------- main.js:89:2
Expand Down
68 changes: 51 additions & 17 deletions tests/object_widening/object_widening.exp
Original file line number Diff line number Diff line change
Expand Up @@ -201,67 +201,101 @@ References:

Error -------------------------------------------------------------------------------------------- type_widening.js:54:2

Cannot cast `b.foo` to number because undefined [1] is incompatible with number [2]. [incompatible-cast]
Cannot cast `b.foo` to number because possibly missing property `foo` in object literal [1] is incompatible with
number [2]. [incompatible-cast]

type_widening.js:54:2
54| (b.foo: number); // Error, foo does not appear in all branches
^^^^^

References:
type_widening.js:17:24
17| type AllOpt = { +foo?: number, +bar?: number, +baz?: number, +qux?: number, ... };
^^^^^^ [1]
type_widening.js:48:23
48| const b = spreadExact(y);
^ [1]
type_widening.js:54:9
54| (b.foo: number); // Error, foo does not appear in all branches
^^^^^^ [2]


Error -------------------------------------------------------------------------------------------- type_widening.js:55:2

Cannot cast `b.bar` to number because undefined [1] is incompatible with number [2]. [incompatible-cast]
Cannot cast `b.bar` to number because possibly missing property `bar` in object literal [1] is incompatible with
number [2]. [incompatible-cast]

type_widening.js:55:2
55| (b.bar: number); // Error, bar does not appear in all branches
^^^^^

References:
type_widening.js:17:39
17| type AllOpt = { +foo?: number, +bar?: number, +baz?: number, +qux?: number, ... };
^^^^^^ [1]
type_widening.js:48:23
48| const b = spreadExact(y);
^ [1]
type_widening.js:55:9
55| (b.bar: number); // Error, bar does not appear in all branches
^^^^^^ [2]


Error -------------------------------------------------------------------------------------------- type_widening.js:56:2

Cannot cast `b.baz` to number because undefined [1] is incompatible with number [2]. [incompatible-cast]
Cannot cast `b.baz` to number because possibly missing property `baz` in object literal [1] is incompatible with
number [2]. [incompatible-cast]

type_widening.js:56:2
56| (b.baz: number); // Error, baz does not appear in all branches
^^^^^

References:
type_widening.js:17:54
17| type AllOpt = { +foo?: number, +bar?: number, +baz?: number, +qux?: number, ... };
^^^^^^ [1]
type_widening.js:48:23
48| const b = spreadExact(y);
^ [1]
type_widening.js:56:9
56| (b.baz: number); // Error, baz does not appear in all branches
^^^^^^ [2]


Error -------------------------------------------------------------------------------------------- type_widening.js:56:2

Cannot cast `b.baz` to number because `void` (due to access of non-existent property `baz`) [1] is incompatible with
number [2]. [incompatible-cast]

type_widening.js:56:2
56| (b.baz: number); // Error, baz does not appear in all branches
^^^^^ [1]

References:
type_widening.js:56:9
56| (b.baz: number); // Error, baz does not appear in all branches
^^^^^^ [2]


Error -------------------------------------------------------------------------------------------- type_widening.js:57:2

Cannot cast `b.qux` to number because undefined [1] is incompatible with number [2]. [incompatible-cast]
Cannot cast `b.qux` to number because possibly missing property `qux` in object literal [1] is incompatible with
number [2]. [incompatible-cast]

type_widening.js:57:2
57| (b.qux: number); // Error, qux does not appear in all branches
^^^^^

References:
type_widening.js:17:69
17| type AllOpt = { +foo?: number, +bar?: number, +baz?: number, +qux?: number, ... };
^^^^^^ [1]
type_widening.js:48:23
48| const b = spreadExact(y);
^ [1]
type_widening.js:57:9
57| (b.qux: number); // Error, qux does not appear in all branches
^^^^^^ [2]


Error -------------------------------------------------------------------------------------------- type_widening.js:57:2

Cannot cast `b.qux` to number because `void` (due to access of non-existent property `qux`) [1] is incompatible with
number [2]. [incompatible-cast]

type_widening.js:57:2
57| (b.qux: number); // Error, qux does not appear in all branches
^^^^^ [1]

References:
type_widening.js:57:9
57| (b.qux: number); // Error, qux does not appear in all branches
^^^^^^ [2]
Expand Down Expand Up @@ -806,4 +840,4 @@ References:



Found 49 errors
Found 51 errors
Loading

0 comments on commit 0b9c9bb

Please sign in to comment.