Skip to content

Commit f5e33d0

Browse files
panagosg7meta-codesync[bot]
authored andcommitted
[flow] disallow current identifier in default value of parameter
Summary: Error on definitions of the form: ``` function f(x: number = x) {} ``` where the default expression contains a reference to the parameter being defined. The key idea of the fix is to keep track of the current pattern bindings introduced by a parameter when visiting the default expression (if one exists). If, when visiting the expression, we encounter one of the current identifiers we raise a new error. Changelog: [fix] Flow will now error when a function parameter appears in the default expression for the same binding. (e.g. [try-Flow](https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkUEKTwJEJ+DAkMiUFSwkyZCC3dbdAC+-EgGiSMAeyjg0AABDAABTyZDS4QiG7SgC80vkAEppcABdKAPSG6UAdzgx2lJkovKV8mlLExLEe7WlElq2GljhoImlOAqCGlGieVFKAB1YBL4VBpQg5cAMAKlcDaBrpRhdfqjSbzZbrbb0w7NCZBJCSG6PV6nL7-VBA8HcKG2BHpbkQA0TCQpVAkg0AAxWABMAE4ACxWPsgAVAA)) Reviewed By: SamChou19815 Differential Revision: D89254628 fbshipit-source-id: efbeff0830b995956b233484a46df27b20c9c209
1 parent abf3e05 commit f5e33d0

File tree

7 files changed

+295
-6
lines changed

7 files changed

+295
-6
lines changed

src/analysis/env_builder/name_resolver.ml

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,9 @@ module Make (Context : C) (FlowAPIUtils : F with type cx = Context.t) :
711711
(* Track parameter binding def_locs currently being processed, so that we can
712712
error when these appear in the corresponding annotation. *)
713713
current_bindings: string L.LMap.t;
714+
(* Track when we're visiting a parameter default expression, so we can
715+
produce the appropriate error message for self-references. *)
716+
in_param_default: bool;
714717
}
715718

716719
type pattern_write_kind =
@@ -1110,6 +1113,7 @@ module Make (Context : C) (FlowAPIUtils : F with type cx = Context.t) :
11101113
jsx_base_name;
11111114
pred_func_map = L.LMap.empty;
11121115
current_bindings = L.LMap.empty;
1116+
in_param_default = false;
11131117
}
11141118

11151119
method jsx_base_name = env_state.jsx_base_name
@@ -2259,6 +2263,31 @@ module Make (Context : C) (FlowAPIUtils : F with type cx = Context.t) :
22592263
| _ -> ());
22602264
env_state <- { env_state with write_entries }
22612265

2266+
(* Override pattern_object_property to detect SELF-references in pattern-inline defaults.
2267+
For {a = a}, check if a's default references a itself (self-reference) - ERROR.
2268+
For {a, b = a}, b's default references a, not b - OK (not a self-reference).
2269+
For {a = b, b}, a's default references b - handled by reference-before-declaration.
2270+
We only check for self-references here; forward references are caught elsewhere. *)
2271+
method! pattern_object_property ?kind prop =
2272+
let open Ast.Pattern.Object.Property in
2273+
let (loc, { key; pattern; default; shorthand }) = prop in
2274+
ignore @@ this#pattern_object_property_key ?kind key;
2275+
ignore @@ this#pattern_object_property_pattern ?kind pattern;
2276+
Base.Option.iter default ~f:(fun default_expr ->
2277+
this#visit_default_with_pattern_bindings pattern default_expr
2278+
);
2279+
(loc, { key; pattern; default; shorthand })
2280+
2281+
method! function_param param =
2282+
let open Ast.Function.Param in
2283+
let (loc, { argument; default }) = param in
2284+
this#visit_function_or_component_param_pattern ~is_rest:false argument;
2285+
ignore @@ super#function_param_pattern argument;
2286+
Base.Option.iter default ~f:(fun default_expr ->
2287+
this#visit_default_with_pattern_bindings argument default_expr
2288+
);
2289+
(loc, { argument; default })
2290+
22622291
(* This method is called during every read of an identifier. We need to ensure that
22632292
* if the identifier is refined that we record the refiner as the write that reaches
22642293
* this read
@@ -2308,6 +2337,18 @@ module Make (Context : C) (FlowAPIUtils : F with type cx = Context.t) :
23082337
env_state <- { env_state with current_bindings = old_val }
23092338
)
23102339

