Skip to content

Commit 10e2d16

Browse files
SamChou19815facebook-github-bot
authored andcommitted
[flow] Scoped libdef support 4/n: Error on libdef name override
Summary: With the support for scoped libdefs, the problem of libdef override will be much more severe. Previously, it's just bad that we just forcefully replace one of the defined entries. Now with scoped libdefs where common code is checked only under common libdefs, a scoped libdef binding overriding a common libdef binding in an inconsistent way might create a false sense of security. Therefore, in this diff, we will now error on the overriding location as a first step, so that we can at least know where the override happens. We can relax this error later when we support more forms of declaration merging. To detect the name overrides, we intercept at the `bind` function. Whenever we see an illegal bind at the global scope level, we will report an error. I have updated all the updaters (those `f`s) to also return whether the binding is legal. Changelog: [errors] Overriding already defined names in library definition will now error. Reviewed By: panagosg7 Differential Revision: D70570824 fbshipit-source-id: 62e59e108143c5233367de626a1af98706bdc660
1 parent 9f8cd90 commit 10e2d16

File tree

22 files changed

+249
-89
lines changed

22 files changed

+249
-89
lines changed

src/parser_utils/type_sig/type_sig_parse.ml

Lines changed: 112 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,33 @@ type frozen_kind =
336336

337337
let ignore2 _ _ = ()
338338

