Skip to content

Commit 632778a

Browse files
SamChou19815facebook-github-bot
authored andcommitted
[flow] Declaration merging for declare namespaces in libdefs
Summary: In this diff, we implement basic declaration merging for `declare namespace` in libdefs. I specifically chose to limit to libdef for now due to its relative self-contained-ness: with some addition work to propagate some additional kinds of signature errors in type sig, we can do the enforcement completely within type sig. Within type sig, we have a specialized implementation of namespace binding. When binding the namespace, it will see whether there is already a namespace in the global libdef scope. If so, we will try to merge it (there are a lot of case analysis, including the most complicated one of promoting a type-only namespace to a value namespace due to merging, which I explained in the comments and checked in tests). When we are merging together two namespaces, we will error on duplicate names, e.g. ``` declare namespace ns { declare const foo: string; } declare namespace ns { declare const foo: string; // error } ``` This error will be added through a new signature error called validation error, which is not a result of unsupported expressions being any, but a result of simple syntactic validation. We can do the validation while we are merging, so it's very convenient. Note that we currently don't error on overridden libdefs, even those within the same file, but we can use similar mechanism to achieve that. (We do need additional tracking, since erroring on the shadowed one will be too impractical right now, as it can cause the shadowed builtin to error, which we can't suppress) A similar approach can be taken to support declaration merging for `declare module` as well (although with even more annoying case analysis for different kinds of modules (cjs/esm)). However, this diff won't do it, and it will be left as an exercise for an interested reader. Changelog: [feature] Declaration merging for `declare namespace` is now supported in toplevel library definitions. Reviewed By: panagosg7 Differential Revision: D70303553 fbshipit-source-id: b0c7b74813b0913cb273931deec3f591611d64d7
1 parent 7944ce6 commit 632778a

21 files changed

+468
-12
lines changed

src/codemods/annotate_exports.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ module SignatureVerification = struct
9696
(fun acc err ->
9797
match err with
9898
| Type_sig.CheckError -> acc
99+
| Type_sig.BindingValidationError _ -> acc
99100
| Type_sig.SigError err ->
100101
let open Signature_error in
101102
let (tot_errors, acc) = acc in

src/parser_utils/file_sig.ml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ and require_bindings =
5959
| BindNamed of (Loc.t Ast_utils.ident * require_bindings) list
6060
[@@deriving show]
6161

62-
type tolerable_error = SignatureVerificationError of Loc.t Signature_error.t [@@deriving show]
62+
type tolerable_error =
63+
| SignatureVerificationError of Loc.t Signature_error.t
64+
| SignatureBindingValidationError of Loc.t Signature_error.binding_validation_t
65+
[@@deriving show]
6366

6467
type tolerable_t = t * tolerable_error list
6568

src/parser_utils/file_sig.mli

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,10 @@ and require_bindings =
101101
| BindNamed of (Loc.t Flow_ast_utils.ident * require_bindings) list
102102
[@@deriving show]
103103

104-
type tolerable_error = SignatureVerificationError of Loc.t Signature_error.t [@@deriving show]
104+
type tolerable_error =
105+
| SignatureVerificationError of Loc.t Signature_error.t
106+
| SignatureBindingValidationError of Loc.t Signature_error.binding_validation_t
107+
[@@deriving show]
105108

106109
type tolerable_t = t * tolerable_error list
107110

src/parser_utils/signature_builder/signature_error.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ type 'loc t =
1515
| UnexpectedExpression of 'loc * Flow_ast_utils.ExpressionSort.t
1616
[@@deriving show, iter, map]
1717

18+
type 'loc binding_validation_t = NameAlreadyBound of 'loc [@@deriving show, iter, map]
19+
1820
let compare = Stdlib.compare

src/parser_utils/type_sig/__tests__/type_sig_tests.ml

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5245,6 +5245,108 @@ let%expect_test "builtin_declare_namespace" =
52455245
Builtin global value globalThis
52465246
Builtin global value ns |}]
52475247