2340+
method private with_in_param_default ~f =
2341+
let old_in_param_default = env_state.in_param_default in
2342+
env_state <- { env_state with in_param_default = true };
2343+
Exception.protect ~f ~finally:(fun () ->
2344+
env_state <- { env_state with in_param_default = old_in_param_default }
2345+
)
2346+
2347+
method private visit_default_with_pattern_bindings pattern default_expr =
2348+
this#with_current_pattern_bindings pattern ~f:(fun () ->
2349+
this#with_in_param_default ~f:(fun () -> ignore @@ this#expression default_expr)
2350+
)
2351+
23112352
(* Override the object type constuctor to disable the EReferenceInAnnotation check
23122353
* since this is a common and safe way to encode recursive object types. *)
23132354
method! object_type loc ot =
@@ -2334,6 +2375,20 @@ module Make (Context : C) (FlowAPIUtils : F with type cx = Context.t) :
23342375
add_output (Error_message.EReferenceInAnnotation (def_loc, name, loc))
23352376
)
23362377

2378+
method private error_on_reference_to_currently_declared_id_in_default id =
2379+
let (loc, { Ast.Identifier.name; _ }) = id in
2380+
let { val_ref = _; def_loc; _ } = this#env_read name in
2381+
Base.Option.iter def_loc ~f:(fun def_loc ->
2382+
match L.LMap.find_opt def_loc env_state.current_bindings with
2383+
| None -> ()
2384+
| Some binding_name ->
2385+
(* Only add the error - do NOT modify binding state (val_ref, write_entries).
2386+
For forward references (e.g., `b` in `{a = b, b}`), modifying the binding
2387+
state would corrupt it and cause spurious "name-already-bound" errors
2388+
when the actual binding is later processed. *)
2389+
add_output (Error_message.EReferenceInDefault (def_loc, binding_name, loc))
2390+
)
2391+
23372392
method! type_identifier_reference id =
23382393
this#error_on_reference_to_currently_declared_id id;
23392394
super#type_identifier_reference id
@@ -2352,6 +2407,8 @@ module Make (Context : C) (FlowAPIUtils : F with type cx = Context.t) :
23522407

23532408
method! identifier (ident : (ALoc.t, ALoc.t) Ast.Identifier.t) =
23542409
let (loc, { Ast.Identifier.name = x; comments = _ }) = ident in
2410+
if env_state.in_param_default then
2411+
this#error_on_reference_to_currently_declared_id_in_default ident;
23552412
this#any_identifier loc x;
23562413
super#identifier ident
23572414

@@ -6513,17 +6570,27 @@ module Make (Context : C) (FlowAPIUtils : F with type cx = Context.t) :
65136570
| _ -> ()
65146571

65156572
method! component_param param =
6516-
let (_, { Ast.Statement.ComponentDeclaration.Param.name; _ }) = param in
6573+
let (loc, { Ast.Statement.ComponentDeclaration.Param.name; local; default; shorthand }) =
6574+
param
6575+
in
65176576
begin
65186577
match name with
65196578
| Ast.Statement.ComponentDeclaration.Param.Identifier
6520-
(loc, { Ast.Identifier.name = "ref"; _ })
6579+
(ref_loc, { Ast.Identifier.name = "ref"; _ })
65216580
| Ast.Statement.ComponentDeclaration.Param.StringLiteral
6522-
(loc, { Ast.StringLiteral.value = "ref"; _ }) ->
6523-
this#any_identifier loc "React"
6581+
(ref_loc, { Ast.StringLiteral.value = "ref"; _ }) ->
6582+
this#any_identifier ref_loc "React"
65246583
| _ -> ()
65256584
end;
6526-
super#component_param param
6585+
ignore @@ this#component_param_name name;
6586+
this#visit_function_or_component_param_pattern ~is_rest:false local;
6587+
this#with_current_pattern_bindings local ~f:(fun () ->
6588+
ignore @@ super#component_param_pattern local;
6589+
Base.Option.iter default ~f:(fun default_expr ->
6590+
this#with_in_param_default ~f:(fun () -> ignore @@ this#expression default_expr)
6591+
)
6592+
);
6593+
(loc, { Ast.Statement.ComponentDeclaration.Param.name; local; default; shorthand })
65276594

65286595
method! jsx_element loc expr =
65296596
let open Ast.JSX in