339+
let loc_of_binding = function
340+
| LocalBinding node ->
341+
(match Local_defs.value node with
342+
| VarBinding { id_loc; _ }
343+
| LetConstBinding { id_loc; _ }
344+
| ConstRefBinding { id_loc; _ }
345+
| ConstFunBinding { id_loc; _ }
346+
| ClassBinding { id_loc; _ }
347+
| DeclareClassBinding { id_loc; _ }
348+
| FunBinding { id_loc; _ }
349+
| ComponentBinding { id_loc; _ }
350+
| EnumBinding { id_loc; _ }
351+
| NamespaceBinding { id_loc; _ }
352+
| TypeBinding { id_loc; _ } ->
353+
id_loc
354+
| DeclareFunBinding { defs_rev; _ } ->
355+
let (id_loc, _, _) = Nel.last defs_rev in
356+
id_loc)
357+
| RemoteBinding node ->
358+
(match Remote_refs.value node with
359+
| ImportBinding { id_loc; _ }
360+
| ImportTypeBinding { id_loc; _ }
361+
| ImportTypeofBinding { id_loc; _ }
362+
| ImportNsBinding { id_loc; _ }
363+
| ImportTypeofNsBinding { id_loc; _ } ->
364+
id_loc)
365+
339366
let create_tables () =
340367
{
341368
locs = Locs.create ();
@@ -608,16 +635,46 @@ module Scope = struct
608635
| ID.ImportTypeof -> ImportTypeofNsBinding { id_loc; name; mref }
609636
| ID.ImportType -> failwith "unexpected import type *"
610637

611-
let bind ~type_only scope name f =
612-
let bind_value values types =
638+
let bind ~type_only scope tbls name id_loc f =
639+
let bind_updater ~in_global_scope f existing_binding =
640+
(* For now, we allow $JSXIntrinsics, since we intentionally kept only a minimal version in
641+
* the builtins, and let flow-typed supply a full one. *)
642+
if in_global_scope && name <> "$JSXIntrinsics" then (
643+
let (binding, legal) = f existing_binding in
644+
(match (legal, existing_binding) with
645+
| (false, Some existing_binding) ->
646+
let override_binding_loc = loc_of_binding existing_binding in
647+
(* Consider
648+
* ```
649+
* declare const foo: string;
650+
* declare const foo: number;
651+
* ```
652+
* The first one is considered the overriding one,
653+
* while the second one is considered to be the existing one.
654+
* This ensures that the builtin libdefs (which come last) will always considered to be the
655+
* existing ones.
656+
* *)
657+
tbls.additional_errors <-
658+
Signature_error.NameOverride
659+
{ name; override_binding_loc; existing_binding_loc = id_loc }
660+
:: tbls.additional_errors
661+
| _ -> ());
662+
binding
663+
) else
664+
let (binding, _legal) = f existing_binding in
665+
binding
666+
in
667+
let bind_value ~in_global_scope f values types =
668+
let f = bind_updater ~in_global_scope f in
613669
match SMap.find_opt name values with
614670
| Some _ -> SMap.update name f values
615671
| None ->
616672
(match SMap.find_opt name types with
617673
| None -> SMap.update name f values
618674
| Some _ -> values)
619675
in
620-
let bind_type values types =
676+
let bind_type ~in_global_scope f values types =
677+
let f = bind_updater ~in_global_scope f in
621678
match SMap.find_opt name types with
622679
| Some _ -> SMap.update name f types
623680
| None ->
@@ -627,19 +684,24 @@ module Scope = struct
627684
in
628685
if type_only then
629686
match scope with
630-
| Global scope -> scope.types <- bind_type scope.values scope.types
631-
| DeclareModule scope -> scope.types <- bind_type scope.values scope.types
632-
| DeclareNamespace scope -> scope.types <- bind_type scope.values scope.types
633-
| Module scope -> scope.types <- bind_type scope.values scope.types
634-
| Lexical scope -> scope.types <- bind_type scope.values scope.types
687+
| Global scope -> scope.types <- bind_type ~in_global_scope:true f scope.values scope.types
688+
| DeclareModule scope ->
689+
scope.types <- bind_type ~in_global_scope:false f scope.values scope.types
690+
| DeclareNamespace scope ->
691+
scope.types <- bind_type ~in_global_scope:false f scope.values scope.types
692+
| Module scope -> scope.types <- bind_type ~in_global_scope:false f scope.values scope.types
693+
| Lexical scope -> scope.types <- bind_type ~in_global_scope:false f scope.values scope.types
635694
| ConditionalTypeExtends _ -> ()
636695
else
637696
match scope with
638-
| Global scope -> scope.values <- bind_value scope.values scope.types
639-
| DeclareModule scope -> scope.values <- bind_value scope.values scope.types
640-
| DeclareNamespace scope -> scope.values <- bind_value scope.values scope.types
641-
| Module scope -> scope.values <- bind_value scope.values scope.types
642-
| Lexical scope -> scope.values <- bind_value scope.values scope.types
697+
| Global scope -> scope.values <- bind_value ~in_global_scope:true f scope.values scope.types
698+
| DeclareModule scope ->
699+
scope.values <- bind_value ~in_global_scope:false f scope.values scope.types
700+
| DeclareNamespace scope ->
701+
scope.values <- bind_value ~in_global_scope:false f scope.values scope.types
702+
| Module scope -> scope.values <- bind_value ~in_global_scope:false f scope.values scope.types
703+
| Lexical scope ->
704+
scope.values <- bind_value ~in_global_scope:false f scope.values scope.types
643705
| ConditionalTypeExtends _ -> ()
644706

645707
let rec lookup_value scope name =
@@ -704,41 +766,41 @@ module Scope = struct
704766
| Some l when l = loc -> Some scope
705767
| _ -> None)
706768

