Skip to content

Commit

Permalink
[flow] Extract and store global info in type sig
Browse files Browse the repository at this point in the history
Summary:
## Intro

For motivation, see D68740497.

This diff mainly implements the support for `declare global {...}` from type sig. There are two major parts:

1. type_sig_parse: which mostly focuses on scoping rules
2. everything else: mostly boring information propagation: from parsed form to packed form and then to types.

I will focus on 1 in the diff summary.

## Restrictions and Limitations

First, `declare module {...}` will only be supported in toplevel of declare module or a module. If it appears anywhere else, it will be ignored, because it will be treated as a `Ast.Statement.Block`. This is easy to enforce: we only do something about `declare global {...}` if the parent scope is `Module` or `DeclareModule`.

Secondly, I will state some limitations: only types-only declarations will be bind to the environment. The goal is to give us flexibility of interpreting these `declare global {...}` later. If we also bind values, then we have to make the effect of global-modifying modules transitive, because that's what's happening at runtime for importing modules with side effect. This can potentially be a disaster for performance given that we allow cyclic modules. However, if it's type only, then we retain the right to say you get what you get. Conceptually, we can think of the following code

```
// a.js
declare global {
  type T = string;
}

// b.js
import 'global';
type T1 = T;
```

as

```
// a.js
declare export namespace $secret$globals {
  type T = string;
}

// b.js
import {type $secret$globals} 'global';
type T1 = $secret$globals.T;
```

## Scoping Rules

Finally let's define the scoping rules. Without losing generality, I will use `declare global` within `declare module` as an example.

Given:

```
declare module foo {
  declare global {
    // global decl 1
  }
  // module decl1

  declare global {
    // global decl 2
  }
  // module decl2
}
```

It will be conceptually treated as

```
declare module foo {
  module_globals {
    // global decl 1
    // global decl 2

    regular_module_stuff {
      // module decl1
      // module decl2
    }
  }
}
```

This implies that the named defined in the `declare global` block can be shadowed by the same name within `regular_module_stuff`, and more importantly, code within `regular_module_stuff` can refer to these globals. It might look weird first, but if you think it over, you will find that it's just a replacement of the following in global libdef

```
// global decl 1
// global decl 2
declare module foo {
  // module decl1
  // module decl2
}
```

In terms of implementation, the above rules are implemented as follows within type sig:

- Normal module stuff binds as usual
- Within `declare global {...}`
  - We will push a `DeclareGlobal` scope that only tracks it's parent.
  - Value bindings are ignored
  - On type bindings, we will trace back to the parent, and add it to the `global_types` field in the `exports` field of `Module` or `DeclareModule`
- On lookup within `declare global {...}`, we will trace back to the parent, and lookup from the  `global_types` field in the `exports` field of `Module` or `DeclareModule` first, before falling back to parents
- On lookup within module or declare module, we will first lookup normal module bindings, and if not found, try look up from the `global_types` field in the `exports` field.

Changelog: [internal]

Reviewed By: panagosg7

Differential Revision: D68246824

fbshipit-source-id: f7d4a58908e56f5175d036a508575e79fea5faf6
  • Loading branch information
SamChou19815 authored and facebook-github-bot committed Jan 31, 2025
1 parent 60cfefd commit d890dfa
Show file tree
Hide file tree
Showing 14 changed files with 830 additions and 320 deletions.
12 changes: 10 additions & 2 deletions src/parser_utils/exports/exports.ml
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ module ESM = struct
export_keys;
type_stars;
stars;
module_globals = _;
strict = _;
platform_availability_set = _;
}
Expand Down Expand Up @@ -409,8 +410,15 @@ module CJS = struct
NamedType name :: acc