src/typing/debug_js.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1953,6 +1953,7 @@ let dump_error_message =
19531953
| EDuplicateClassMember { loc; name; _ } ->
19541954
spf "EDuplicateClassMember (%s) (%s)" (string_of_aloc loc) name
19551955
| EReferenceInAnnotation _ -> "EReferenceInAnnotation"
1956+
| EReferenceInDefault _ -> "EReferenceInDefault"
19561957
| EEmptyArrayNoProvider { loc } -> spf "EEmptyArrayNoProvider (%s)" (string_of_aloc loc)
19571958
| EUnusedPromise { loc; _ } -> spf "EUnusedPromise (%s)" (string_of_aloc loc)
19581959
| EReactIntrinsicOverlap _ -> "EReactIntrinsicOverlap (_, _, _)"

src/typing/errors/error_message.ml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,7 @@ and 'loc t' =
621621
annot_locs: 'loc Env_api.annot_loc list;
622622
}
623623
| EReferenceInAnnotation of ('loc * string * 'loc)
624+
| EReferenceInDefault of ('loc * string * 'loc)
624625
| EDuplicateClassMember of {
625626
loc: 'loc;
626627
name: string;
@@ -1761,6 +1762,7 @@ let rec map_loc_of_error_message (f : 'a -> 'b) : 'a t' -> 'b t' =
17611762
recursion = Base.List.map ~f recursion;
17621763
}
17631764
| EReferenceInAnnotation (bind_loc, name, loc) -> EReferenceInAnnotation (f bind_loc, name, f loc)
1765+
| EReferenceInDefault (bind_loc, name, loc) -> EReferenceInDefault (f bind_loc, name, f loc)
17641766
| EDuplicateClassMember { loc; name; static; class_kind } ->
17651767
EDuplicateClassMember { loc = f loc; name; static; class_kind }
17661768
| EEmptyArrayNoProvider { loc } -> EEmptyArrayNoProvider { loc = f loc }
@@ -2243,6 +2245,7 @@ let util_use_op_of_msg nope util = function
22432245
| EDefinitionCycle _
22442246
| ERecursiveDefinition _
22452247
| EReferenceInAnnotation _
2248+
| EReferenceInDefault _
22462249
| EAnnotationInference _
22472250
| ETrivialRecursiveDefinition _
22482251
| EDuplicateClassMember _
@@ -2473,6 +2476,7 @@ let loc_of_msg : 'loc t' -> 'loc option = function
24732476
| EInvalidMappedType { loc; _ }
24742477
| ETSSyntax { loc; _ }
24752478
| EReferenceInAnnotation (loc, _, _)
2479+
| EReferenceInDefault (_, _, loc)
24762480
| EDuplicateComponentProp { spread = loc; _ }
24772481
| ERefComponentProp { spread = loc; _ }
24782482
| EKeySpreadProp { spread = loc; _ }
@@ -3661,6 +3665,8 @@ let friendly_message_of_msg = function
36613665
| EDefinitionCycle dependencies -> Normal (MessageDefinitionCycle dependencies)
36623666
| EReferenceInAnnotation (_, name, loc) ->
36633667
Normal (MessageInvalidSelfReferencingTypeAnnotation { name; loc })
3668+
| EReferenceInDefault (def_loc, name, ref_loc) ->
3669+
Normal (MessageInvalidSelfReferencingDefault { name; def_loc; ref_loc })
36643670
| EUnusedPromise { async = true; _ } -> Normal MessageUnusedPromiseInAsyncScope
36653671
| EUnusedPromise { async = false; _ } -> Normal MessageUnusedPromiseInSyncScope
36663672
| EReactIntrinsicOverlap { use; def; type_; mixed } ->
@@ -4111,6 +4117,7 @@ let error_code_of_message err : error_code option =
41114117
| EDefinitionCycle _ -> Some DefinitionCycle
41124118
| ERecursiveDefinition _ -> Some RecursiveDefinition
41134119
| EReferenceInAnnotation _ -> Some RecursiveDefinition
4120+
| EReferenceInDefault _ -> Some RecursiveDefinition
41144121
| EDuplicateClassMember _ -> Some DuplicateClassMember
41154122
| EEmptyArrayNoProvider _ -> Some EmptyArrayNoAnnot
41164123
| EBigIntRShift3 _ -> Some BigIntRShift3

