Skip to content

Commit

Permalink
Fixed crash related to JS synthethic rest param and preceeding parame…
Browse files Browse the repository at this point in the history
…ters with initializers
  • Loading branch information
Andarist committed Feb 21, 2024
1 parent 60f93aa commit 0b00da1
Show file tree
Hide file tree
Showing 7 changed files with 408 additions and 8 deletions.
20 changes: 15 additions & 5 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11666,7 +11666,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!links.type) {
const type = getTypeOfVariableOrParameterOrPropertyWorker(symbol, checkMode);
// For a contextually typed parameter it is possible that a type has already
// been assigned (in assignTypeToParameterAndFixTypeParameters), and we want
// been assigned (in contextuallyCheckFunctionExpressionOrObjectLiteralMethod), and we want
// to preserve this type. In fact, we need to _prefer_ that type, but it won't
// be assigned until contextual typing is complete, so we need to defer in
// cases where contextual typing may take place.
Expand Down Expand Up @@ -12057,6 +12057,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!links.type) {
Debug.assertIsDefined(links.deferralParent);
Debug.assertIsDefined(links.deferralConstituents);
if (links.deferralParent === neverType) {
const functionDecl = links.jsSyntheticRestParameterFunctionDeclaration!;
if (isFunctionExpressionOrArrowFunction(functionDecl)) {
const contextualSignature = getContextualSignature(functionDecl);
if (contextualSignature) {
return getRestTypeAtPosition(contextualSignature, functionDecl.parameters.length);
}
}
}
links.type = links.deferralParent.flags & TypeFlags.Union ? getUnionType(links.deferralConstituents) : getIntersectionType(links.deferralConstituents);
}
return links.type;
Expand Down Expand Up @@ -15296,7 +15305,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
* 2. It has at least one parameter, and the last parameter has a matching `@param` with a type that starts with `...`
*/
function maybeAddJsSyntheticRestParameter(declaration: SignatureDeclaration | JSDocSignature, parameters: Symbol[]): boolean {
if (isJSDocSignature(declaration) || !containsArgumentsReference(declaration)) {
if (!isFunctionLikeDeclaration(declaration) || !containsArgumentsReference(declaration)) {
return false;
}
const lastParam = lastOrUndefined(declaration.parameters);
Expand All @@ -15316,6 +15325,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
syntheticArgsSymbol.links.deferralParent = neverType;
syntheticArgsSymbol.links.deferralConstituents = [anyArrayType];
syntheticArgsSymbol.links.deferralWriteConstituents = [anyArrayType];
syntheticArgsSymbol.links.jsSyntheticRestParameterFunctionDeclaration = declaration;
}
if (lastParamVariadicType) {
// Replace the last parameter with a rest parameter.
Expand Down Expand Up @@ -15351,12 +15361,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
links.containsArgumentsReference = true;
}
else {
links.containsArgumentsReference = traverse((declaration as FunctionLikeDeclaration).body!);
links.containsArgumentsReference = traverse((declaration as FunctionLikeDeclaration).body);
}
}
return links.containsArgumentsReference;