let exports type_sig type_exports exports info =
let (CJSModuleInfo { type_export_keys; type_stars; strict = _; platform_availability_set = _ })
=
let (CJSModuleInfo
{
type_export_keys;
type_stars;
module_globals = _;
strict = _;
platform_availability_set = _;
}
) =
info
in
let acc =
Expand Down
801 changes: 530 additions & 271 deletions src/parser_utils/type_sig/__tests__/type_sig_tests.ml

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/parser_utils/type_sig/type_sig_mark.ml
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,11 @@ let mark_star (loc, mref) =
let mark_exports
~locs_to_dirtify
file_loc
(P.Exports { kind; types; type_stars; strict = _; platform_availability_set = _ }) =
(P.Exports { kind; types; type_stars; global_types; strict = _; platform_availability_set = _ })
=
SMap.iter (fun _ t -> mark_export_type ~locs_to_dirtify t) types;
List.iter mark_star type_stars;
SMap.iter (fun _ -> mark_binding ~locs_to_dirtify) global_types;
match kind with
| P.UnknownModule -> ()
| P.CJSModule t -> mark_parsed ~locs_to_dirtify ~visit_loc:ignore t
Expand Down
50 changes: 44 additions & 6 deletions src/parser_utils/type_sig/type_sig_pack.ml
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,18 @@ type 'loc type_export =
| ExportTypeFrom of Remote_refs.index
[@@deriving map, show { with_path = false }]

type module_globals =
| ModuleGlobals of {
global_types: Local_defs.index array;
global_types_keys: string array;
}
[@@deriving show { with_path = false }]

