Skip to content

Commit bb2f267

Browse files
SamChou19815facebook-github-bot
authored andcommitted
[name_resolver] Avoiding creating phi nodes when merging with havoc if the variable is initialized
Summary: Currently, we only have this behavior when the variable declaration is annotated, but we still create phi nodes if the variable is initialized. There is no good reason for such split, except that we want to maintain the behavior with the inference before new environment. However, this decision can cause serious perf issues under LTI, when the union type won't just merge together in a single type, but remained to be unions. Consider the following example: ``` let data1: Array<string> = []; let data = data1 if (true) data = data.filter(s => s.substring(0)); if (true) data = data.filter(s => s.substring(0)); // repeat n times if (true) data = data.filter(s => s.substring(0)); (data: Array<string>); ``` At the point of final cast, the name_resolver will tell us the read of `data` is phi node, and the type will be obtained by flowing n types into a result tvar. Pre-LTI, these n types will coalesce into one type, so the perf is not terrible, but under LTI, the result will be a size-n union type. The issue also exists for each if branch, where the size of union type is linearly increasing. Under LTI, we will also do contextual typing for `s`, so we will do work in each item in the if-chain proportional to the size of the union type, which leads to a quadratic blowup. To fix it, I extend the avoiding phi-node logic to include initialized variables as well. This also fits in the LTI story, since under LTI every expressions, like annotations, cannot have their type widened. Changelog: [errors] When a variable is initialized at declaration site, subsequent assignment in a conditional branch will no longer cause later reads to get a union type. This is already the case if the variable is annotated. As a result of this change, you might get new type errors. Example: https://flow.org/try/#0DYUwLgBAJghmMQLwQEQEYUG4BQBLAZhABRgBOAriAJTRwLJo6zyYQD0bEADqSAG64A9uQDOwAJ4RckAO65gwCAAsYfEBDDiu6gHbkAtgCMQpCAB8IIsrh0BzADQQdgmVNnzFKtRq3qrpG1tsIA Reviewed By: panagosg7 Differential Revision: D43134095 (d4f94ee) ------------------------------------------------------------------------ (from ad59e13768a9567e487b642b9a54ac6b6b34027e) fbshipit-source-id: 48f15ff0e74cd98880e89160b918212a5f419749
1 parent cec5df5 commit bb2f267

File tree

13 files changed

+108
-329
lines changed

13 files changed

+108
-329
lines changed

src/analysis/env_builder/__tests__/env_builder_refinement_test.ml

Lines changed: 29 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1537,9 +1537,7 @@ try {
15371537
Global invariant
15381538
};
15391539
(5, 2) to (5, 3) => {
1540-
(1, 4) to (1, 5): (`x`),
1541-
(3, 24) to (3, 25): (`x`),
1542-
{refinement = Not (Maybe); writes = (1, 4) to (1, 5): (`x`)}
1540+
(1, 4) to (1, 5): (`x`)
15431541
}] |}]
15441542

15451543
let%expect_test "nested_logical_throw_and" =
@@ -1664,8 +1662,7 @@ x;|};
16641662
[%expect {|
16651663
[
16661664
(5, 0) to (5, 1) => {
1667-
(1, 4) to (1, 5): (`x`),
1668-
(3, 2) to (3, 3): (`x`)
1665+
(1, 4) to (1, 5): (`x`)
16691666
}] |}]
16701667

16711668
let%expect_test "if_no_else_statement_with_assignment_simple_with_annotation" =
@@ -1937,13 +1934,10 @@ x;|};
19371934
{refinement = Not (Maybe); writes = (2, 4) to (2, 5): (`y`)}
19381935
};
19391936
(9, 0) to (9, 1) => {
1940-
(2, 4) to (2, 5): (`y`),
1941-
{refinement = Not (Maybe); writes = (2, 4) to (2, 5): (`y`)},
1942-
{refinement = Not (Not (Maybe)); writes = (2, 4) to (2, 5): (`y`)}
1937+
(2, 4) to (2, 5): (`y`)
19431938
};
19441939
(10, 0) to (10, 1) => {
1945-
(1, 4) to (1, 5): (`x`),
1946-
{refinement = Not (Maybe); writes = (1, 4) to (1, 5): (`x`)}
1940+
(1, 4) to (1, 5): (`x`)
19471941
}] |}]
19481942

