Skip to content

Commit b09a575

Browse files
committed
Fixed crash related to JS synthethic rest param and preceeding parameters with initializers
1 parent 60f93aa commit b09a575

7 files changed

+437
-33
lines changed

src/compiler/checker.ts

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11666,7 +11666,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1166611666
if (!links.type) {
1166711667
const type = getTypeOfVariableOrParameterOrPropertyWorker(symbol, checkMode);
1166811668
// For a contextually typed parameter it is possible that a type has already
11669-
// been assigned (in assignTypeToParameterAndFixTypeParameters), and we want
11669+
// been assigned (in contextuallyCheckFunctionExpressionOrObjectLiteralMethod), and we want
1167011670
// to preserve this type. In fact, we need to _prefer_ that type, but it won't
1167111671
// be assigned until contextual typing is complete, so we need to defer in
1167211672
// cases where contextual typing may take place.
@@ -12057,6 +12057,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1205712057
if (!links.type) {
1205812058
Debug.assertIsDefined(links.deferralParent);
1205912059
Debug.assertIsDefined(links.deferralConstituents);
12060+
if (links.deferralParent === neverType) {
12061+
const functionDecl = links.jsSyntheticRestParameterFunctionDeclaration!;
12062+
if (isFunctionExpressionOrArrowFunction(functionDecl)) {
12063+
const contextualSignature = getContextualSignature(functionDecl);
12064+
if (contextualSignature) {
12065+
return getRestTypeAtPosition(contextualSignature, functionDecl.parameters.length);
12066+
}
12067+
}
12068+
}
1206012069
links.type = links.deferralParent.flags & TypeFlags.Union ? getUnionType(links.deferralConstituents) : getIntersectionType(links.deferralConstituents);
1206112070
}
1206212071
return links.type;
@@ -15296,7 +15305,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1529615305
* 2. It has at least one parameter, and the last parameter has a matching `@param` with a type that starts with `...`
1529715306
*/
1529815307
function maybeAddJsSyntheticRestParameter(declaration: SignatureDeclaration | JSDocSignature, parameters: Symbol[]): boolean {
15299-
if (isJSDocSignature(declaration) || !containsArgumentsReference(declaration)) {
15308+
if (!isFunctionLikeDeclaration(declaration) || !containsArgumentsReference(declaration)) {
1530015309
return false;
1530115310
}
1530215311
const lastParam = lastOrUndefined(declaration.parameters);
@@ -15316,6 +15325,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1531615325
syntheticArgsSymbol.links.deferralParent = neverType;
1531715326
syntheticArgsSymbol.links.deferralConstituents = [anyArrayType];
1531815327
syntheticArgsSymbol.links.deferralWriteConstituents = [anyArrayType];
15328+
syntheticArgsSymbol.links.jsSyntheticRestParameterFunctionDeclaration = declaration;
1531915329
}
1532015330
if (lastParamVariadicType) {
1532115331
// Replace the last parameter with a rest parameter.
@@ -15351,12 +15361,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1535115361
links.containsArgumentsReference = true;
1535215362
}
1535315363
else {
15354-
links.containsArgumentsReference = traverse((declaration as FunctionLikeDeclaration).body!);
15364+
links.containsArgumentsReference = traverse((declaration as FunctionLikeDeclaration).body);
1535515365
}
1535615366
}
1535715367
return links.containsArgumentsReference;
1535815368

15359-
function traverse(node: Node): boolean {
15369+
function traverse(node: Node | undefined): boolean {
1536015370
if (!node) return false;
1536115371
switch (node.kind) {
1536215372
case SyntaxKind.Identifier:
@@ -15367,7 +15377,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1536715377
case SyntaxKind.GetAccessor:
1536815378
case SyntaxKind.SetAccessor:
1536915379
return (node as NamedDeclaration).name!.kind === SyntaxKind.ComputedPropertyName
15370-
&& traverse((node as NamedDeclaration).name!);
15380+
&& traverse((node as NamedDeclaration).name);
1537115381

1537215382
case SyntaxKind.PropertyAccessExpression:
1537315383
case SyntaxKind.ElementAccessExpression:
@@ -49586,37 +49596,41 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4958649596
}
4958749597

4958849598
function checkGrammarParameterList(parameters: NodeArray<ParameterDeclaration>) {
49589-
let seenOptionalParameter = false;
49590-
const parameterCount = parameters.length;
49599+
// This is lazy to avoid fixing the type of JS synthetic rest parameters before contextual typing has a chance to assign it.
49600+
// `isOptionalParameter` could fix it when checking the minimum arg count - that needs the rest's type for parameters with initializers.
49601+
addLazyDiagnostic(() => {
49602+
let seenOptionalParameter = false;
49603+
const parameterCount = parameters.length;
4959149604

49592-
for (let i = 0; i < parameterCount; i++) {
49593-
const parameter = parameters[i];
49594-
if (parameter.dotDotDotToken) {
49595-
if (i !== (parameterCount - 1)) {
49596-
return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list);
49597-
}
49598-
if (!(parameter.flags & NodeFlags.Ambient)) { // Allow `...foo,` in ambient declarations; see GH#23070
49599-
checkGrammarForDisallowedTrailingComma(parameters, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma);
49600-
}
49605+
for (let i = 0; i < parameterCount; i++) {
49606+
const parameter = parameters[i];
49607+
if (parameter.dotDotDotToken) {
49608+
if (i !== (parameterCount - 1)) {
49609+
return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list);
49610+
}
49611+
if (!(parameter.flags & NodeFlags.Ambient)) { // Allow `...foo,` in ambient declarations; see GH#23070
49612+
checkGrammarForDisallowedTrailingComma(parameters, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma);
49613+
}
4960149614

49602-
if (parameter.questionToken) {
49603-
return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_rest_parameter_cannot_be_optional);
49604-
}
49615+
if (parameter.questionToken) {
49616+
return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_rest_parameter_cannot_be_optional);
49617+
}
4960549618

49606-
if (parameter.initializer) {
49607-
return grammarErrorOnNode(parameter.name, Diagnostics.A_rest_parameter_cannot_have_an_initializer);
49619+
if (parameter.initializer) {
49620+
return grammarErrorOnNode(parameter.name, Diagnostics.A_rest_parameter_cannot_have_an_initializer);
49621+
}
4960849622
}
49609-
}
49610-
else if (isOptionalParameter(parameter)) {
49611-
seenOptionalParameter = true;
49612-
if (parameter.questionToken && parameter.initializer) {
49613-
return grammarErrorOnNode(parameter.name, Diagnostics.Parameter_cannot_have_question_mark_and_initializer);
49623+
else if (isOptionalParameter(parameter)) {
49624+
seenOptionalParameter = true;
49625+
if (parameter.questionToken && parameter.initializer) {
49626+
return grammarErrorOnNode(parameter.name, Diagnostics.Parameter_cannot_have_question_mark_and_initializer);
49627+
}
49628+
}
49629+
else if (seenOptionalParameter && !parameter.initializer) {
49630+
return grammarErrorOnNode(parameter.name, Diagnostics.A_required_parameter_cannot_follow_an_optional_parameter);
4961449631
}
4961549632
}
49616-
else if (seenOptionalParameter && !parameter.initializer) {
49617-
return grammarErrorOnNode(parameter.name, Diagnostics.A_required_parameter_cannot_follow_an_optional_parameter);
49618-
}
49619-
}
49633+
});
4962049634
}
4962149635

4962249636
function getNonSimpleParameters(parameters: readonly ParameterDeclaration[]): readonly ParameterDeclaration[] {

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5897,6 +5897,7 @@ export interface SymbolLinks {
58975897
tupleLabelDeclaration?: NamedTupleMember | ParameterDeclaration; // Declaration associated with the tuple's label
58985898
accessibleChainCache?: Map<string, Symbol[] | undefined>;
58995899
filteredIndexSymbolCache?: Map<string, Symbol> //Symbol with applicable declarations
5900+
jsSyntheticRestParameterFunctionDeclaration?: FunctionLikeDeclaration; // function declaration to which the js synthetic rest parameter belongs to, its contextual signature might be looked up
59005901
}
59015902

59025903
/** @internal */
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
index.js(24,9): error TS7005: Variable 'args' implicitly has an 'any[]' type.
2+
index.js(31,9): error TS7005: Variable 'args' implicitly has an 'any[]' type.
3+
index.js(36,9): error TS7005: Variable 'args' implicitly has an 'any[]' type.
4+
index.js(41,9): error TS7005: Variable 'args' implicitly has an 'any[]' type.
5+
6+
7+
==== index.js (4 errors) ====
8+
'use strict';
9+
10+
// https://github.com/microsoft/TypeScript/issues/57435
11+
12+
/** @type {globalThis['structuredClone']} */
13+
const structuredClone =
14+
globalThis.structuredClone ??
15+
function structuredClone (value, options = undefined) {
16+
if (arguments.length === 0) {
17+
throw new TypeError('missing argument')
18+
}
19+
return value;
20+
}
21+
22+
/** @type {(a: number, b: boolean | undefined, ...rest: string[]) => void} */
23+
const test1 = function(value, options = undefined) {
24+
if (arguments.length === 0) {
25+
throw new TypeError('missing argument')
26+
}
27+
}
28+
29+
/** @type {(a: number, b: boolean | undefined, ...rest: string[]) => void} */
30+
const test2 = function inner(value, options = undefined) {
31+
const args = [].slice.call(arguments);
32+
~~~~
33+
!!! error TS7005: Variable 'args' implicitly has an 'any[]' type.
34+
35+
inner(1, true, 'hello', 'world');
36+
}
37+
38+
/** @type {(a: number, b: boolean | undefined) => void} */
39+
const test3 = function inner(value, options = undefined) {
40+
const args = [].slice.call(arguments);
41+
~~~~
42+
!!! error TS7005: Variable 'args' implicitly has an 'any[]' type.
43+
}
44+
45+
/** @type {(a: number, b: boolean | undefined, ...rest: [string?, ...number[]]) => void} */
46+
const test4 = function inner(value, options = undefined) {
47+
const args = [].slice.call(arguments);
48+
~~~~
49+
!!! error TS7005: Variable 'args' implicitly has an 'any[]' type.
50+
}
51+
52+
/** @type {(a: number, b: boolean | undefined, ...rest: [string, ...number[]]) => void} */
53+
const test5 = function inner(value, options = undefined) {
54+
const args = [].slice.call(arguments);
55+
~~~~
56+
!!! error TS7005: Variable 'args' implicitly has an 'any[]' type.
57+
}
58+
59+
export {}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//// [tests/cases/compiler/argumentsReferenceAndParameterWithInitializer1.ts] ////
2+
3+
=== index.js ===
4+
'use strict';
5+
6+
// https://github.com/microsoft/TypeScript/issues/57435
7+
8+
/** @type {globalThis['structuredClone']} */
9+
const structuredClone =
10+
>structuredClone : Symbol(structuredClone, Decl(index.js, 5, 5))
11+
12+
globalThis.structuredClone ??
13+
>globalThis.structuredClone : Symbol(structuredClone, Decl(lib.dom.d.ts, --, --))
14+
>globalThis : Symbol(globalThis)
15+
>structuredClone : Symbol(structuredClone, Decl(lib.dom.d.ts, --, --))
16+
17+
function structuredClone (value, options = undefined) {
18+
>structuredClone : Symbol(structuredClone, Decl(index.js, 6, 31))
19+
>value : Symbol(value, Decl(index.js, 7, 28))
20+
>options : Symbol(options, Decl(index.js, 7, 34))
21+
>undefined : Symbol(undefined)
22+
23+
if (arguments.length === 0) {
24+
>arguments.length : Symbol(IArguments.length, Decl(lib.es5.d.ts, --, --))
25+
>arguments : Symbol(arguments)
26+
>length : Symbol(IArguments.length, Decl(lib.es5.d.ts, --, --))
27+
28+
throw new TypeError('missing argument')
29+
>TypeError : Symbol(TypeError, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
30+
}
31+
return value;
32+
>value : Symbol(value, Decl(index.js, 7, 28))
33+
}
34+
35+
/** @type {(a: number, b: boolean | undefined, ...rest: string[]) => void} */
36+
const test1 = function(value, options = undefined) {
37+
>test1 : Symbol(test1, Decl(index.js, 15, 5))
38+
>value : Symbol(value, Decl(index.js, 15, 23))
39+
>options : Symbol(options, Decl(index.js, 15, 29))
40+
>undefined : Symbol(undefined)
41+
42+
if (arguments.length === 0) {
43+
>arguments.length : Symbol(IArguments.length, Decl(lib.es5.d.ts, --, --))
44+
>arguments : Symbol(arguments)
45+
>length : Symbol(IArguments.length, Decl(lib.es5.d.ts, --, --))
46+
47+
throw new TypeError('missing argument')
48+
>TypeError : Symbol(TypeError, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
49+
}
50+
}
51+
52+
/** @type {(a: number, b: boolean | undefined, ...rest: string[]) => void} */
53+
const test2 = function inner(value, options = undefined) {
54+
>test2 : Symbol(test2, Decl(index.js, 22, 5))
55+
>inner : Symbol(inner, Decl(index.js, 22, 13))
56+
>value : Symbol(value, Decl(index.js, 22, 29))
57+
>options : Symbol(options, Decl(index.js, 22, 35))
58+
>undefined : Symbol(undefined)
59+
60+
const args = [].slice.call(arguments);
61+
>args : Symbol(args, Decl(index.js, 23, 7))
62+
>[].slice.call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
63+
>[].slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --))
64+
>slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --))
65+
>call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
66+
>arguments : Symbol(arguments)
67+
68+
inner(1, true, 'hello', 'world');
69+
>inner : Symbol(inner, Decl(index.js, 22, 13))
70+
}
71+
72+
/** @type {(a: number, b: boolean | undefined) => void} */
73+
const test3 = function inner(value, options = undefined) {
74+
>test3 : Symbol(test3, Decl(index.js, 29, 5))
75+
>inner : Symbol(inner, Decl(index.js, 29, 13))
76+
>value : Symbol(value, Decl(index.js, 29, 29))
77+
>options : Symbol(options, Decl(index.js, 29, 35))
78+
>undefined : Symbol(undefined)
79+
80+
const args = [].slice.call(arguments);
81+
>args : Symbol(args, Decl(index.js, 30, 7))
82+
>[].slice.call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
83+
>[].slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --))
84+
>slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --))
85+
>call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
86+
>arguments : Symbol(arguments)
87+
}
88+
89+
/** @type {(a: number, b: boolean | undefined, ...rest: [string?, ...number[]]) => void} */
90+
const test4 = function inner(value, options = undefined) {
91+
>test4 : Symbol(test4, Decl(index.js, 34, 5))
92+
>inner : Symbol(inner, Decl(index.js, 34, 13))
93+
>value : Symbol(value, Decl(index.js, 34, 29))
94+
>options : Symbol(options, Decl(index.js, 34, 35))
95+
>undefined : Symbol(undefined)
96+
97+
const args = [].slice.call(arguments);
98+
>args : Symbol(args, Decl(index.js, 35, 7))
99+
>[].slice.call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
100+
>[].slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --))
101+
>slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --))
102+
>call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
103+
>arguments : Symbol(arguments)
104+
}
105+
106+
/** @type {(a: number, b: boolean | undefined, ...rest: [string, ...number[]]) => void} */
107+
const test5 = function inner(value, options = undefined) {
108+
>test5 : Symbol(test5, Decl(index.js, 39, 5))
109+
>inner : Symbol(inner, Decl(index.js, 39, 13))
110+
>value : Symbol(value, Decl(index.js, 39, 29))
111+
>options : Symbol(options, Decl(index.js, 39, 35))
112+
>undefined : Symbol(undefined)
113+
114+
const args = [].slice.call(arguments);
115+
>args : Symbol(args, Decl(index.js, 40, 7))
116+
>[].slice.call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
117+
>[].slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --))
118+
>slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --))
119+
>call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
120+
>arguments : Symbol(arguments)
121+
}
122+
123+
export {}

0 commit comments

Comments
 (0)