5248+
let%expect_test "declare_namespace_declaration_merging" =
5249+
print_builtins [{|
5250+
declare namespace ns_v {
5251+
declare const a: string;
5252+
}
5253+
declare namespace ns_v {
5254+
declare const b: string;
5255+
}
5256+
declare namespace ns_t {
5257+
type T1 = string;
5258+
}
5259+
declare namespace ns_t {
5260+
type T2 = string;
5261+
}
5262+
declare namespace ns_v_and_then_t {
5263+
declare const a: string;
5264+
}
5265+
declare namespace ns_t_and_then_v {
5266+
type T1 = string;
5267+
}
5268+
declare namespace ns_v_and_then_t {
5269+
type T1 = string;
5270+
}
5271+
declare namespace ns_t_and_then_v {
5272+
declare const a: string;
5273+
}
5274+
5275+
declare const non_ns_value: string;
5276+
type non_ns_type = string;
5277+
// The following namespaces won't have any effect
5278+
declare namespace non_ns_value {
5279+
declare const b: string;
5280+
}
5281+
declare namespace non_ns_value {
5282+
type T1 = string;
5283+
}
5284+
declare namespace non_ns_type {
5285+
declare const b: string;
5286+
}
5287+
declare namespace non_ns_type {
5288+
type T1 = string;
5289+
}
5290+
|}];
5291+
[%expect {|
5292+
Local defs:
5293+
0. Variable {id_loc = [2:16-17]; name = "a"; def = (Annot (String [2:19-25]))}
5294+
1. NamespaceBinding {id_loc = [1:18-22];
5295+
name = "ns_v";
5296+
values =
5297+
{ "a" -> ([2:16-17], (Ref LocalRef {ref_loc = [2:16-17]; index = 0}));
5298+
"b" -> ([5:16-17], (Ref LocalRef {ref_loc = [5:16-17]; index = 2})) };
5299+
types = {}}
5300+
2. Variable {id_loc = [5:16-17]; name = "b"; def = (Annot (String [5:19-25]))}
5301+
3. TypeAlias {id_loc = [8:7-9]; name = "T1"; tparams = Mono; body = (Annot (String [8:12-18]))}
5302+
4. NamespaceBinding {id_loc = [7:18-22];
5303+
name = "ns_t"; values = {};
5304+
types =
5305+
{ "T1" -> ([8:7-9], (Ref LocalRef {ref_loc = [8:7-9]; index = 3}));
5306+
"T2" -> ([11:7-9], (Ref LocalRef {ref_loc = [11:7-9]; index = 5})) }}
5307+
5. TypeAlias {id_loc = [11:7-9];
5308+
name = "T2"; tparams = Mono;
5309+
body = (Annot (String [11:12-18]))}
5310+
6. Variable {id_loc = [14:16-17]; name = "a"; def = (Annot (String [14:19-25]))}
5311+
7. NamespaceBinding {id_loc = [13:18-33];
5312+
name = "ns_v_and_then_t";
5313+
values = { "a" -> ([14:16-17], (Ref LocalRef {ref_loc = [14:16-17]; index = 6})) };
5314+
types = { "T1" -> ([20:7-9], (Ref LocalRef {ref_loc = [20:7-9]; index = 10})) }}
5315+
8. TypeAlias {id_loc = [17:7-9];
5316+
name = "T1"; tparams = Mono;
5317+
body = (Annot (String [17:12-18]))}
5318+
9. NamespaceBinding {id_loc = [16:18-33];
5319+
name = "ns_t_and_then_v";
5320+
values = { "a" -> ([23:16-17], (Ref LocalRef {ref_loc = [23:16-17]; index = 11})) };
5321+
types = { "T1" -> ([17:7-9], (Ref LocalRef {ref_loc = [17:7-9]; index = 8})) }}
5322+
10. TypeAlias {id_loc = [20:7-9];
5323+
name = "T1"; tparams = Mono;
5324+
body = (Annot (String [20:12-18]))}
5325+
11. Variable {id_loc = [23:16-17]; name = "a"; def = (Annot (String [23:19-25]))}
5326+
12. Variable {id_loc = [25:14-26]; name = "non_ns_value"; def = (Annot (String [25:28-34]))}
5327+
13. TypeAlias {id_loc = [26:5-16];
5328+
name = "non_ns_type"; tparams = Mono;
5329+
body = (Annot (String [26:19-25]))}
5330+
14. NamespaceBinding {id_loc = [0:0];
5331+
name = "globalThis";
5332+
values =
5333+
{ "globalThis" -> ([0:0], (Ref LocalRef {ref_loc = [0:0]; index = 14}));
5334+
"non_ns_value" -> ([25:14-26], (Ref LocalRef {ref_loc = [25:14-26]; index = 12}));
5335+
"ns_t_and_then_v" -> ([16:18-33], (Ref LocalRef {ref_loc = [16:18-33]; index = 9}));
5336+
"ns_v" -> ([1:18-22], (Ref LocalRef {ref_loc = [1:18-22]; index = 1}));
5337+
"ns_v_and_then_t" -> ([13:18-33], (Ref LocalRef {ref_loc = [13:18-33]; index = 7})) };
5338+
types =
5339+
{ "non_ns_type" -> ([26:5-16], (Ref LocalRef {ref_loc = [26:5-16]; index = 13}));
5340+
"ns_t" -> ([7:18-22], (Ref LocalRef {ref_loc = [7:18-22]; index = 4})) }}
5341+
5342+
Builtin global value globalThis
5343+
Builtin global value non_ns_value
5344+
Builtin global value ns_t_and_then_v
5345+
Builtin global value ns_v
5346+
Builtin global value ns_v_and_then_t
5347+
Builtin global type non_ns_type
5348+
Builtin global type ns_t |}]
5349+
52485350
let%expect_test "builtin_pattern" =
52495351
print_builtins [{|
52505352
const o = { p: 0 };

src/parser_utils/type_sig/type_sig.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,7 @@ type 'a op =
615615
*)
616616
type 'loc errno =
617617
| CheckError
618+
| BindingValidationError of 'loc Signature_error.binding_validation_t
618619
| SigError of 'loc Signature_error.t
619620
[@@deriving show { with_path = false }, map]
620621

src/parser_utils/type_sig/type_sig_mark.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,8 @@ let mark_exports
266266
SMap.iter (fun _ t -> mark_export ~locs_to_dirtify t) names;
267267
List.iter mark_star stars
268268

269+
let mark_errors = List.iter (Signature_error.iter_binding_validation_t (mark_loc ~visit_loc:ignore))
270+
269271
let mark_builtin_module (loc, exports) =
270272
mark_loc ~visit_loc:ignore loc;
271273
mark_exports ~locs_to_dirtify:[] loc exports

src/parser_utils/type_sig/type_sig_mark.mli

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,8 @@ val mark_exports :
1313
Loc.t Type_sig_parse.exports ->
1414
unit
1515

16+
val mark_errors :
17+
Loc.t Type_sig_collections.Locs.node Signature_error.binding_validation_t list -> unit
18+
1619
val mark_builtin_module :
1720
Loc.t Type_sig_collections.Locs.node * Loc.t Type_sig_parse.exports -> unit

src/parser_utils/type_sig/type_sig_pack.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ type 'loc pattern =
235235

236236
type 'loc cx = { mutable errs: 'loc errno list }
237237