19491943
let%expect_test "while_with_runtime_writes" =
@@ -1978,7 +1972,7 @@ x;|};
19781972
(2, 4) to (2, 5): (`y`)
19791973
};
19801974
(10, 0) to (10, 1) => {
1981-
{refinement = Not (Not (Maybe)); writes = (1, 4) to (1, 5): (`x`),(5, 4) to (5, 5): (`x`)}
1975+
{refinement = Not (Not (Maybe)); writes = (1, 4) to (1, 5): (`x`)}
19821976
}] |}]
19831977

19841978
let%expect_test "while_with_var_write" =
@@ -2042,9 +2036,7 @@ x;|};
20422036
{refinement = Not (Maybe); writes = (2, 4) to (2, 5): (`y`)}
20432037
};
20442038
(9, 0) to (9, 1) => {
2045-
(2, 4) to (2, 5): (`y`),
2046-
{refinement = Not (Maybe); writes = (2, 4) to (2, 5): (`y`)},
2047-
{refinement = Not (Not (Maybe)); writes = (2, 4) to (2, 5): (`y`)}
2039+
(2, 4) to (2, 5): (`y`)
20482040
};
20492041
(10, 0) to (10, 1) => {
20502042
{refinement = Not (Not (Maybe)); writes = (1, 4) to (1, 5): (`x`)}
@@ -2074,7 +2066,7 @@ x;|};
20742066
{refinement = Not (3); writes = {refinement = Not (Maybe); writes = (1, 4) to (1, 5): (`x`)}}
20752067
};
20762068
(8, 0) to (8, 1) => {
2077-
{refinement = Not (Not (Maybe)); writes = (1, 4) to (1, 5): (`x`),{refinement = 3; writes = (1, 4) to (1, 5): (`x`)},{refinement = Not (3); writes = (1, 4) to (1, 5): (`x`)}}
2069+
{refinement = Not (Not (Maybe)); writes = (1, 4) to (1, 5): (`x`)}
20782070
}] |}]
20792071

20802072
let%expect_test "do_while" =
@@ -2160,14 +2152,13 @@ x;|};
21602152
(2, 4) to (2, 5): (`y`)
21612153
};
21622154
(8, 9) to (8, 10) => {
2163-
(1, 4) to (1, 5): (`x`),
2164-
(5, 4) to (5, 5): (`x`)
2155+
(1, 4) to (1, 5): (`x`)
21652156
};
21662157
(9, 0) to (9, 1) => {
21672158
(2, 4) to (2, 5): (`y`)
21682159
};
21692160
(10, 0) to (10, 1) => {
2170-
{refinement = Not (Not (Maybe)); writes = (1, 4) to (1, 5): (`x`),(5, 4) to (5, 5): (`x`)}
2161+
{refinement = Not (Not (Maybe)); writes = (1, 4) to (1, 5): (`x`)}
21712162
}] |}]
21722163

21732164
let%expect_test "do_while_continue" =
@@ -2320,13 +2311,10 @@ x;|};
23202311
{refinement = Not (Maybe); writes = (2, 4) to (2, 5): (`y`)}
23212312
};
23222313
(9, 0) to (9, 1) => {
2323-
(2, 4) to (2, 5): (`y`),
2324-
{refinement = Not (Maybe); writes = (2, 4) to (2, 5): (`y`)},
2325-
{refinement = Not (Not (Maybe)); writes = (2, 4) to (2, 5): (`y`)}
2314+
(2, 4) to (2, 5): (`y`)
23262315
};
23272316
(10, 0) to (10, 1) => {
2328-
(1, 4) to (1, 5): (`x`),
2329-
{refinement = Not (Maybe); writes = (1, 4) to (1, 5): (`x`)}
2317+
(1, 4) to (1, 5): (`x`)
23302318
}] |}]
23312319

