src/typing/scope.ml (376 lines of code) (raw):
(*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open Utils_js
open Loc_collections
let mk_id = Reason.mk_id
(******************************************************************************)
(* Scopes *)
(******************************************************************************)
(* these are basically owned by Env, but are here
to break circularity between Env and Flow_js
*)
(* entry state *)
module State = struct
type t =
| Undeclared
| Declared
| MaybeInitialized
| Initialized
let to_int = function
| Undeclared -> 0
| Declared -> 1
| MaybeInitialized -> 2
| Initialized -> 3
let to_string = function
| Undeclared -> "Undeclared"
| Declared -> "Declared"
| MaybeInitialized -> "MaybeInitialized"
| Initialized -> "Initialized"
let compare x y = Stdlib.compare (to_int x) (to_int y)
end
(* entries for vars/lets, consts and types *)
module Entry = struct
type value_kind =
(* consts are either explicit bindings or e.g. const params *)
| Const of const_binding_kind
(* Some let bindings are explicit (like you wrote let x = 123) and some
* are implicit (like class declarations). For implicit lets, we should
* track why this is a let binding for better error messages *)
| Let of (let_binding_kind * non_const_specialization)
| Var of non_const_specialization
and const_binding_kind =
| ConstImportBinding
| ConstParamBinding
| ConstVarBinding
| EnumNameBinding
and let_binding_kind =
| LetVarBinding
| ClassNameBinding
| CatchParamBinding
| FunctionBinding
| DeclaredFunctionBinding of { predicate: bool }
| ParamBinding
and non_const_specialization =
| Havocable
| NotWrittenByClosure
| ConstLike
let string_of_let_binding_kind = function
| LetVarBinding -> "let"
| ClassNameBinding -> "class"
| CatchParamBinding -> "catch"
| FunctionBinding -> "function"
| DeclaredFunctionBinding _ -> "declared function"
| ParamBinding -> "param"
let string_of_value_kind = function
| Const ConstImportBinding -> "import"
| Const ConstParamBinding -> "const param"
| Const ConstVarBinding -> "const"
| Const EnumNameBinding -> "enum"
| Let (kind, _) -> string_of_let_binding_kind kind
| Var _ -> "var"
type value_binding = {
kind: value_kind;
value_state: State.t;
(* The location where the binding was declared/created *)
value_declare_loc: ALoc.t;
(* The last location (in this scope) where the entry value was assigned *)
value_assign_loc: ALoc.t;
specific: Type.t;
general: Type.annotated_or_inferred;
closure_writes: (ALocSet.t * Type.t * Type.t option) option;
provider: Type.t;
}
type type_binding_kind =
| ImportTypeBinding
| TypeBinding
type type_binding = {
type_binding_kind: type_binding_kind;
type_state: State.t;
type_loc: ALoc.t;
type_: Type.t;
}
type t =
| Value of value_binding
| Type of type_binding
| Class of Type.class_binding
(* constructors *)
let new_class
class_binding_id
class_private_fields
class_private_static_fields
class_private_methods
class_private_static_methods =
Class
{
Type.class_binding_id;
class_private_fields;
class_private_static_fields;
class_private_methods;
class_private_static_methods;
}
let new_value kind state specific ?closure_writes ~provider general value_declare_loc =
Value
{
kind;
value_state = state;
value_declare_loc;
value_assign_loc = value_declare_loc;
specific;
general;
closure_writes;
provider;
}
let new_const ~loc ?(state = State.Undeclared) ?(kind = ConstVarBinding) general =
let specific = TypeUtil.type_t_of_annotated_or_inferred general in
new_value (Const kind) state specific general loc ~provider:specific
let new_import ~loc t =
new_value (Const ConstImportBinding) State.Initialized t (Type.Inferred t) loc ~provider:t
let new_let
~loc
?(state = State.Undeclared)
?(kind = LetVarBinding)
?(spec = Havocable)
?closure_writes
~provider
general =
let specific = TypeUtil.type_t_of_annotated_or_inferred general in
new_value (Let (kind, spec)) state specific ?closure_writes ~provider general loc
let new_var
~loc
?(state = State.Undeclared)
?specific
?closure_writes
~provider
?(spec = Havocable)
general =
let specific =
match specific with
| Some t -> t
| None -> TypeUtil.type_t_of_annotated_or_inferred general
in
new_value (Var spec) state specific ?closure_writes ~provider general loc
let new_type_ type_binding_kind state loc type_ =
Type { type_binding_kind; type_state = state; type_loc = loc; type_ }
let new_type ~loc ?(state = State.Undeclared) type_ = new_type_ TypeBinding state loc type_
let new_import_type ~loc type_ = new_type_ ImportTypeBinding State.Initialized loc type_
(* accessors *)
let entry_loc = function
| Value v -> v.value_declare_loc
| Type t -> t.type_loc
| Class _ -> ALoc.none
let assign_loc = function
| Value v -> v.value_assign_loc
| Type t -> t.type_loc
| Class _ -> ALoc.none
let declared_type = function
| Value v -> TypeUtil.type_t_of_annotated_or_inferred v.general
| Type t -> t.type_
| Class _ -> assert_false "Internal Error: Class bindings have no type"
let actual_type = function
| Value v -> v.specific
| Type t -> t.type_
| Class _ -> assert_false "Internal Error: Class bindings have no type"
let string_of_kind = function
| Value v -> string_of_value_kind v.kind
| Type _ -> "type"
| Class c -> spf "Class %s" (ALoc.debug_to_string (c.Type.class_binding_id :> ALoc.t))
let kind_of_value (value : value_binding) = value.kind
let general_of_value (value : value_binding) =
TypeUtil.type_t_of_annotated_or_inferred value.general
let state_of_value (value : value_binding) = value.value_state
(** Given a named entry, return a new Value entry with specific type replaced
with general type for non-internal, non-Const value entries. Types, consts
and internal vars are read-only, so specific types can be preserved.
*)
let havoc ?on_call name entry =
match entry with
| Type _ -> entry
| Value ({ kind = Const _; specific = Type.DefT (_, _, Type.EmptyT); _ } as v) ->
(* cleared consts: see note on Env.reset_current_activation *)
if Reason.is_internal_name name then
entry
else
Value { v with specific = TypeUtil.type_t_of_annotated_or_inferred v.general }
| Value { kind = Const _; _ }
| Value { kind = Let (_, ConstLike); _ }
| Value { kind = Var ConstLike; _ } ->
entry
| Value { kind = Let (_, NotWrittenByClosure); _ }
| Value { kind = Var NotWrittenByClosure; _ }
when on_call <> None ->
entry
| Value v ->
if Reason.is_internal_name name then
entry
else
let value_state =
let open State in
match v.value_state with
| Declared -> MaybeInitialized
| state -> state
in
(match (on_call, v.closure_writes) with
| (Some widen_on_call, Some (_, t, Some provider_t)) ->
let t = widen_on_call v.specific t provider_t in
Value { v with specific = t; value_state }
| (Some widen_on_call, Some (_, t, None)) ->
let t = widen_on_call v.specific t v.provider in
Value { v with specific = t; value_state }
| _ -> Value { v with specific = v.provider; value_state })
| Class _ -> entry
let reset loc name entry =
match entry with
| Class _
| Type _ ->
entry
| Value v ->
if Reason.is_internal_name name then
entry
else
Value { v with specific = Type.EmptyT.at loc |> Type.with_trust Trust.bogus_trust }
let is_lex = function
| Type _ -> false
| Class _ -> true
| Value v ->
(match v.kind with
| Const _ -> true
| Let _ -> true
| _ -> false)
end
type var_scope_kind =
| Ordinary (* function or module *)
| Async (* async function *)
| Generator (* generator function *)
| AsyncGenerator (* async generator function *)
| Module (* module scope *)
| Global (* global scope *)
| Predicate (* predicate function *)
| Ctor
(* constructor *)
let string_of_var_scope_kind = function
| Ordinary -> "Ordinary"
| Async -> "Async"
| Generator -> "Generator"
| AsyncGenerator -> "AsyncGenerator"
| Module -> "Module"
| Global -> "Global"
| Predicate -> "Predicate"
| Ctor -> "Constructor"
(* var and lexical scopes differ in hoisting behavior
and auxiliary properties *)
(* TODO lexical scope support *)
type kind =
| VarScope of var_scope_kind
| LexScope
let string_of_kind = function
| VarScope kind -> spf "VarScope %s" (string_of_var_scope_kind kind)
| LexScope -> "LexScope"
type refi_binding = {
refi_loc: ALoc.t;
refined: Type.t;
original: Type.t;
}
(* a scope is a mutable binding table, plus kind and attributes *)
(* scopes are tagged by id, which are shared by clones. function
types hold the id of their activation scopes. *)
(* TODO add in-scope type variable binding table *)
(* when the typechecker is constructing the typed AST, declare functions are
processed separately from (before) when most statements are processed. To
be able to put the typed ASTs of the type annotations that were on the
declare functions into the spots where they go in the typed AST, we put
the declare functions' type annotations' typed ASTs in the scope during the
earlier pass when the declare functions are processed, then during the
second pass when the full typed AST is being constructed, we get them from
the scope and put them where they belong in the typed AST. *)
type t = {
id: int;
kind: kind;
mutable entries: Entry.t NameUtils.Map.t;
mutable refis: refi_binding Key_map.t;
mutable declare_func_annots: (ALoc.t, ALoc.t * Type.t) Flow_ast.Type.annotation SMap.t;
}
(* ctor helper *)
let fresh_impl kind =
{
id = mk_id ();
kind;
entries = NameUtils.Map.empty;
refis = Key_map.empty;
declare_func_annots = SMap.empty;
}
(* return a fresh scope of the most common kind (var) *)
let fresh ?(var_scope_kind = Ordinary) () = fresh_impl (VarScope var_scope_kind)
(* return a fresh lexical scope *)
let fresh_lex () = fresh_impl LexScope
(* clone a scope: snapshot mutable entries.
NOTE: tvars (OpenT) are essentially refs, and are shared by clones.
*)
let clone { id; kind; entries; refis; declare_func_annots } =
{ id; kind; entries; refis; declare_func_annots }
(* use passed f to iterate over all scope entries *)
let iter_entries f scope = NameUtils.Map.iter f scope.entries
(* use passed f to update all scope entries *)
let update_entries f scope = scope.entries <- NameUtils.Map.mapi f scope.entries
(* add entry to scope *)
let add_entry name entry scope = scope.entries <- NameUtils.Map.add name entry scope.entries
(* remove entry from scope *)
let remove_entry name scope = scope.entries <- NameUtils.Map.remove name scope.entries
(* get entry from scope, or None *)
let get_entry name scope = NameUtils.Map.find_opt name scope.entries
(* use passed f to update all scope refis *)
let update_refis f scope = scope.refis <- Key_map.mapi f scope.refis
(* add refi to scope *)
let add_refi key refi scope = scope.refis <- Key_map.add key refi scope.refis
(* remove entry from scope *)
let remove_refi key scope = scope.refis <- Key_map.remove key scope.refis
(* get entry from scope, or None *)
let get_refi name scope = Key_map.find_opt name scope.refis
(* havoc a refi *)
let havoc_refi key scope =
scope.refis <- scope.refis |> Key_map.filter (fun k _ -> Key.compare key k != 0)
(* helper: filter all refis whose expressions involve the given name *)
let filter_refis_using_propname ~private_ propname refis =
refis |> Key_map.filter (fun key _ -> not (Key.uses_propname ~private_ propname key))
(* havoc a scope's refinements:
if name is passed, clear refis whose expressions involve it.
otherwise, clear them all
*)
let havoc_refis ?name ~private_ scope =
scope.refis <-
(match name with
| Some name -> scope.refis |> filter_refis_using_propname ~private_ name
| None -> Key_map.empty)
let havoc_all_refis ?name scope =
havoc_refis ?name ~private_:false scope;
havoc_refis ?name ~private_:true scope
(* havoc a scope:
- clear all refinements
- reset specific types of entries to their general types
*)
let havoc scope =
havoc_all_refis scope;
update_entries Entry.havoc scope
let reset loc scope =
havoc_all_refis scope;
update_entries (Entry.reset loc) scope
let add_declare_func_annot name annot scope =
scope.declare_func_annots <- SMap.add name annot scope.declare_func_annots
let get_declare_func_annot name scope = SMap.find_opt name scope.declare_func_annots
let is_lex scope =
match scope.kind with
| LexScope -> true
| _ -> false
let is_global scope =
match scope.kind with
| VarScope Global -> true
| _ -> false
let is_toplevel scope =
match scope.kind with
| VarScope Global
| VarScope Module ->
true
| _ -> false