238-
let create_cx () = { errs = [] }
238+
let create_cx errs = { errs }
239239

240240
let pack_smap f map =
241241
let bindings = Array.of_list (SMap.bindings map) in

src/parser_utils/type_sig/type_sig_parse.ml

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ and 'loc tables = {
327327
remote_refs: 'loc remote_binding Remote_refs.builder;
328328
pattern_defs: 'loc parsed Pattern_defs.builder;
329329
patterns: 'loc pattern Patterns.builder;
330+
mutable additional_errors: 'loc loc_node Signature_error.binding_validation_t list;
330331
}
331332

332333
type frozen_kind =
@@ -344,6 +345,7 @@ let create_tables () =
344345
remote_refs = Remote_refs.create ();
345346
pattern_defs = Pattern_defs.create ();
346347
patterns = Patterns.create ();
348+
additional_errors = [];
347349
}
348350

349351
let push_loc tbls = Locs.push tbls.locs
@@ -1092,6 +1094,88 @@ module Scope = struct
10921094
(* a `declare namespace` exports every binding. *)
10931095
let finalize_declare_namespace_exn ~is_type_only scope tbls id_loc name =
10941096
match scope with
1097+
(* Special declaration merging rules for namespaces in the global scope *)
1098+
| DeclareNamespace { values; types; parent = Global global_scope; _ } ->
1099+
let (values, types) = namespace_binding_of_values_and_types scope (values, types) in
1100+
let def : _ local_binding = NamespaceBinding { id_loc; name; values; types } in
1101+
let union_values_and_types existing_values existing_types values types =
1102+
let new_binding name (l, _) =
1103+
if SMap.mem name existing_values || SMap.mem name existing_types then (
1104+
tbls.additional_errors <- Signature_error.NameAlreadyBound l :: tbls.additional_errors;
1105+
false
1106+
) else
1107+
true
1108+
in
1109+
let values = SMap.filter new_binding values in
1110+
let types = SMap.filter new_binding types in
1111+
(SMap.union existing_values values, SMap.union existing_types types)
1112+
in
1113+
(* When we decide that the namespace binding is mergeable. We call this function to
1114+
* mutate the existing node with merged names. *)
1115+
let merge_with_existing_local_binding_node (node : _ local_binding Local_defs.node) =
1116+
Local_defs.modify node (function
1117+
| NamespaceBinding { id_loc; name; values = existing_values; types = existing_types } ->
1118+
let (values, types) =
1119+
union_values_and_types existing_values existing_types values types
1120+
in
1121+
NamespaceBinding { id_loc; name; values; types }
1122+
| b -> b
1123+
)
1124+
in
1125+
(* This updater handle the simple case when we can directly update one kind of binding map
1126+
* (either value or type-only bindings). *)
1127+
let simple_updater = function
1128+
| Some (LocalBinding node) as existing_binding ->
1129+
merge_with_existing_local_binding_node node;
1130+
existing_binding
1131+
| Some (RemoteBinding _) as existing_binding -> existing_binding
1132+
| None ->
1133+
let node = push_local_def tbls def in
1134+
Some (LocalBinding node)
1135+
in
1136+
let updater values_map types_map =
1137+
match (is_type_only, SMap.find_opt name values_map, SMap.find_opt name types_map) with
1138+
| (_, Some _, Some _) ->
1139+
failwith "Invariant violation: a name cannot be in both values and types"
1140+
(* Simple case: no existing binding exist *)
1141+
| (false, None, None) -> (SMap.update name simple_updater values_map, types_map)
1142+
| (true, None, None) -> (values_map, SMap.update name simple_updater types_map)
1143+
(* Simple case: existing binding exist, but in the same value/type category *)
1144+
| (false, Some _, None) -> (SMap.update name simple_updater values_map, types_map)
1145+
| (true, None, Some _) -> (values_map, SMap.update name simple_updater types_map)
1146+
(* Originally not-type-only, now merging with a type-only namespace,
1147+
* so it's still not type-only *)
1148+
| (true, Some _, None) -> (SMap.update name simple_updater values_map, types_map)
1149+
(* Originally type-only, but now it's merging with a non-type-only namespace.
1150+
* The namespace binding needs to be promoted to a non-type-only namespace.
1151+
* (implemented by removing the node from type-only map, add to the value map, and
1152+
* mutate the binding node in place with additional names.)
1153+
*
1154+
* e.g. Consider
1155+
* ```
1156+
* declare namespace ns { type A = '' };
1157+
* declare namespace ns { declare const b: string };
1158+
* ```
1159+
*
1160+
* In order for the behavior to be equivalent to
1161+
1162+
* ```
1163+
* declare namespace ns { type A = ''; declare const b: string };
1164+
* ```
1165+
* We have to promote the original type-only namespace to a value binding.
1166+
*)
1167+
| (false, None, Some (LocalBinding node))
1168+
when match Local_defs.value node with
1169+
| NamespaceBinding _ -> true
1170+
| _ -> false ->
1171+
merge_with_existing_local_binding_node node;
1172+
(SMap.add name (LocalBinding node) values_map, SMap.remove name types_map)
1173+
(* Has an existing non-namespace binding, do nothing *)
1174+
| (false, None, Some _) -> (values_map, types_map)
1175+
in
1176+
let (values, types) = updater global_scope.values global_scope.types in
1177+
global_scope.values <- values;
1178+
global_scope.types <- types
10951179
| DeclareNamespace { values; types; parent; _ } ->
10961180
let (values, types) = namespace_binding_of_values_and_types scope (values, types) in
10971181
bind_local
@@ -1100,6 +1184,7 @@ module Scope = struct
11001184
tbls
11011185
name
11021186
(NamespaceBinding { id_loc; name; values; types })
1187+
ignore2
11031188
| _ -> failwith "The scope must be lexical"
11041189

11051190
let bind_globalThis scope tbls ~global_this_loc =
@@ -4172,7 +4257,7 @@ let namespace_decl
41724257
comments = _;
41734258
} =
41744259
match id with
4175-
| Ast.Statement.DeclareNamespace.Global _ -> ignore
4260+
| Ast.Statement.DeclareNamespace.Global _ -> ()
41764261
| Ast.Statement.DeclareNamespace.Local (id_loc, { Ast.Identifier.name; _ }) ->
41774262
let id_loc = push_loc tbls id_loc in
41784263
let stmts =
@@ -4677,7 +4762,7 @@ let rec statement opts scope tbls (loc, stmt) =
46774762
Scope.finalize_declare_module_exports_exn scope
46784763
| S.DeclareNamespace decl ->
46794764
let is_type_only = Flow_ast_utils.is_type_only_declaration_statement (loc, stmt) in
4680-
namespace_decl opts scope tbls ~is_type_only ~visit_statement:statement decl ignore2
4765+
namespace_decl opts scope tbls ~is_type_only ~visit_statement:statement decl
46814766
| S.DeclareEnum decl
46824767
| S.EnumDeclaration decl ->
46834768
enum_decl opts scope tbls decl ignore2

0 commit comments

Comments
 (0)