23322320
let%expect_test "for_no_init_no_update_with_runtime_writes" =
@@ -2361,7 +2349,7 @@ x;|};
23612349
(2, 4) to (2, 5): (`y`)
23622350
};
23632351
(10, 0) to (10, 1) => {
2364-
{refinement = Not (Not (Maybe)); writes = (1, 4) to (1, 5): (`x`),(5, 4) to (5, 5): (`x`)}
2352+
{refinement = Not (Not (Maybe)); writes = (1, 4) to (1, 5): (`x`)}
23652353
}] |}]
23662354

23672355
let%expect_test "for_no_init_no_update_continue" =
@@ -2411,9 +2399,7 @@ x;|};
24112399
{refinement = Not (Maybe); writes = (2, 4) to (2, 5): (`y`)}
24122400
};
24132401
(9, 0) to (9, 1) => {
2414-
(2, 4) to (2, 5): (`y`),
2415-
{refinement = Not (Maybe); writes = (2, 4) to (2, 5): (`y`)},
2416-
{refinement = Not (Not (Maybe)); writes = (2, 4) to (2, 5): (`y`)}
2402+
(2, 4) to (2, 5): (`y`)
24172403
};
24182404
(10, 0) to (10, 1) => {
24192405
{refinement = Not (Not (Maybe)); writes = (1, 4) to (1, 5): (`x`)}
@@ -2443,9 +2429,7 @@ x;|};
24432429
{refinement = Not (3); writes = {refinement = Not (Maybe); writes = (1, 4) to (1, 5): (`x`)}}
24442430
};
24452431
(8, 0) to (8, 1) => {
2446-
(1, 4) to (1, 5): (`x`),
2447-
{refinement = 3; writes = {refinement = Not (Maybe); writes = (1, 4) to (1, 5): (`x`)}},
2448-
{refinement = Not (3); writes = (1, 4) to (1, 5): (`x`)}
2432+
(1, 4) to (1, 5): (`x`)
24492433
}] |}]
24502434

24512435
let%expect_test "for_shadow" =
@@ -2524,9 +2508,7 @@ y;|};
25242508
{refinement = Not (Maybe); writes = (1, 4) to (1, 5): (`y`)}
25252509
};
25262510
(8, 0) to (8, 1) => {
2527-
(1, 4) to (1, 5): (`y`),
2528-
{refinement = Not (Maybe); writes = (1, 4) to (1, 5): (`y`)},
2529-
{refinement = Not (Not (Maybe)); writes = (1, 4) to (1, 5): (`y`)}
2511+
(1, 4) to (1, 5): (`y`)
25302512
}] |}]
25312513

25322514
let%expect_test "for_with_runtime_writes" =
@@ -2604,9 +2586,7 @@ y;|};
26042586
{refinement = Not (Maybe); writes = (1, 4) to (1, 5): (`y`)}
26052587
};
26062588
(8, 0) to (8, 1) => {
2607-
(1, 4) to (1, 5): (`y`),
2608-
{refinement = Not (Maybe); writes = (1, 4) to (1, 5): (`y`)},
2609-
{refinement = Not (Not (Maybe)); writes = (1, 4) to (1, 5): (`y`)}
2589+
(1, 4) to (1, 5): (`y`)
26102590
}] |}]
26112591

26122592
let%expect_test "no_havoc_before_write_seen" =
@@ -2846,9 +2826,7 @@ y;|};
28462826
{refinement = Not (Maybe); writes = (1, 4) to (1, 5): (`y`)}
28472827
};
28482828
(8, 0) to (8, 1) => {
2849-
(1, 4) to (1, 5): (`y`),
2850-
{refinement = Not (Maybe); writes = (1, 4) to (1, 5): (`y`)},
2851-
{refinement = Not (Not (Maybe)); writes = (1, 4) to (1, 5): (`y`)}
2829+
(1, 4) to (1, 5): (`y`)
28522830
}] |}]
28532831

28542832
let%expect_test "for_in_with_runtime_writes" =
@@ -2880,8 +2858,7 @@ x;|};
28802858
(2, 4) to (2, 5): (`y`)
28812859
};
28822860
(10, 0) to (10, 1) => {
2883-
(1, 4) to (1, 5): (`x`),
2884-
(5, 4) to (5, 5): (`x`)
2861+
(1, 4) to (1, 5): (`x`)
28852862
}] |}]
28862863