707-
let bind_local scope tbls name def k =
769+
let bind_local scope tbls name id_loc def k =
708770
let host = find_host scope def in
709-
bind host name (function
710-
| Some _ as existing_binding -> existing_binding
771+
bind host tbls name id_loc (function
772+
| Some _ as existing_binding -> (existing_binding, false)
711773
| None ->
712774
let node = push_local_def tbls def in
713775
k name node;
714-
Some (LocalBinding node)
776+
(Some (LocalBinding node), true)
715777
)
716778

717-
let bind_remote scope tbls name ref =
718-
bind scope name (function
719-
| Some _ as existing_binding -> existing_binding
779+
let bind_remote scope tbls name id_loc ref =
780+
bind scope tbls name id_loc (function
781+
| Some _ as existing_binding -> (existing_binding, false)
720782
| None ->
721783
let node = push_remote_ref tbls ref in
722-
Some (RemoteBinding node)
784+
(Some (RemoteBinding node), true)
723785
)
724786

725787
let bind_type scope tbls id_loc name def =
726-
bind_local ~type_only:true scope tbls name (TypeBinding { id_loc; def })
788+
bind_local ~type_only:true scope tbls name id_loc (TypeBinding { id_loc; def })
727789

728790
let bind_class scope tbls id_loc name def =
729-
bind_local ~type_only:false scope tbls name (ClassBinding { id_loc; name; def })
791+
bind_local ~type_only:false scope tbls name id_loc (ClassBinding { id_loc; name; def })
730792

731793
let bind_declare_class scope tbls id_loc name def =
732-
bind_local ~type_only:false scope tbls name (DeclareClassBinding { id_loc; name; def })
794+
bind_local ~type_only:false scope tbls name id_loc (DeclareClassBinding { id_loc; name; def })
733795

734796
let bind_enum scope tbls id_loc name def =
735-
bind_local ~type_only:false scope tbls name (EnumBinding { id_loc; name; def })
797+
bind_local ~type_only:false scope tbls name id_loc (EnumBinding { id_loc; name; def })
736798

737799
(* Function declarations preceded by declared functions are taken to have the
738800
* type of the declared functions. This is a weird special case aimed to
739801
* support overloaded signatures. *)
740802
let bind_function scope tbls id_loc fn_loc name ~async ~generator ~effect:_ def k =
741-
bind ~type_only:false scope name (fun binding_opt ->
803+
bind ~type_only:false scope tbls name id_loc (fun binding_opt ->
742804
match binding_opt with
743805
| None ->
744806
let statics = SMap.empty in
@@ -747,52 +809,60 @@ module Scope = struct
747809
in
748810
let node = push_local_def tbls def in
749811
k name node;
750-
Some (LocalBinding node)
751-
| Some (RemoteBinding _) -> binding_opt
812+
(Some (LocalBinding node), true)
813+
| Some (RemoteBinding _) -> (binding_opt, false)
752814
| Some (LocalBinding node) ->
753815
(match Local_defs.value node with
754816
| DeclareFunBinding _ -> k name node
755817
| _ -> ());
756-
binding_opt
818+
(binding_opt, false)
757819
)
758820

759821
(* Multiple declared functions with the same name in the same scope define an
760822
* overloaded function. Note that declared functions are block scoped, so we
761823
* don't need to walk the scope chain since the scope argument is certainly
762824
* the host scope. *)
763825
let bind_declare_function scope tbls id_loc fn_loc name def k =
764-
bind ~type_only:false scope name (fun binding_opt ->
826+
bind ~type_only:false scope tbls name id_loc (fun binding_opt ->
765827
match binding_opt with
766828
| None ->
767829
let defs_rev = Nel.one (id_loc, fn_loc, def) in
768830
let def = DeclareFunBinding { name; defs_rev } in
769831
let node = push_local_def tbls def in
770832
k name node;
771-
Some (LocalBinding node)
772-
| Some (RemoteBinding _) -> binding_opt
833+
(Some (LocalBinding node), true)
834+
| Some (RemoteBinding _) -> (binding_opt, false)
773835
| Some (LocalBinding node) ->
836+
let legal_ref = ref false in
774837
Local_defs.modify node (function
775838
| DeclareFunBinding { name; defs_rev } ->
776839
k name node;
840+
legal_ref := true;
777841
let defs_rev = Nel.cons (id_loc, fn_loc, def) defs_rev in
778842
DeclareFunBinding { name; defs_rev }
779843
| def -> def
780844
);
781-
binding_opt
845+
(binding_opt, !legal_ref)
782846
)
783847

784848
let bind_component scope tbls id_loc fn_loc name def =
785-
bind_local ~type_only:false scope tbls name (ComponentBinding { id_loc; fn_loc; name; def })
849+
bind_local
850+
~type_only:false
851+
scope
852+
tbls
853+
name
854+
id_loc
855+
(ComponentBinding { id_loc; fn_loc; name; def })
786856

787857
let bind_var scope tbls kind id_loc name def =
788-
bind_local ~type_only:false scope tbls name (value_binding kind id_loc name def)
858+
bind_local ~type_only:false scope tbls name id_loc (value_binding kind id_loc name def)
789859

790860
let bind_const scope tbls id_loc name def =
791-
bind_local ~type_only:false scope tbls name (LetConstBinding { id_loc; name; def })
861+
bind_local ~type_only:false scope tbls name id_loc (LetConstBinding { id_loc; name; def })
792862

793863
let bind_const_ref scope tbls id_loc name ref_loc ref_name ref_scope =
794864
let ref = Ref { ref_loc; name = ref_name; scope = ref_scope; resolved = None } in
795-
bind_local ~type_only:false scope tbls name (ConstRefBinding { id_loc; name; ref })
865+
bind_local ~type_only:false scope tbls name id_loc (ConstRefBinding { id_loc; name; ref })
796866

797867
let bind_const_fun scope tbls id_loc name loc ~async ~generator def =
798868
let statics = SMap.empty in
@@ -801,6 +871,7 @@ module Scope = struct
801871
scope
802872
tbls
803873
name
874+
id_loc
804875
(ConstFunBinding { id_loc; name; loc; async; generator; def; statics })
805876

806877
let bind_import scope tbls kind id_loc ~local ~remote mref =
@@ -813,7 +884,7 @@ module Scope = struct
813884
| ImportTypeof ->
814885
true
815886
in
816-
bind_remote ~type_only scope tbls local (import_binding kind id_loc local mref ~remote)
887+
bind_remote ~type_only scope tbls local id_loc (import_binding kind id_loc local mref ~remote)
817888

818889
let bind_import_ns scope tbls kind id_loc name mref =
819890
let mref = push_module_ref tbls mref in
@@ -825,7 +896,7 @@ module Scope = struct
825896
| ImportTypeof ->
826897
true
827898
in
828-
bind_remote ~type_only scope tbls name (import_ns_binding kind id_loc name mref)
899+
bind_remote ~type_only scope tbls name id_loc (import_ns_binding kind id_loc name mref)
829900

830901
let rec assign_binding =
831902
let f prop_name prop def =
@@ -1185,6 +1256,7 @@ module Scope = struct
11851256
parent
11861257
tbls
11871258
name
1259+
id_loc
11881260
(NamespaceBinding { id_loc; name; values; types })
11891261
ignore2
11901262
| _ -> failwith "The scope must be lexical"
@@ -1206,6 +1278,7 @@ module Scope = struct
12061278
scope
12071279
tbls
12081280
name
1281+
loc
12091282
(NamespaceBinding { id_loc = loc; name; values; types })
12101283
ignore2
12111284
| _ -> failwith "finalize_globalThis must be called after parsing all lib files on global scope"

tests/call_caching1/.flowconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ lib
77

88
[options]
99
all=true
10-
no_flowlib=true
10+
no_flowlib=false

tests/call_caching1/lib/core.js

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,4 @@
1-
declare class Array<T> {
2-
@@iterator(): Iterator<T>;
3-
map<U>(callbackfn: (value: T, index: number, array: Array<T>) => U, thisArg?: any): Array<U>;
4-
}
5-
6-
type IteratorResult<+Yield,+Return> =
7-
| { done: true, +value?: Return, ...}
8-
| { done: false, +value: Yield, ... };
9-
10-
interface $Iterator<+Yield,+Return,-Next> {
11-
@@iterator(): $Iterator<Yield,Return,Next>;
12-
next(value?: Next): IteratorResult<Yield,Return>;
13-
}
14-
type Iterator<+T> = $Iterator<T,void,void>;
15-
16-
interface $Iterable<+Yield,+Return,-Next> {
17-
@@iterator(): $Iterator<Yield,Return,Next>;
18-
}
19-
type Iterable<+T> = $Iterable<T,void,void>;
20-
21-
declare class Map<K, V> {
1+
declare class MyMap<K, V> {
222
@@iterator(): Iterator<[K, V]>;
233
constructor(iterable: ?Iterable<[K, V]>): void;
244
set(key: K, value: V): Map<K, V>;

tests/call_caching1/test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const Immutable = require('immutable');
22

3-
const tasksPerStatusMap = new Map(
4-
([]: Array<string>).map(taskStatus => [taskStatus, (new Map(): Map<string, string> | Immutable.Map<string, string>)]),
3+
const tasksPerStatusMap = new MyMap(
4+
([]: Array<string>).map(taskStatus => [taskStatus, (new MyMap(): MyMap<string, string> | Immutable.Map<string, string>)]),
55
);
66
for (let [taskStatus, tasksMap] of tasksPerStatusMap) {
77
tasksPerStatusMap.set(taskStatus, Immutable.Map(tasksMap));

tests/call_caching2/call_caching2.exp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ References:
1212
test.js:1:22
1313
1| function Foo(items: ?Iterable<number>) {
1414
^^^^^^^^^^^^^^^^ [1]
15-
lib/immutable.js:7:20
16-
7| static <T>(iter: Array<T>): Iterable<T>;
15+
lib/immutable.js:5:20
16+
5| static <T>(iter: Array<T>): Iterable<T>;
1717
^^^^^^^^ [2]
1818
test.js:2:21
1919
2| Iterable(items || []).size;
2020
^^ [3]
21-
lib/immutable.js:6:18
22-
6| static <V,Iter:Iterable<V>>(iter: Iter): Iter;
21+
lib/immutable.js:4:18
22+
4| static <V,Iter:Iterable<V>>(iter: Iter): Iter;
2323
^^^^^^^^^^^ [4]
2424

2525

tests/call_caching2/lib/immutable.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
// Copyright (c) Meta Platforms, Inc. and affiliates.
22

3-
declare class Array<T> { }
4-
53
declare class Iterable<S> {
64
static <V,Iter:Iterable<V>>(iter: Iter): Iter;
75
static <T>(iter: Array<T>): Iterable<T>;

tests/computed_race/.flowconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ lib.js
33

44
[options]
55
all=true
6+
no_flowlib=false

tests/computed_race/lib.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +0,0 @@
1-
declare class Object {
2-
static freeze<T>(o: T): T;
3-
}

tests/duplicate_libs/duplicate_libs.exp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
Error ------------------------------------------------------------------------------------------- lib/shared/lib.js:3:13
2+
3+
This name declaration overrides an existing binding `global_foo` [1]. Overriding in library definitions can lead to
4+
surprising behaviors. [libdef-override]
5+
6+
3| declare var global_foo: number; // intentional-libdef-override
7+
^^^^^^^^^^ [1]
8+
9+
110
Error ------------------------------------------------------------------------------------------------------ test.js:1:2
211

312
Cannot cast `global_foo` to string because number [1] is incompatible with string [2]. [incompatible-cast]
@@ -8,12 +17,12 @@ Cannot cast `global_foo` to string because number [1] is incompatible with strin
817

918
References:
1019
lib/shared/lib.js:3:25
11-
3| declare var global_foo: number;
20+
3| declare var global_foo: number; // intentional-libdef-override
1221
^^^^^^ [1]
1322
test.js:1:14
1423
1| (global_foo: string); // error: number ~> string
1524
^^^^^^ [2]
1625

1726

1827

19-
Found 1 error
28+
Found 2 errors
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
// The lib file is included twice, because it is included in both `lib` and
22
// `lib/shared`, which are both listed in the `.flowconfig`
3-
declare var global_foo: number;
3+
declare var global_foo: number; // intentional-libdef-override

0 commit comments

Comments
 (0)