function traverse(node: Node): boolean {
function traverse(node: Node | undefined): boolean {
if (!node) return false;
switch (node.kind) {
case SyntaxKind.Identifier:
Expand All @@ -15367,7 +15377,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
return (node as NamedDeclaration).name!.kind === SyntaxKind.ComputedPropertyName
&& traverse((node as NamedDeclaration).name!);
&& traverse((node as NamedDeclaration).name);

case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5897,6 +5897,7 @@ export interface SymbolLinks {
tupleLabelDeclaration?: NamedTupleMember | ParameterDeclaration; // Declaration associated with the tuple's label
accessibleChainCache?: Map<string, Symbol[] | undefined>;
filteredIndexSymbolCache?: Map<string, Symbol> //Symbol with applicable declarations
jsSyntheticRestParameterFunctionDeclaration?: FunctionLikeDeclaration; // function declaration to which the js synthetic rest parameter belongs to, its contextual signature might be looked up
}

/** @internal */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
index.js(24,9): error TS7005: Variable 'args' implicitly has an 'any[]' type.
index.js(31,9): error TS7005: Variable 'args' implicitly has an 'any[]' type.
index.js(36,9): error TS7005: Variable 'args' implicitly has an 'any[]' type.
index.js(41,9): error TS7005: Variable 'args' implicitly has an 'any[]' type.


==== index.js (4 errors) ====
'use strict';

// https://github.com/microsoft/TypeScript/issues/57435

/** @type {globalThis['structuredClone']} */
const structuredClone =
globalThis.structuredClone ??
function structuredClone (value, options = undefined) {
if (arguments.length === 0) {
throw new TypeError('missing argument')
}
return value;
}

/** @type {(a: number, b: boolean | undefined, ...rest: string[]) => void} */
const test1 = function(value, options = undefined) {
if (arguments.length === 0) {
throw new TypeError('missing argument')
}
}

/** @type {(a: number, b: boolean | undefined, ...rest: string[]) => void} */
const test2 = function inner(value, options = undefined) {
const args = [].slice.call(arguments);
~~~~
!!! error TS7005: Variable 'args' implicitly has an 'any[]' type.

inner(1, true, 'hello', 'world');
}

/** @type {(a: number, b: boolean | undefined) => void} */
const test3 = function inner(value, options = undefined) {
const args = [].slice.call(arguments);
~~~~
!!! error TS7005: Variable 'args' implicitly has an 'any[]' type.
}

/** @type {(a: number, b: boolean | undefined, ...rest: [string?, ...number[]]) => void} */
const test4 = function inner(value, options = undefined) {
const args = [].slice.call(arguments);
~~~~
!!! error TS7005: Variable 'args' implicitly has an 'any[]' type.
}

/** @type {(a: number, b: boolean | undefined, ...rest: [string, ...number[]]) => void} */
const test5 = function inner(value, options = undefined) {
const args = [].slice.call(arguments);
~~~~
!!! error TS7005: Variable 'args' implicitly has an 'any[]' type.
}

export {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//// [tests/cases/compiler/argumentsReferenceAndParameterWithInitializer1.ts] ////

=== index.js ===
'use strict';

// https://github.com/microsoft/TypeScript/issues/57435

/** @type {globalThis['structuredClone']} */
const structuredClone =
>structuredClone : Symbol(structuredClone, Decl(index.js, 5, 5))

globalThis.structuredClone ??
>globalThis.structuredClone : Symbol(structuredClone, Decl(lib.dom.d.ts, --, --))
>globalThis : Symbol(globalThis)
>structuredClone : Symbol(structuredClone, Decl(lib.dom.d.ts, --, --))

function structuredClone (value, options = undefined) {
>structuredClone : Symbol(structuredClone, Decl(index.js, 6, 31))
>value : Symbol(value, Decl(index.js, 7, 28))
>options : Symbol(options, Decl(index.js, 7, 34))
>undefined : Symbol(undefined)

if (arguments.length === 0) {
>arguments.length : Symbol(IArguments.length, Decl(lib.es5.d.ts, --, --))
>arguments : Symbol(arguments)
>length : Symbol(IArguments.length, Decl(lib.es5.d.ts, --, --))

throw new TypeError('missing argument')
>TypeError : Symbol(TypeError, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
}
return value;
>value : Symbol(value, Decl(index.js, 7, 28))
}

/** @type {(a: number, b: boolean | undefined, ...rest: string[]) => void} */
const test1 = function(value, options = undefined) {
>test1 : Symbol(test1, Decl(index.js, 15, 5))
>value : Symbol(value, Decl(index.js, 15, 23))
>options : Symbol(options, Decl(index.js, 15, 29))
>undefined : Symbol(undefined)

if (arguments.length === 0) {
>arguments.length : Symbol(IArguments.length, Decl(lib.es5.d.ts, --, --))
>arguments : Symbol(arguments)
>length : Symbol(IArguments.length, Decl(lib.es5.d.ts, --, --))

throw new TypeError('missing argument')
>TypeError : Symbol(TypeError, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
}
}

/** @type {(a: number, b: boolean | undefined, ...rest: string[]) => void} */
const test2 = function inner(value, options = undefined) {
>test2 : Symbol(test2, Decl(index.js, 22, 5))
>inner : Symbol(inner, Decl(index.js, 22, 13))
>value : Symbol(value, Decl(index.js, 22, 29))
>options : Symbol(options, Decl(index.js, 22, 35))
>undefined : Symbol(undefined)

const args = [].slice.call(arguments);
>args : Symbol(args, Decl(index.js, 23, 7))
>[].slice.call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
>[].slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --))
>slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --))
>call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
>arguments : Symbol(arguments)

inner(1, true, 'hello', 'world');
>inner : Symbol(inner, Decl(index.js, 22, 13))
}

/** @type {(a: number, b: boolean | undefined) => void} */
const test3 = function inner(value, options = undefined) {
>test3 : Symbol(test3, Decl(index.js, 29, 5))
>inner : Symbol(inner, Decl(index.js, 29, 13))
>value : Symbol(value, Decl(index.js, 29, 29))
>options : Symbol(options, Decl(index.js, 29, 35))
>undefined : Symbol(undefined)

const args = [].slice.call(arguments);
>args : Symbol(args, Decl(index.js, 30, 7))
>[].slice.call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
>[].slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --))
>slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --))
>call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
>arguments : Symbol(arguments)
}

/** @type {(a: number, b: boolean | undefined, ...rest: [string?, ...number[]]) => void} */
const test4 = function inner(value, options = undefined) {
>test4 : Symbol(test4, Decl(index.js, 34, 5))
>inner : Symbol(inner, Decl(index.js, 34, 13))
>value : Symbol(value, Decl(index.js, 34, 29))
>options : Symbol(options, Decl(index.js, 34, 35))
>undefined : Symbol(undefined)

const args = [].slice.call(arguments);
>args : Symbol(args, Decl(index.js, 35, 7))
>[].slice.call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
>[].slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --))
>slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --))
>call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
>arguments : Symbol(arguments)
}

/** @type {(a: number, b: boolean | undefined, ...rest: [string, ...number[]]) => void} */
const test5 = function inner(value, options = undefined) {
>test5 : Symbol(test5, Decl(index.js, 39, 5))
>inner : Symbol(inner, Decl(index.js, 39, 13))
>value : Symbol(value, Decl(index.js, 39, 29))
>options : Symbol(options, Decl(index.js, 39, 35))
>undefined : Symbol(undefined)

const args = [].slice.call(arguments);
>args : Symbol(args, Decl(index.js, 40, 7))
>[].slice.call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
>[].slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --))
>slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --))
>call : Symbol(CallableFunction.call, Decl(lib.es5.d.ts, --, --))
>arguments : Symbol(arguments)
}

export {}
Loading

0 comments on commit 0b00da1

Please sign in to comment.