28872864
let%expect_test "for_in_continue" =
@@ -2920,9 +2897,7 @@ y;|};
29202897
{refinement = Not (Maybe); writes = (1, 4) to (1, 5): (`y`)}
29212898
};
29222899
(8, 0) to (8, 1) => {
2923-
(1, 4) to (1, 5): (`y`),
2924-
{refinement = Not (Maybe); writes = (1, 4) to (1, 5): (`y`)},
2925-
{refinement = Not (Not (Maybe)); writes = (1, 4) to (1, 5): (`y`)}
2900+
(1, 4) to (1, 5): (`y`)
29262901
}] |}]
29272902

29282903
let%expect_test "for_in_reassign_right" =
@@ -2937,8 +2912,7 @@ stuff;|};
29372912
(1, 4) to (1, 9): (`stuff`)
29382913
};
29392914
(5, 0) to (5, 5) => {
2940-
(1, 4) to (1, 9): (`stuff`),
2941-
(3, 2) to (3, 7): (`stuff`)
2915+
(1, 4) to (1, 9): (`stuff`)
29422916
}] |}]
29432917

29442918
let%expect_test "for_of" =
@@ -3057,9 +3031,7 @@ y;|};
30573031
{refinement = Not (Maybe); writes = (1, 4) to (1, 5): (`y`)}
30583032
};
30593033
(8, 0) to (8, 1) => {
3060-
(1, 4) to (1, 5): (`y`),
3061-
{refinement = Not (Maybe); writes = (1, 4) to (1, 5): (`y`)},
3062-
{refinement = Not (Not (Maybe)); writes = (1, 4) to (1, 5): (`y`)}
3034+
(1, 4) to (1, 5): (`y`)
30633035
}] |}]
30643036

30653037
let%expect_test "for_of_with_runtime_writes" =
@@ -3091,8 +3063,7 @@ x;|};
30913063
(2, 4) to (2, 5): (`y`)
30923064
};
30933065
(10, 0) to (10, 1) => {
3094-
(1, 4) to (1, 5): (`x`),
3095-
(5, 4) to (5, 5): (`x`)
3066+
(1, 4) to (1, 5): (`x`)
30963067
}] |}]
30973068

30983069
let%expect_test "for_of_continue" =
@@ -3131,9 +3102,7 @@ y;|};
31313102
{refinement = Not (Maybe); writes = (1, 4) to (1, 5): (`y`)}
31323103
};
31333104
(8, 0) to (8, 1) => {
3134-
(1, 4) to (1, 5): (`y`),
3135-
{refinement = Not (Maybe); writes = (1, 4) to (1, 5): (`y`)},
3136-
{refinement = Not (Not (Maybe)); writes = (1, 4) to (1, 5): (`y`)}
3105+
(1, 4) to (1, 5): (`y`)
31373106
}] |}]
31383107

31393108
let%expect_test "for_of_reassign_right" =
@@ -3148,8 +3117,7 @@ stuff;|};
31483117
(1, 4) to (1, 9): (`stuff`)
31493118
};
31503119
(5, 0) to (5, 5) => {
3151-
(1, 4) to (1, 9): (`stuff`),
3152-
(3, 2) to (3, 7): (`stuff`)
3120+
(1, 4) to (1, 9): (`stuff`)
31533121
}] |}]
31543122

31553123
let%expect_test "invariant" =
@@ -3254,8 +3222,7 @@ try {
32543222
Global invariant
32553223
};
32563224
(8, 2) to (8, 3) => {
3257-
(1, 4) to (1, 5): (`x`),
3258-
(5, 21) to (5, 22): (`x`)
3225+
(1, 4) to (1, 5): (`x`)
32593226
}] |}]
32603227

32613228
let%expect_test "switch_empty" =
@@ -4149,8 +4116,7 @@ x.foo; // No refinement
41494116
(3, 4) to (3, 5): (`x`)
41504117
};
41514118
(8, 0) to (8, 1) => {
4152-
(3, 4) to (3, 5): (`x`),
4153-
{refinement = SentinelR foo; writes = (3, 4) to (3, 5): (`x`)}
4119+
(3, 4) to (3, 5): (`x`)
41544120
}] |}]
41554121