type 'loc cjs_module_info =
| CJSModuleInfo of {
type_export_keys: string array;
type_stars: ('loc * Module_refs.index) list;
module_globals: module_globals;
strict: bool;
platform_availability_set: Platform_set.t option;
}
Expand All @@ -177,6 +185,7 @@ type 'loc es_module_info =
type_stars: ('loc * Module_refs.index) list;
export_keys: string array;
stars: ('loc * Module_refs.index) list;
module_globals: module_globals;
strict: bool;
platform_availability_set: Platform_set.t option;
}
Expand Down Expand Up @@ -427,16 +436,24 @@ and pack_exports
cx
file_loc
module_name
(P.Exports { kind; types; type_stars; strict; platform_availability_set }) =
(P.Exports { kind; types; type_stars; global_types; strict; platform_availability_set }) =
let (type_export_keys, type_exports) = pack_smap pack_type_export types in
let type_stars = List.map pack_star type_stars in
match kind with
| P.UnknownModule ->
let info = CJSModuleInfo { type_export_keys; type_stars; strict; platform_availability_set } in
let module_globals = pack_module_globals global_types in
let info =
CJSModuleInfo
{ type_export_keys; type_stars; module_globals; strict; platform_availability_set }
in
CJSModule { type_exports; exports = None; info }
| P.CJSModule t ->
let exports = Some (pack_parsed cx t) in
let info = CJSModuleInfo { type_export_keys; type_stars; strict; platform_availability_set } in
let module_globals = pack_module_globals global_types in
let info =
CJSModuleInfo
{ type_export_keys; type_stars; module_globals; strict; platform_availability_set }
in
CJSModule { type_exports; exports; info }
| P.CJSModuleProps props ->
let file_loc = pack_loc file_loc in
Expand All @@ -449,7 +466,11 @@ and pack_exports
props
in
let exports = Some (Value (ObjLit { loc = file_loc; frozen = true; proto = None; props })) in
let info = CJSModuleInfo { type_export_keys; type_stars; strict; platform_availability_set } in
let module_globals = pack_module_globals global_types in
let info =
CJSModuleInfo
{ type_export_keys; type_stars; module_globals; strict; platform_availability_set }
in
CJSModule { type_exports; exports; info }
| P.CJSDeclareModule props ->
let file_loc = pack_loc file_loc in
Expand All @@ -464,14 +485,27 @@ and pack_exports
let exports =
Some (Value (DeclareModuleImplicitlyExportedObject { loc = file_loc; module_name; props }))
in
let info = CJSModuleInfo { type_export_keys; type_stars; strict; platform_availability_set } in
let module_globals = pack_module_globals global_types in
let info =
CJSModuleInfo
{ type_export_keys; type_stars; module_globals; strict; platform_availability_set }
in
CJSModule { type_exports; exports; info }
| P.ESModule { names; stars } ->
let (export_keys, exports) = pack_smap (pack_export cx) names in
let stars = List.map pack_star stars in
let module_globals = pack_module_globals global_types in
let info =
ESModuleInfo
{ type_export_keys; type_stars; export_keys; stars; strict; platform_availability_set }
{
type_export_keys;
type_stars;
export_keys;
stars;
module_globals;
strict;
platform_availability_set;
}
in
ESModule { type_exports; exports; info }

Expand Down Expand Up @@ -529,6 +563,10 @@ and pack_builtin = function
| P.LocalBinding b -> Local_defs.index_exn b
| P.RemoteBinding _ -> failwith "unexpected remote builtin"

and pack_module_globals global_types =
let (global_types_keys, global_types) = pack_smap pack_builtin global_types in
ModuleGlobals { global_types_keys; global_types }

and pack_builtin_module cx name (loc, exports) =
let module_kind = pack_exports cx loc name exports in
let loc = pack_loc loc in
Expand Down
72 changes: 65 additions & 7 deletions src/parser_utils/type_sig/type_sig_parse.ml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ and 'loc exports =
mutable kind: 'loc module_kind;
mutable types: 'loc export_type smap;
mutable type_stars: ('loc loc_node * module_ref_node) list;
mutable global_types: 'loc binding_node SMap.t;
strict: bool;
platform_availability_set: Platform_set.t option;
}
Expand Down Expand Up @@ -155,6 +156,7 @@ and 'loc scope =
mutable types: 'loc binding_node SMap.t;
exports: 'loc exports;
}
| DeclareGlobal of { parent: 'loc scope }
| Lexical of {
mutable values: 'loc binding_node SMap.t;
mutable types: 'loc binding_node SMap.t;
Expand Down Expand Up @@ -421,6 +423,7 @@ module Exports = struct
kind = UnknownModule;
types = SMap.empty;
type_stars = [];
global_types = SMap.empty;
strict;
platform_availability_set;
}
Expand Down Expand Up @@ -535,7 +538,8 @@ module Scope = struct
| DeclareModule { parent; _ }
| Lexical { parent; _ }
| ConditionalTypeExtends { parent; _ }
| DeclareNamespace { parent; _ } ->
| DeclareNamespace { parent; _ }
| DeclareGlobal { parent } ->
Some parent
| Global _
| Module _ ->
Expand All @@ -544,6 +548,7 @@ module Scope = struct
let modify_exports f = function
| Module scope -> f scope.exports
| DeclareModule scope -> f scope.exports
| DeclareGlobal _
| DeclareNamespace _
| Global _
| Lexical _
Expand All @@ -554,6 +559,7 @@ module Scope = struct
| Global { values; types; modules } -> (values, types, modules)
| DeclareModule _
| DeclareNamespace _
| DeclareGlobal _
| Module _
| Lexical _
| ConditionalTypeExtends _ ->
Expand All @@ -564,6 +570,7 @@ module Scope = struct
| Global _
| DeclareModule _
| DeclareNamespace _
| DeclareGlobal _
| Lexical _
| ConditionalTypeExtends _ ->
raise Not_found
Expand Down Expand Up @@ -628,6 +635,11 @@ module Scope = struct
| Global scope -> scope.types <- bind_type scope.values scope.types
| DeclareModule scope -> scope.types <- bind_type scope.values scope.types
| DeclareNamespace scope -> scope.types <- bind_type scope.values scope.types
| DeclareGlobal { parent = DeclareModule { exports; _ } | Module { exports; _ } } ->
let (Exports e) = exports in
e.global_types <- bind_type SMap.empty e.global_types
| DeclareGlobal { parent = _ } ->
failwith "DeclareGlobal can only have DeclareModule or Module as parent."
| Module scope -> scope.types <- bind_type scope.values scope.types
| Lexical scope -> scope.types <- bind_type scope.values scope.types
| ConditionalTypeExtends _ -> ()
Expand All @@ -636,6 +648,8 @@ module Scope = struct
| Global scope -> scope.values <- bind_value scope.values scope.types
| DeclareModule scope -> scope.values <- bind_value scope.values scope.types
| DeclareNamespace scope -> scope.values <- bind_value scope.values scope.types
(* Only type_only bindings are allowed in declare global *)
| DeclareGlobal { parent = _ } -> ()
| Module scope -> scope.values <- bind_value scope.values scope.types
| Lexical scope -> scope.values <- bind_value scope.values scope.types
| ConditionalTypeExtends _ -> ()
Expand All @@ -652,6 +666,7 @@ module Scope = struct
(match SMap.find_opt name values with
| Some binding -> Some (binding, scope)
| None -> lookup_value parent name)
| DeclareGlobal { parent } -> lookup_value parent name

let rec lookup_type scope name =
let lookup_scope name values types =
Expand All @@ -662,22 +677,40 @@ module Scope = struct
match scope with
| Global { values; types; _ } ->
Base.Option.map ~f:(fun binding -> (binding, scope)) (lookup_scope name values types)
| Module { values; types; _ } ->
Base.Option.map ~f:(fun binding -> (binding, scope)) (lookup_scope name values types)
| ConditionalTypeExtends { parent; _ } -> lookup_type parent name
| DeclareModule { parent; values; types; _ }
| DeclareNamespace { parent; values; types }
| Lexical { parent; values; types } ->
(match lookup_scope name values types with
| Some binding -> Some (binding, scope)
| None -> lookup_type parent name)
| Module { values; types; exports = Exports { global_types; _ }; _ } ->
Base.Option.map
~f:(fun binding -> (binding, scope))
(match lookup_scope name values types with
| Some result -> Some result
| None -> lookup_scope name SMap.empty global_types)
| DeclareModule { parent; values; types; exports = Exports { global_types; _ }; _ } ->
(match lookup_scope name values types with
| Some binding -> Some (binding, scope)
| None ->
(match lookup_scope name SMap.empty global_types with
| Some binding -> Some (binding, scope)
| None -> lookup_type parent name))
| DeclareGlobal { parent = (DeclareModule { exports; _ } | Module { exports; _ }) as parent } ->
let (Exports { global_types; _ }) = exports in
(match lookup_scope name SMap.empty global_types with
| Some binding -> Some (binding, scope)
| None -> lookup_type parent name)
| DeclareGlobal { parent = _ } ->
failwith "DeclareGlobal can only have DeclareModule or Module as parent."

let rec find_host scope b =
match scope with
| Global _
| DeclareModule _
| DeclareNamespace _
| Module _ ->
| Module _
| DeclareGlobal _ ->
scope
| ConditionalTypeExtends { parent; _ } -> find_host parent b
| Lexical { parent; _ } ->
Expand All @@ -691,7 +724,8 @@ module Scope = struct
| Global _
| DeclareModule _
| DeclareNamespace _
| Module _ ->
| Module _
| DeclareGlobal _ ->
None
| Lexical { parent; _ } -> scope_of_infer_name parent name loc
| ConditionalTypeExtends ({ infer_type_names; _ } as scope) ->
Expand Down Expand Up @@ -1017,6 +1051,7 @@ module Scope = struct
(* is already the right kind? shouldn't happen *)
failwith "only call finalize_declare_module_exports_exn once per DeclareModule")
| DeclareNamespace _
| DeclareGlobal _
| Global _
| Module _
| Lexical _
Expand Down Expand Up @@ -1090,6 +1125,16 @@ module Scope = struct
(NamespaceBinding { id_loc; name; values; types })
| _ -> failwith "The scope must be lexical"

let within_declare_global scope ~f =
match scope with
| Module _ ->
let scope = DeclareGlobal { parent = scope } in
f scope
| DeclareModule _ ->
let scope = DeclareGlobal { parent = scope } in
f scope
| _ -> ()

let bind_globalThis scope tbls ~global_this_loc =
match scope with
| Global global_scope ->
Expand Down Expand Up @@ -4160,7 +4205,20 @@ let namespace_decl
comments = _;
} =
match id with
| Ast.Statement.DeclareNamespace.Global _TODO_DECLARE_GLOBAL -> (fun _ -> ())
| Ast.Statement.DeclareNamespace.Global _ ->
if opts.enable_declare_global then
Scope.within_declare_global scope ~f:(fun scope ->
let stmts =
Base.List.filter stmts ~f:(fun (_, stmt) ->
Flow_ast_utils.acceptable_statement_in_declaration_context
~in_declare_namespace:true
stmt
|> Base.Result.is_ok
)
in
List.iter (visit_statement opts scope tbls) stmts
);
ignore
| Ast.Statement.DeclareNamespace.Local (id_loc, { Ast.Identifier.name; _ }) ->
let id_loc = push_loc tbls id_loc in
let stmts =
Expand Down
Loading

0 comments on commit d890dfa

Please sign in to comment.