src/typing/errors/flow_intermediate_error.ml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4076,6 +4076,14 @@ let to_printable_error :
40764076
hardcoded_string_desc_ref "reference" loc;
40774077
text " to the binding being declared.";
40784078
]
4079+
| MessageInvalidSelfReferencingDefault { name; def_loc; ref_loc } ->
4080+
[
4081+
text "Invalid default expression for parameter ";
4082+
hardcoded_string_desc_ref name def_loc;
4083+
text ". It contains a ";
4084+
hardcoded_string_desc_ref "reference" ref_loc;
4085+
text " to the binding being declared.";
4086+
]
40794087
| MessageInvalidTrivialRecursiveDefinition description ->
40804088
[text "Invalid trivially recursive definition of "; desc description; text ". "]
40814089
| MessageInvalidTupleRequiredAfterOptional { reason_tuple; reason_required; reason_optional } ->

src/typing/errors/flow_intermediate_error_types.ml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,11 @@ type 'loc message =
935935
name: string;
936936
loc: 'loc;
937937
}
938+
| MessageInvalidSelfReferencingDefault of {
939+
name: string;
940+
def_loc: 'loc;
941+
ref_loc: 'loc;
942+
}
938943
| MessageInvalidTrivialRecursiveDefinition of 'loc virtual_reason_desc
939944
| MessageInvalidTupleRequiredAfterOptional of {
940945
reason_tuple: 'loc virtual_reason;

tests/binding/binding.exp

Lines changed: 166 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1416,6 +1416,171 @@ References:
14161416
^^^ [1]
14171417

14181418

1419+
Error ----------------------------------------------------------------------------------------------- rec_decls.js:31:26
1420+
1421+
Invalid default expression for parameter x [1]. It contains a reference [2] to the binding being declared.
1422+
[recursive-definition]
1423+
1424+
rec_decls.js:31:26
1425+
31| function f(x: number = x) {} // error: x references the param being declared
1426+
^ [2]
1427+
1428+
References:
1429+
rec_decls.js:31:14
1430+
31| function f(x: number = x) {} // error: x references the param being declared
1431+
^ [1]
1432+
1433+
1434+
Error ----------------------------------------------------------------------------------------------- rec_decls.js:32:33
1435+
1436+
Cannot assign `a` to object type because number [1] is incompatible with object type [2]. [incompatible-type]
1437+
1438+
rec_decls.js:32:33
1439+
32| function g({a}: {a: number} = a) {} // error: a references the param being declared
1440+
^
1441+
1442+
References:
1443+
rec_decls.js:32:23
1444+
32| function g({a}: {a: number} = a) {} // error: a references the param being declared
1445+
^^^^^^ [1]
1446+
rec_decls.js:32:19
1447+
32| function g({a}: {a: number} = a) {} // error: a references the param being declared
1448+
^^^^^^^^^^^ [2]
1449+
1450+
1451+
Error ----------------------------------------------------------------------------------------------- rec_decls.js:32:33
1452+
1453+
Invalid default expression for parameter a [1]. It contains a reference [2] to the binding being declared.
1454+
[recursive-definition]
1455+
1456+
rec_decls.js:32:33
1457+
32| function g({a}: {a: number} = a) {} // error: a references the param being declared
1458+
^ [2]
1459+
1460+
References:
1461+
rec_decls.js:32:15
1462+
32| function g({a}: {a: number} = a) {} // error: a references the param being declared
1463+
^ [1]
1464+
1465+
1466+
Error ----------------------------------------------------------------------------------------------- rec_decls.js:33:26
1467+
1468+
Invalid default expression for parameter y [1]. It contains a reference [2] to the binding being declared.
1469+
[recursive-definition]
1470+
1471+
rec_decls.js:33:26
1472+
33| function h(y: number = y + 1) {} // error: y references the param being declared
1473+
^ [2]
1474+
1475+
References:
1476+
rec_decls.js:33:14
1477+
33| function h(y: number = y + 1) {} // error: y references the param being declared
1478+
^ [1]
1479+
1480+
1481+
Error ----------------------------------------------------------------------------------------------- rec_decls.js:34:26
1482+
1483+
Invalid default expression for parameter x [1]. It contains a reference [2] to the binding being declared.
1484+
[recursive-definition]
1485+
1486+
rec_decls.js:34:26
1487+
34| function i(x: number = x + x) {} // error: x references the param being declared (twice)
1488+
^ [2]
1489+
1490+
References:
1491+
rec_decls.js:34:14
1492+
34| function i(x: number = x + x) {} // error: x references the param being declared (twice)
1493+
^ [1]
1494+
1495+
1496+
Error ----------------------------------------------------------------------------------------------- rec_decls.js:34:30
1497+
1498+
Invalid default expression for parameter x [1]. It contains a reference [2] to the binding being declared.
1499+
[recursive-definition]
1500+
1501+
rec_decls.js:34:30
1502+
34| function i(x: number = x + x) {} // error: x references the param being declared (twice)
1503+
^ [2]
1504+
1505+
References:
1506+
rec_decls.js:34:14
1507+
34| function i(x: number = x + x) {} // error: x references the param being declared (twice)
1508+
^ [1]
1509+
1510+
1511+
Error ----------------------------------------------------------------------------------------------- rec_decls.js:40:40
1512+
1513+
Cannot use variable `b` [1] because the declaration either comes later or was skipped. [reference-before-declaration]
1514+
1515+
rec_decls.js:40:40
1516+
40| function default_expr_scope_err({a = b, b}: {a?: string, b: string}) {} // error: a's default references b, which comes after
1517+
^
1518+
1519+
References:
1520+
rec_decls.js:40:43
1521+
40| function default_expr_scope_err({a = b, b}: {a?: string, b: string}) {} // error: a's default references b, which comes after
1522+
^ [1]
1523+
1524+
1525+
Error ----------------------------------------------------------------------------------------------- rec_decls.js:44:20
1526+
1527+
Invalid default expression for parameter c [1]. It contains a reference [2] to the binding being declared.
1528+
[recursive-definition]
1529+
1530+
rec_decls.js:44:20
1531+
44| {a, b = a, c = c}: {a: string, b?: string, c?: string}, // b = a OK (earlier), c = c ERROR (self)
1532+
^ [2]
1533+
1534+
References:
1535+
rec_decls.js:44:16
1536+
44| {a, b = a, c = c}: {a: string, b?: string, c?: string}, // b = a OK (earlier), c = c ERROR (self)
1537+
^ [1]
1538+
1539+
1540+
Error ----------------------------------------------------------------------------------------------- rec_decls.js:45:24
1541+
1542+
Cannot use variable `g` [1] because the declaration either comes later or was skipped. [reference-before-declaration]
1543+
1544+
rec_decls.js:45:24
1545+
45| {d = a, e = d, f = g, g}: {d?: string, e?: string, f?: string, g: string}, // d = a OK (earlier param), e = d OK (earlier in same pattern), f = g ERROR (forward)
1546+
^
1547+
1548+
References:
1549+
rec_decls.js:45:27
1550+
45| {d = a, e = d, f = g, g}: {d?: string, e?: string, f?: string, g: string}, // d = a OK (earlier param), e = d OK (earlier in same pattern), f = g ERROR (forward)
1551+
^ [1]
1552+
1553+
1554+
Error ----------------------------------------------------------------------------------------------- rec_decls.js:58:19
1555+
1556+
Invalid default expression for parameter z [1]. It contains a reference [2] to the binding being declared.
1557+
[recursive-definition]
1558+
1559+
rec_decls.js:58:19
1560+
58| function Foo({z = z ?? null}: O) { // error: z references the param being declared
1561+
^ [2]
1562+
1563+
References:
1564+
rec_decls.js:58:15
1565+
58| function Foo({z = z ?? null}: O) { // error: z references the param being declared
1566+
^ [1]
1567+
1568+
1569+
Error ----------------------------------------------------------------------------------------------- rec_decls.js:62:28
1570+
1571+
Invalid default expression for parameter scope [1]. It contains a reference [2] to the binding being declared.
1572+
[recursive-definition]
1573+
1574+
rec_decls.js:62:28
1575+
62| component Comp(...{scope = scope ?? null}: {scope?: ?string}) { // error: scope references the param being declared
1576+
^^^^^ [2]
1577+
1578+
References:
1579+
rec_decls.js:62:20
1580+
62| component Comp(...{scope = scope ?? null}: {scope?: ?string}) { // error: scope references the param being declared
1581+
^^^^^ [1]
1582+
1583+
14191584
Error ----------------------------------------------------------------------------------------------- rec_params.js:7:26
14201585

14211586
Cannot resolve name `x`. [cannot-resolve-name]
@@ -2161,4 +2326,4 @@ Cannot resolve name `unboundFunction2`. [cannot-resolve-name]
21612326

21622327

21632328

2164-
Found 164 errors
2329+
Found 175 errors

0 commit comments

Comments
 (0)