From 0b00da168cc6024735a95cda44e5d2434df06c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 21 Feb 2024 10:31:10 +0100 Subject: [PATCH] Fixed crash related to JS synthethic rest param and preceeding parameters with initializers --- src/compiler/checker.ts | 20 ++- src/compiler/types.ts | 1 + ...nceAndParameterWithInitializer1.errors.txt | 59 +++++++ ...erenceAndParameterWithInitializer1.symbols | 123 ++++++++++++++ ...eferenceAndParameterWithInitializer1.types | 152 ++++++++++++++++++ ...fContextSensitiveParameterNoCrash.baseline | 10 +- ...tsReferenceAndParameterWithInitializer1.ts | 51 ++++++ 7 files changed, 408 insertions(+), 8 deletions(-) create mode 100644 tests/baselines/reference/argumentsReferenceAndParameterWithInitializer1.errors.txt create mode 100644 tests/baselines/reference/argumentsReferenceAndParameterWithInitializer1.symbols create mode 100644 tests/baselines/reference/argumentsReferenceAndParameterWithInitializer1.types create mode 100644 tests/cases/compiler/argumentsReferenceAndParameterWithInitializer1.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f9ab74b053256..51d87b12b9d02 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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. @@ -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; @@ -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); @@ -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. @@ -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: @@ -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: diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 28061c88e94bb..15032b364a729 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5897,6 +5897,7 @@ export interface SymbolLinks { tupleLabelDeclaration?: NamedTupleMember | ParameterDeclaration; // Declaration associated with the tuple's label accessibleChainCache?: Map; filteredIndexSymbolCache?: Map //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 */ diff --git a/tests/baselines/reference/argumentsReferenceAndParameterWithInitializer1.errors.txt b/tests/baselines/reference/argumentsReferenceAndParameterWithInitializer1.errors.txt new file mode 100644 index 0000000000000..8d3f13fe4cbd4 --- /dev/null +++ b/tests/baselines/reference/argumentsReferenceAndParameterWithInitializer1.errors.txt @@ -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 {} \ No newline at end of file diff --git a/tests/baselines/reference/argumentsReferenceAndParameterWithInitializer1.symbols b/tests/baselines/reference/argumentsReferenceAndParameterWithInitializer1.symbols new file mode 100644 index 0000000000000..2fa1357821f99 --- /dev/null +++ b/tests/baselines/reference/argumentsReferenceAndParameterWithInitializer1.symbols @@ -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 {} diff --git a/tests/baselines/reference/argumentsReferenceAndParameterWithInitializer1.types b/tests/baselines/reference/argumentsReferenceAndParameterWithInitializer1.types new file mode 100644 index 0000000000000..12f4b187c9362 --- /dev/null +++ b/tests/baselines/reference/argumentsReferenceAndParameterWithInitializer1.types @@ -0,0 +1,152 @@ +//// [tests/cases/compiler/argumentsReferenceAndParameterWithInitializer1.ts] //// + +=== index.js === +'use strict'; +>'use strict' : "use strict" + +// https://github.com/microsoft/TypeScript/issues/57435 + +/** @type {globalThis['structuredClone']} */ +const structuredClone = +>structuredClone : (value: T, options?: StructuredSerializeOptions | undefined) => T + + globalThis.structuredClone ?? +>globalThis.structuredClone ?? function structuredClone (value, options = undefined) { if (arguments.length === 0) { throw new TypeError('missing argument') } return value; } : (value: T, options?: StructuredSerializeOptions | undefined) => T +>globalThis.structuredClone : (value: T, options?: StructuredSerializeOptions | undefined) => T +>globalThis : typeof globalThis +>structuredClone : (value: T, options?: StructuredSerializeOptions | undefined) => T + + function structuredClone (value, options = undefined) { +>function structuredClone (value, options = undefined) { if (arguments.length === 0) { throw new TypeError('missing argument') } return value; } : (value: T, options?: StructuredSerializeOptions | undefined) => T +>structuredClone : (value: T, options?: StructuredSerializeOptions | undefined) => T +>value : T +>options : StructuredSerializeOptions | undefined +>undefined : undefined + + if (arguments.length === 0) { +>arguments.length === 0 : boolean +>arguments.length : number +>arguments : IArguments +>length : number +>0 : 0 + + throw new TypeError('missing argument') +>new TypeError('missing argument') : TypeError +>TypeError : TypeErrorConstructor +>'missing argument' : "missing argument" + } + return value; +>value : T + } + +/** @type {(a: number, b: boolean | undefined, ...rest: string[]) => void} */ +const test1 = function(value, options = undefined) { +>test1 : (a: number, b: boolean | undefined, ...rest: string[]) => void +>function(value, options = undefined) { if (arguments.length === 0) { throw new TypeError('missing argument') }} : (value: number, options?: boolean | undefined, ...args: string[]) => void +>value : number +>options : boolean | undefined +>undefined : undefined + + if (arguments.length === 0) { +>arguments.length === 0 : boolean +>arguments.length : number +>arguments : IArguments +>length : number +>0 : 0 + + throw new TypeError('missing argument') +>new TypeError('missing argument') : TypeError +>TypeError : TypeErrorConstructor +>'missing argument' : "missing argument" + } +} + +/** @type {(a: number, b: boolean | undefined, ...rest: string[]) => void} */ +const test2 = function inner(value, options = undefined) { +>test2 : (a: number, b: boolean | undefined, ...rest: string[]) => void +>function inner(value, options = undefined) { const args = [].slice.call(arguments); inner(1, true, 'hello', 'world');} : (value: number, options?: boolean | undefined, ...args: string[]) => void +>inner : (value: number, options?: boolean | undefined, ...args: string[]) => void +>value : number +>options : boolean | undefined +>undefined : undefined + + const args = [].slice.call(arguments); +>args : any[] +>[].slice.call(arguments) : never[] +>[].slice.call : (this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R +>[].slice : (start?: number | undefined, end?: number | undefined) => never[] +>[] : never[] +>slice : (start?: number | undefined, end?: number | undefined) => never[] +>call : (this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R +>arguments : IArguments + + inner(1, true, 'hello', 'world'); +>inner(1, true, 'hello', 'world') : void +>inner : (value: number, options?: boolean | undefined, ...args: string[]) => void +>1 : 1 +>true : true +>'hello' : "hello" +>'world' : "world" +} + +/** @type {(a: number, b: boolean | undefined) => void} */ +const test3 = function inner(value, options = undefined) { +>test3 : (a: number, b: boolean | undefined) => void +>function inner(value, options = undefined) { const args = [].slice.call(arguments);} : (value: number, options?: boolean | undefined) => void +>inner : (value: number, options?: boolean | undefined) => void +>value : number +>options : boolean | undefined +>undefined : undefined + + const args = [].slice.call(arguments); +>args : any[] +>[].slice.call(arguments) : never[] +>[].slice.call : (this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R +>[].slice : (start?: number | undefined, end?: number | undefined) => never[] +>[] : never[] +>slice : (start?: number | undefined, end?: number | undefined) => never[] +>call : (this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R +>arguments : IArguments +} + +/** @type {(a: number, b: boolean | undefined, ...rest: [string?, ...number[]]) => void} */ +const test4 = function inner(value, options = undefined) { +>test4 : (a: number, b: boolean | undefined, rest_0?: string | undefined, ...rest_1: number[]) => void +>function inner(value, options = undefined) { const args = [].slice.call(arguments);} : (value: number, options?: boolean | undefined, args_0?: string | undefined, ...args_1: number[]) => void +>inner : (value: number, options?: boolean | undefined, args_0?: string | undefined, ...args_1: number[]) => void +>value : number +>options : boolean | undefined +>undefined : undefined + + const args = [].slice.call(arguments); +>args : any[] +>[].slice.call(arguments) : never[] +>[].slice.call : (this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R +>[].slice : (start?: number | undefined, end?: number | undefined) => never[] +>[] : never[] +>slice : (start?: number | undefined, end?: number | undefined) => never[] +>call : (this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R +>arguments : IArguments +} + +/** @type {(a: number, b: boolean | undefined, ...rest: [string, ...number[]]) => void} */ +const test5 = function inner(value, options = undefined) { +>test5 : (a: number, b: boolean | undefined, rest_0: string, ...rest_1: number[]) => void +>function inner(value, options = undefined) { const args = [].slice.call(arguments);} : (value: number, options: boolean | undefined, args_0: string, ...args_1: number[]) => void +>inner : (value: number, options: boolean | undefined, args_0: string, ...args_1: number[]) => void +>value : number +>options : boolean | undefined +>undefined : undefined + + const args = [].slice.call(arguments); +>args : any[] +>[].slice.call(arguments) : never[] +>[].slice.call : (this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R +>[].slice : (start?: number | undefined, end?: number | undefined) => never[] +>[] : never[] +>slice : (start?: number | undefined, end?: number | undefined) => never[] +>call : (this: (this: T, ...args: A) => R, thisArg: T, ...args: A) => R +>arguments : IArguments +} + +export {} diff --git a/tests/baselines/reference/completionDetailsOfContextSensitiveParameterNoCrash.baseline b/tests/baselines/reference/completionDetailsOfContextSensitiveParameterNoCrash.baseline index cecf5e4f9d7e4..0639061cb4935 100644 --- a/tests/baselines/reference/completionDetailsOfContextSensitiveParameterNoCrash.baseline +++ b/tests/baselines/reference/completionDetailsOfContextSensitiveParameterNoCrash.baseline @@ -85,7 +85,7 @@ // return curry(getStylingByKeys, 2)(mergedStyling, ...args); // ^^^^ // | ---------------------------------------------------------------------- -// | (parameter) args: any +// | (parameter) args: [] // | ---------------------------------------------------------------------- // }, // 3 @@ -135,8 +135,12 @@ "kind": "space" }, { - "text": "any", - "kind": "keyword" + "text": "[", + "kind": "punctuation" + }, + { + "text": "]", + "kind": "punctuation" } ], "documentation": [] diff --git a/tests/cases/compiler/argumentsReferenceAndParameterWithInitializer1.ts b/tests/cases/compiler/argumentsReferenceAndParameterWithInitializer1.ts new file mode 100644 index 0000000000000..4c3b32235ea5c --- /dev/null +++ b/tests/cases/compiler/argumentsReferenceAndParameterWithInitializer1.ts @@ -0,0 +1,51 @@ +// @strict: true +// @checkJs: true +// @lib: dom, esnext +// @noEmit: true + +// @filename: index.js + +'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); + + inner(1, true, 'hello', 'world'); +} + +/** @type {(a: number, b: boolean | undefined) => void} */ +const test3 = function inner(value, options = undefined) { + const args = [].slice.call(arguments); +} + +/** @type {(a: number, b: boolean | undefined, ...rest: [string?, ...number[]]) => void} */ +const test4 = function inner(value, options = undefined) { + const args = [].slice.call(arguments); +} + +/** @type {(a: number, b: boolean | undefined, ...rest: [string, ...number[]]) => void} */ +const test5 = function inner(value, options = undefined) { + const args = [].slice.call(arguments); +} + +export {} \ No newline at end of file