41564122
let%expect_test "heap_refinement_while_loop_subject_changed" =
@@ -4175,8 +4141,7 @@ x.foo; // No heap refinement here
41754141
{refinement = 3; writes = projection at (3, 7) to (3, 12)}
41764142
};
41774143
(8, 0) to (8, 1) => {
4178-
(2, 4) to (2, 5): (`x`),
4179-
(5, 2) to (5, 3): (`x`)
4144+
(2, 4) to (2, 5): (`x`)
41804145
}] |}]
41814146

41824147
let%expect_test "heap_refinement_while_loop_projection_changed" =
@@ -4211,8 +4176,7 @@ x.foo;
42114176
{refinement = 4; writes = {refinement = 3; writes = projection at (4, 7) to (4, 12)}}
42124177
};
42134178
(9, 0) to (9, 1) => {
4214-
(3, 4) to (3, 5): (`x`),
4215-
{refinement = SentinelR foo; writes = {refinement = SentinelR foo; writes = (3, 4) to (3, 5): (`x`)}}
4179+
(3, 4) to (3, 5): (`x`)
42164180
}] |}]
42174181

42184182
let%expect_test "heap_refinement_while_loop_negated" =
@@ -4276,7 +4240,7 @@ x.foo;
42764240
{refinement = 4; writes = {refinement = 3; writes = projection at (3, 7) to (3, 12)}}
42774241
};
42784242
(9, 0) to (9, 1) => {
4279-
{refinement = Not (SentinelR foo); writes = (2, 4) to (2, 5): (`x`),{refinement = SentinelR foo; writes = (2, 4) to (2, 5): (`x`)}}
4243+
{refinement = Not (SentinelR foo); writes = (2, 4) to (2, 5): (`x`)}
42804244
};
42814245
(9, 0) to (9, 5) => {
42824246
{refinement = Not (3); writes = projection at (3, 7) to (3, 12),{refinement = 4; writes = projection at (3, 7) to (3, 12)}}

src/analysis/env_builder/name_resolver.ml

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,10 +1321,23 @@ module Make
13211321
match def_loc with
13221322
| Some loc ->
13231323
(match Env_api.Provider_api.providers_of_def provider_info loc with
1324-
(* We only use havoc as the merge if the providers that generate the havoc val are
1325-
annotated. Only when the havoc val is annotated, we know for certain that it wouldn't
1326-
be widened in the branches we are merging together. *)
1327-
| Some { Env_api.Provider_api.state = Find_providers.AnnotatedVar _; _ }
1324+
(* We only use havoc as the merge if the variable's provider is at the declaration site.
1325+
i.e. annotated or initialized at declaration. This prevents us from creating large
1326+
union types in later if chains.
1327+
1328+
e.g.
1329+
let x = init; // or let x: annot = ...;
1330+
if (...) x = expr_1;
1331+
if (...) x = expr_2;
1332+
// ...
1333+
if (...) x = expr_n;
1334+
x should still has type of init, instead of a size-n+1 union type. *)
1335+
| Some
1336+
{
1337+
Env_api.Provider_api.state =
1338+
Find_providers.AnnotatedVar _ | Find_providers.InitializedVar;
1339+
_;
1340+
}
13281341
when can_merge_with_havoc v1 v2 || can_merge_with_havoc v2 v1 ->
13291342
havoc
13301343
| _ -> Val.merge v1 v2)

tests/badly_positioned_flow_use_op/b.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,14 @@ declare function foo(data: Data): void;
77
const o = {
88
fun: foo,
99
}
10-
/*
11-
The error position for this one is bad,
12-
as it points to line 5.
13-
*/
10+
1411
function test1(b: boolean) {
1512
var data = { x: 0 };
1613
if (b) data = { x: 0, z: 0 };
17-
o['fun'](data);
14+
o['fun'](data); // no error
1815
}
1916
/*The error position for this one is ok.*/
2017
function test2(b: boolean) {
2118
var data = { z: 0 };
22-
o['fun'](data);
19+
o['fun'](data); // Error: { z: 0 } ~> Data
2320
}

0 commit comments

Comments
 (0)