-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Let/const #431
Let/const #431
Changes from 14 commits
f962a63
f5c56c4
2b27342
309d0da
760c1e9
d07d944
87a59ff
8fb878c
d12e8bf
222d411
6642876
5e063da
c6ddef1
6ed936c
03c8a4b
40fca43
d1d3b32
99e4262
882c81a
bbf8193
4659a41
fb281a2
5c27d4d
3c8267d
f109936
ab4dd92
c9594e2
222dc44
cc30bd1
6ad275a
f321c2d
614057a
b02c045
dde9473
6340fae
74934d2
8db94d6
888beed
f0ddfd3
7bab756
ff0c88c
273fa6f
1548e00
f36b6c1
47c1e69
9e6c28d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,7 +44,10 @@ let swap_changeset f = | |
changeset := newset; | ||
oldset | ||
|
||
let global_scope: scope = ref SMap.empty | ||
let global_scope: scope = { | ||
kind = VarScope; | ||
entries = ref SMap.empty; | ||
} | ||
|
||
let global_poison = ["eval"; "arguments"] | ||
let global_lexicals = [ | ||
|
@@ -60,12 +63,14 @@ let cache_global cx x reason = | |
else | ||
Flow_js.get_builtin cx x reason | ||
)) in | ||
global_scope := SMap.add x (create_env_entry t t None) !global_scope; | ||
let global_entries = global_scope.entries in | ||
global_entries := SMap.add x (create_env_entry t t None) !global_entries; | ||
cx.globals <- SSet.add x cx.globals; | ||
global_scope | ||
|
||
let find_global cx x reason = | ||
if (SMap.mem x !global_scope) | ||
let global_entries = global_scope.entries in | ||
if (SMap.mem x !global_entries) | ||
then | ||
global_scope | ||
else | ||
|
@@ -75,33 +80,58 @@ let find_env ?(for_type=false) cx x reason = | |
let rec loop = function | ||
| [] -> find_global cx x reason | ||
| scope::scopes -> | ||
(match SMap.get x !scope with | ||
let entries = scope.entries in | ||
(match SMap.get x !entries with | ||
| Some entry when (not entry.for_type || for_type) -> scope | ||
| _ -> loop scopes) | ||
in | ||
loop (!env) | ||
|
||
let get_from_scope x scope = | ||
SMap.find_unsafe x !scope | ||
let entries = scope.entries in | ||
SMap.find_unsafe x !entries | ||
|
||
let set_in_scope x entry scope = | ||
scope := !scope |> SMap.add x entry | ||
let entries = scope.entries in | ||
entries := !entries |> SMap.add x entry | ||
|
||
let exists_in_scope x scope = | ||
SMap.get x !scope | ||
let entries = scope.entries in | ||
SMap.get x !entries | ||
|
||
let set_in_env x entry scope_kind = | ||
let rec loop = function | ||
| [] -> set_in_scope x entry global_scope | ||
| scope::scopes -> | ||
if (scope_kind = LexicalScope || scope.kind = scope_kind) then | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Won't this cause Thinking about this a bit, I actually think we need to introduce one
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, if the entry is var-scoped, then Also, this same condition ensures that let-scoped variables will be installed into any kind of scope. (That's what the first part is all about, the second part ensures VarScope entries are installed only into VarScope scopes.) You'll find that both of those test cases work today with the existing changes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I think you're right -- I was getting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mind adding some [regression] tests covering those scenarios at some point? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will do. I'm confident existing tests indirectly cover this stuff, but it doesn't hurt to be explicit. |
||
set_in_scope x entry scope | ||
else | ||
loop scopes in | ||
loop (!env) | ||
|
||
let exists_in_env x scope_kind = | ||
let rec loop = function | ||
| [] -> exists_in_scope x global_scope | ||
| scope::scopes -> | ||
if (scope_kind = LexicalScope || scope.kind = scope_kind) then | ||
exists_in_scope x scope | ||
else | ||
loop scopes in | ||
loop (!env) | ||
|
||
let update_scope ?(for_type=false) x (specific_t, general_t) scope = | ||
let values = !scope in | ||
let new_entry = match SMap.get x values with | ||
let entries = scope.entries in | ||
let new_entry = match SMap.get x !entries with | ||
| Some {def_loc; for_type; _;} -> | ||
create_env_entry ~for_type specific_t general_t def_loc | ||
| None -> | ||
create_env_entry ~for_type specific_t general_t None | ||
in | ||
scope := values |> SMap.add x new_entry | ||
entries := !entries |> SMap.add x new_entry | ||
|
||
let unset_in_scope x scope = | ||
scope := !scope |> SMap.remove x | ||
let entries = scope.entries in | ||
entries := !entries |> SMap.remove x | ||
|
||
let read_env ?(for_type=false) cx x reason = | ||
find_env ~for_type cx x reason |> get_from_scope x | ||
|
@@ -116,9 +146,11 @@ let pop_env () = | |
env := List.tl !env | ||
|
||
let flat_env () = | ||
List.fold_left (fun acc x -> | ||
SMap.union acc !x | ||
) !global_scope !env | ||
let rec loop acc = function | ||
| { entries; _ }::bs -> loop (SMap.union acc !entries) bs | ||
| [] -> acc in | ||
let global_entries = global_scope.entries in | ||
loop !global_entries !env | ||
|
||
let switch_env scope = | ||
pop_env (); | ||
|
@@ -127,10 +159,9 @@ let switch_env scope = | |
let peek_env () = | ||
List.hd !env | ||
|
||
let init_env cx x shape = | ||
let scope = peek_env() in | ||
match exists_in_scope x scope with | ||
| None -> set_in_scope x shape scope | ||
let init_env cx x shape scope_kind = | ||
match exists_in_env x scope_kind with | ||
| None -> set_in_env x shape scope_kind | ||
| Some { general; for_type; def_loc; _; } when for_type <> shape.for_type -> | ||
(* When we have a value var shadowing a type var, replace the type var | ||
* with the value var *) | ||
|
@@ -146,7 +177,7 @@ let init_env cx x shape = | |
shadower_reason, "This binding is shadowing"; | ||
shadowed_reason, "which is a different sort of binding"; | ||
]; | ||
set_in_scope x shape scope; | ||
set_in_env x shape scope_kind; | ||
Flow_js.unify cx shape.general general | ||
| Some { general; _ } -> | ||
Flow_js.unify cx general shape.general | ||
|
@@ -179,8 +210,7 @@ let var_ref ?(for_type=false) cx x reason = | |
mod_reason_of_t (repos_reason p) t | ||
|
||
let get_refinement cx key r = | ||
let scope = peek_env () in | ||
match exists_in_scope key scope with | ||
match exists_in_env key VarScope with | ||
| Some { specific; _ } -> | ||
let pos = pos_of_reason r in | ||
let t = mod_reason_of_t (repos_reason pos) specific in | ||
|
@@ -195,7 +225,10 @@ let set_var ?(for_type=false) cx x specific_t reason = | |
write_env ~for_type cx x (specific_t, general) reason | ||
|
||
let clone_env ctx = | ||
List.map (fun scope -> ref !scope) ctx | ||
List.map (fun scope -> | ||
let { kind; entries } = scope in | ||
{ kind; entries = ref !entries } | ||
) ctx | ||
|
||
let update_frame cx ctx = | ||
let current_frame = List.hd !Flow_js.frames in | ||
|
@@ -214,12 +247,15 @@ let rec merge_env cx reason (ctx, ctx1, ctx2) changeset = | |
match (ctx, ctx1, ctx2) with | ||
| ([],[],[]) -> () | ||
| (scope::ctx, scope1::ctx1, scope2::ctx2) -> | ||
let entries = scope.entries in | ||
let entries1 = scope1.entries in | ||
let entries2 = scope2.entries in | ||
(* merge scope, scope1, scope2 *) | ||
let not_found = SSet.fold (fun x not_found -> | ||
match SMap.get x !scope with | ||
match SMap.get x !entries with | ||
| Some {specific;general;def_loc;for_type;} -> | ||
let shape1 = SMap.find_unsafe x !scope1 in | ||
let shape2 = SMap.find_unsafe x !scope2 in | ||
let shape1 = SMap.find_unsafe x !entries1 in | ||
let shape2 = SMap.find_unsafe x !entries2 in | ||
let s1 = shape1.specific in | ||
let s2 = shape2.specific in | ||
let (s, g) = | ||
|
@@ -236,7 +272,7 @@ let rec merge_env cx reason (ctx, ctx1, ctx2) changeset = | |
(tvar,general) | ||
in | ||
let for_type = for_type || shape1.for_type || shape2.for_type in | ||
scope := SMap.add x (create_env_entry ~for_type s g def_loc) !scope; | ||
scope.entries := SMap.add x (create_env_entry ~for_type s g def_loc) !entries; | ||
not_found | ||
| None -> | ||
SSet.add x not_found | ||
|
@@ -246,26 +282,35 @@ let rec merge_env cx reason (ctx, ctx1, ctx2) changeset = | |
| _ -> assert false | ||
|
||
let widen_env cx reason = | ||
let scope = List.hd !env in | ||
let vars = !scope in | ||
if SMap.cardinal vars < 32 then | ||
scope := vars |> SMap.mapi (fun x {specific;general;def_loc;for_type;} -> | ||
if specific = general | ||
then create_env_entry ~for_type specific general def_loc | ||
else | ||
let reason = replace_reason x reason in | ||
let tvar = Flow_js.mk_tvar cx reason in | ||
Flow_js.unit_flow cx (specific,tvar); | ||
Flow_js.unit_flow cx (tvar,general); | ||
create_env_entry ~for_type tvar general def_loc | ||
) | ||
let rec loop = function | ||
| [] -> () | ||
| scope::scopes -> | ||
let entries = scope.entries in | ||
let entries = !entries in | ||
if SMap.cardinal entries < 32 then | ||
scope.entries := entries |> SMap.mapi (fun x {specific;general;def_loc;for_type;} -> | ||
if specific = general | ||
then create_env_entry ~for_type specific general def_loc | ||
else | ||
let reason = replace_reason x reason in | ||
let tvar = Flow_js.mk_tvar cx reason in | ||
Flow_js.unit_flow cx (specific,tvar); | ||
Flow_js.unit_flow cx (tvar,general); | ||
create_env_entry ~for_type tvar general def_loc | ||
); | ||
if scope.kind = VarScope then () | ||
else loop scopes | ||
in | ||
loop !env | ||
|
||
let rec copy_env_ cx reason x = function | ||
| ([],[]) -> () | ||
| (scope1::ctx1, scope2::ctx2) -> | ||
(match SMap.get x !scope1 with | ||
let entries1 = scope1.entries in | ||
let entries2 = scope2.entries in | ||
(match SMap.get x !entries1 with | ||
| Some {specific=s1;_} -> | ||
let s2 = (SMap.find_unsafe x !scope2).specific in | ||
let s2 = (SMap.find_unsafe x !entries2).specific in | ||
Flow_js.unit_flow cx (s2,s1) | ||
| None -> copy_env_ cx reason x (ctx1,ctx2) | ||
) | ||
|
@@ -281,16 +326,18 @@ let copy_env cx reason (ctx1,ctx2) xs = | |
|
||
let havoc_env () = | ||
List.iter (fun scope -> | ||
scope := SMap.mapi (fun x {specific=_;general;def_loc;for_type;} -> | ||
let entries = scope.entries in | ||
entries := SMap.mapi (fun x {specific=_;general;def_loc;for_type;} -> | ||
create_env_entry ~for_type general general def_loc | ||
) !scope | ||
) !entries | ||
) !env | ||
|
||
let rec havoc_env2_ x = function | ||
| scope::scopes -> | ||
(match SMap.get x !scope with | ||
let entries = scope.entries in | ||
(match SMap.get x !entries with | ||
| Some {specific=_;general;def_loc;for_type;} -> | ||
scope := !scope |> | ||
entries := !entries |> | ||
SMap.add x (create_env_entry ~for_type general general def_loc) | ||
| None -> | ||
havoc_env2_ x scopes | ||
|
@@ -302,21 +349,48 @@ let havoc_env2 xs = | |
|
||
let havoc_heap_refinements () = | ||
List.iter (fun scope -> | ||
scope := SMap.mapi (fun x ({specific=_;general;def_loc;for_type;} as entry) -> | ||
let entries = scope.entries in | ||
entries := SMap.mapi (fun x ({specific=_;general;def_loc;for_type;} as entry) -> | ||
if is_refinement x | ||
then create_env_entry ~for_type general general def_loc | ||
else entry | ||
) !scope | ||
) !entries | ||
) !env | ||
|
||
let clear_env reason = | ||
let scope = List.hd !env in | ||
scope := !scope |> SMap.mapi (fun x {specific;general;def_loc;for_type;} -> | ||
(* internal names (.this, .super, .return, .exports) are read-only *) | ||
if is_internal_name x | ||
then create_env_entry ~for_type specific general def_loc | ||
else create_env_entry ~for_type (UndefT reason) general def_loc | ||
) | ||
let rec loop = function | ||
| [] -> () | ||
| scope::scopes -> | ||
let entries = scope.entries in | ||
entries := !entries |> SMap.mapi (fun x {specific;general;def_loc;for_type;} -> | ||
(* internal names (.this, .super, .return, .exports) are read-only *) | ||
if is_internal_name x | ||
then create_env_entry ~for_type specific general def_loc | ||
else create_env_entry ~for_type (UndefT reason) general def_loc | ||
); | ||
if scope.kind = VarScope then () | ||
else loop scopes | ||
in | ||
loop !env | ||
|
||
let string_of_scope_entry cx entry = | ||
let pos = match entry.def_loc with | ||
| Some loc -> (string_of_pos (pos_of_loc loc)) | ||
| None -> "(none)" | ||
in | ||
Utils.spf "{ specific: %s; general: %s; def_loc: %s; for_type: %b }" | ||
(dump_t cx entry.specific) | ||
(dump_t cx entry.general) | ||
pos | ||
entry.for_type | ||
|
||
let string_of_scope cx scope = | ||
let entries = scope.entries in | ||
SMap.fold (fun k v acc -> | ||
(Utils.spf "%s: %s" k (string_of_scope_entry cx v))::acc | ||
) !entries [] | ||
|> String.concat ";\n " | ||
|> Utils.spf "{\n %s\n}" | ||
|
||
let string_of_env cx ctx = | ||
String.concat "\n" (List.map (string_of_scope cx) ctx) | ||
|
@@ -327,7 +401,7 @@ let string_of_env cx ctx = | |
let install_refinement cx x xtypes = | ||
if exists_in_scope x (peek_env ()) = None then | ||
let t = SMap.find_unsafe x xtypes in | ||
init_env cx x (create_env_entry t t None) | ||
init_env cx x (create_env_entry t t None) VarScope (* TODO: verify correct hoist behavior *) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm pretty sure that the scope of the refinement depends on the type of entry corresponding to x. Furthermore, code that relies on Unfortunately, we don't generally know the scope kind of the entry here, and I'm not even sure we can thread it through. The refinement logic is still a bit opaque to me, but I'll try to come up with some test cases to stress this code. Help here would be appreciated. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, so I think I have two tests that show the refinement scope needs to depend on the scope of the thing being refined. Here is why just
Here is why just
@gabelevi, you helped me understand this refinement stuff in the first place (thanks!). Do you see any problem with my reasoning here? It seems like the scope_kind of the refinement needs to depend on the scope_kind of the thing being refined ( If that's true, we currently don't store that information beyond There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've implemented this in 4659a41. Mostly sure it's correct :) |
||
|
||
let refine_with_pred cx reason pred xtypes = | ||
SMap.iter (fun x predx -> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair warning, the env/global_scope situation was tweaked recently (actually, it's the stuff @mroch did that makes it possible to refine globals). Shouldn't be too hard to merge, but nontrivial.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Err, I already merged that locally. Thought I pushed, but apparently I didn't. I'll push that up now.