diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 80a2afdf5d6b7..3ca86b0adb6a1 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -24012,10 +24012,15 @@ func (c *Checker) mayResolveTypeAlias(node *ast.Node) bool { return true case ast.KindTypeOperator: return node.AsTypeOperatorNode().Operator != ast.KindUniqueKeyword && c.mayResolveTypeAlias(node.Type()) - case ast.KindParenthesizedType, ast.KindOptionalType, ast.KindNamedTupleMember: + case ast.KindParenthesizedType, ast.KindOptionalType: + return c.mayResolveTypeAlias(node.Type()) + case ast.KindNamedTupleMember: + if node.AsNamedTupleMember().DotDotDotToken != nil { + return c.mayResolveRestElementTypeAlias(node.Type()) + } return c.mayResolveTypeAlias(node.Type()) case ast.KindRestType: - return node.Type().Kind != ast.KindArrayType || c.mayResolveTypeAlias(node.Type().AsArrayTypeNode().ElementType) + return c.mayResolveRestElementTypeAlias(node.Type()) case ast.KindUnionType: return core.Some(node.AsUnionTypeNode().Types.Nodes, c.mayResolveTypeAlias) case ast.KindIntersectionType: @@ -24029,6 +24034,12 @@ func (c *Checker) mayResolveTypeAlias(node *ast.Node) bool { return false } +// mayResolveRestElementTypeAlias checks the type of a rest element, labeled or not. A rest element '...T[]' +// contributes T itself to the tuple's element types, so the check looks through the array type. +func (c *Checker) mayResolveRestElementTypeAlias(node *ast.Node) bool { + return node.Kind != ast.KindArrayType || c.mayResolveTypeAlias(node.AsArrayTypeNode().ElementType) +} + func (c *Checker) createNormalizedTypeReference(target *Type, typeArguments []*Type) *Type { if target.objectFlags&ObjectFlagsTuple != 0 { return c.createNormalizedTupleType(target, typeArguments) diff --git a/tsc/testdata/baselines/reference/compiler/recursiveTupleTypeLabeledRestElement.errors.txt b/tsc/testdata/baselines/reference/compiler/recursiveTupleTypeLabeledRestElement.errors.txt new file mode 100644 index 0000000000000..cd16cd8d1e49e --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/recursiveTupleTypeLabeledRestElement.errors.txt @@ -0,0 +1,64 @@ +a.ts(19,14): error TS2322: Type '["i", number]' is not assignable to type 'Labeled'. + Type '["i", number]' is not assignable to type '["i", ...rest: Labeled[]]'. + Type at position 1 in source is not compatible with type at position 1 in target. + Type 'number' is not assignable to type 'Labeled'. +a.ts(20,14): error TS2322: Type '["li", string]' is not assignable to type 'DocNode'. + Type '["li", string]' is not assignable to type '["li", index: number, ...children: DocNode[]]'. + Type at position 1 in source is not compatible with type at position 1 in target. + Type 'string' is not assignable to type 'number'. +a.ts(21,44): error TS2322: Type 'string' is not assignable to type 'Tree'. +b.js(7,14): error TS2322: Type '["i", number]' is not assignable to type 'JSDocLabeled'. + Type '["i", number]' is not assignable to type '["i", ...rest: JSDocLabeled[]]'. + Type at position 1 in source is not compatible with type at position 1 in target. + Type 'number' is not assignable to type 'JSDocLabeled'. + + +==== a.ts (3 errors) ==== + export type Unlabeled = string | ["i", ...Unlabeled[]]; + export type Labeled = string | ["i", ...rest: Labeled[]]; + + export type LabeledAfterLabeled = string | ["li", index: number, ...rest: LabeledAfterLabeled[]]; + export type LabeledBeforeLabeled = string | ["a", ...rest: LabeledBeforeLabeled[], last: number]; + export type LabeledParenthesized = string | ["a", ...rest: (LabeledParenthesized)[]]; + + export type DocNode = + | string + | ["i", ...children: DocNode[]] + | ["li", index: number, ...children: DocNode[]]; + + export type Tree = T | [label: string, ...children: Tree[]]; + + export const ok1: Labeled = ["i", "a", ["i", "b"]]; + export const ok2: DocNode = ["li", 0, "a", ["i"]]; + export const ok3: Tree = ["root", 1, ["leaf", 2]]; + + export const bad1: Labeled = ["i", 1]; + ~~~~ +!!! error TS2322: Type '["i", number]' is not assignable to type 'Labeled'. +!!! error TS2322: Type '["i", number]' is not assignable to type '["i", ...rest: Labeled[]]'. +!!! error TS2322: Type at position 1 in source is not compatible with type at position 1 in target. +!!! error TS2322: Type 'number' is not assignable to type 'Labeled'. + export const bad2: DocNode = ["li", "0"]; + ~~~~ +!!! error TS2322: Type '["li", string]' is not assignable to type 'DocNode'. +!!! error TS2322: Type '["li", string]' is not assignable to type '["li", index: number, ...children: DocNode[]]'. +!!! error TS2322: Type at position 1 in source is not compatible with type at position 1 in target. +!!! error TS2322: Type 'string' is not assignable to type 'number'. + export const bad3: Tree = ["root", "1"]; + ~~~ +!!! error TS2322: Type 'string' is not assignable to type 'Tree'. + +==== b.js (1 errors) ==== + /** @typedef {string | ["i", ...rest: JSDocLabeled[]]} JSDocLabeled */ + + /** @type {JSDocLabeled} */ + export const ok4 = ["i", "a", ["i"]]; + + /** @type {JSDocLabeled} */ + export const bad4 = ["i", 1]; + ~~~~ +!!! error TS2322: Type '["i", number]' is not assignable to type 'JSDocLabeled'. +!!! error TS2322: Type '["i", number]' is not assignable to type '["i", ...rest: JSDocLabeled[]]'. +!!! error TS2322: Type at position 1 in source is not compatible with type at position 1 in target. +!!! error TS2322: Type 'number' is not assignable to type 'JSDocLabeled'. + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/recursiveTupleTypeLabeledRestElement.js b/tsc/testdata/baselines/reference/compiler/recursiveTupleTypeLabeledRestElement.js new file mode 100644 index 0000000000000..99f0ba3c70084 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/recursiveTupleTypeLabeledRestElement.js @@ -0,0 +1,71 @@ +//// [tests/cases/compiler/recursiveTupleTypeLabeledRestElement.ts] //// + +//// [a.ts] +export type Unlabeled = string | ["i", ...Unlabeled[]]; +export type Labeled = string | ["i", ...rest: Labeled[]]; + +export type LabeledAfterLabeled = string | ["li", index: number, ...rest: LabeledAfterLabeled[]]; +export type LabeledBeforeLabeled = string | ["a", ...rest: LabeledBeforeLabeled[], last: number]; +export type LabeledParenthesized = string | ["a", ...rest: (LabeledParenthesized)[]]; + +export type DocNode = + | string + | ["i", ...children: DocNode[]] + | ["li", index: number, ...children: DocNode[]]; + +export type Tree = T | [label: string, ...children: Tree[]]; + +export const ok1: Labeled = ["i", "a", ["i", "b"]]; +export const ok2: DocNode = ["li", 0, "a", ["i"]]; +export const ok3: Tree = ["root", 1, ["leaf", 2]]; + +export const bad1: Labeled = ["i", 1]; +export const bad2: DocNode = ["li", "0"]; +export const bad3: Tree = ["root", "1"]; + +//// [b.js] +/** @typedef {string | ["i", ...rest: JSDocLabeled[]]} JSDocLabeled */ + +/** @type {JSDocLabeled} */ +export const ok4 = ["i", "a", ["i"]]; + +/** @type {JSDocLabeled} */ +export const bad4 = ["i", 1]; + + +//// [a.js] +export const ok1 = ["i", "a", ["i", "b"]]; +export const ok2 = ["li", 0, "a", ["i"]]; +export const ok3 = ["root", 1, ["leaf", 2]]; +export const bad1 = ["i", 1]; +export const bad2 = ["li", "0"]; +export const bad3 = ["root", "1"]; +//// [b.js] +/** @typedef {string | ["i", ...rest: JSDocLabeled[]]} JSDocLabeled */ +/** @type {JSDocLabeled} */ +export const ok4 = ["i", "a", ["i"]]; +/** @type {JSDocLabeled} */ +export const bad4 = ["i", 1]; + + +//// [a.d.ts] +export type Unlabeled = string | ["i", ...Unlabeled[]]; +export type Labeled = string | ["i", ...rest: Labeled[]]; +export type LabeledAfterLabeled = string | ["li", index: number, ...rest: LabeledAfterLabeled[]]; +export type LabeledBeforeLabeled = string | ["a", ...rest: LabeledBeforeLabeled[], last: number]; +export type LabeledParenthesized = string | ["a", ...rest: (LabeledParenthesized)[]]; +export type DocNode = string | ["i", ...children: DocNode[]] | ["li", index: number, ...children: DocNode[]]; +export type Tree = T | [label: string, ...children: Tree[]]; +export declare const ok1: Labeled; +export declare const ok2: DocNode; +export declare const ok3: Tree; +export declare const bad1: Labeled; +export declare const bad2: DocNode; +export declare const bad3: Tree; +//// [b.d.ts] +/** @typedef {string | ["i", ...rest: JSDocLabeled[]]} JSDocLabeled */ +export type JSDocLabeled = string | ["i", ...rest: JSDocLabeled[]]; +/** @type {JSDocLabeled} */ +export declare const ok4: JSDocLabeled; +/** @type {JSDocLabeled} */ +export declare const bad4: JSDocLabeled; diff --git a/tsc/testdata/baselines/reference/compiler/recursiveTupleTypeLabeledRestElement.symbols b/tsc/testdata/baselines/reference/compiler/recursiveTupleTypeLabeledRestElement.symbols new file mode 100644 index 0000000000000..75837f7f899f2 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/recursiveTupleTypeLabeledRestElement.symbols @@ -0,0 +1,75 @@ +//// [tests/cases/compiler/recursiveTupleTypeLabeledRestElement.ts] //// + +=== a.ts === +export type Unlabeled = string | ["i", ...Unlabeled[]]; +>Unlabeled : Symbol(Unlabeled, Decl(a.ts, 0, 0)) +>Unlabeled : Symbol(Unlabeled, Decl(a.ts, 0, 0)) + +export type Labeled = string | ["i", ...rest: Labeled[]]; +>Labeled : Symbol(Labeled, Decl(a.ts, 0, 55)) +>Labeled : Symbol(Labeled, Decl(a.ts, 0, 55)) + +export type LabeledAfterLabeled = string | ["li", index: number, ...rest: LabeledAfterLabeled[]]; +>LabeledAfterLabeled : Symbol(LabeledAfterLabeled, Decl(a.ts, 1, 57)) +>LabeledAfterLabeled : Symbol(LabeledAfterLabeled, Decl(a.ts, 1, 57)) + +export type LabeledBeforeLabeled = string | ["a", ...rest: LabeledBeforeLabeled[], last: number]; +>LabeledBeforeLabeled : Symbol(LabeledBeforeLabeled, Decl(a.ts, 3, 97)) +>LabeledBeforeLabeled : Symbol(LabeledBeforeLabeled, Decl(a.ts, 3, 97)) + +export type LabeledParenthesized = string | ["a", ...rest: (LabeledParenthesized)[]]; +>LabeledParenthesized : Symbol(LabeledParenthesized, Decl(a.ts, 4, 97)) +>LabeledParenthesized : Symbol(LabeledParenthesized, Decl(a.ts, 4, 97)) + +export type DocNode = +>DocNode : Symbol(DocNode, Decl(a.ts, 5, 85)) + + | string + | ["i", ...children: DocNode[]] +>DocNode : Symbol(DocNode, Decl(a.ts, 5, 85)) + + | ["li", index: number, ...children: DocNode[]]; +>DocNode : Symbol(DocNode, Decl(a.ts, 5, 85)) + +export type Tree = T | [label: string, ...children: Tree[]]; +>Tree : Symbol(Tree, Decl(a.ts, 10, 52)) +>T : Symbol(T, Decl(a.ts, 12, 17)) +>T : Symbol(T, Decl(a.ts, 12, 17)) +>Tree : Symbol(Tree, Decl(a.ts, 10, 52)) +>T : Symbol(T, Decl(a.ts, 12, 17)) + +export const ok1: Labeled = ["i", "a", ["i", "b"]]; +>ok1 : Symbol(ok1, Decl(a.ts, 14, 12)) +>Labeled : Symbol(Labeled, Decl(a.ts, 0, 55)) + +export const ok2: DocNode = ["li", 0, "a", ["i"]]; +>ok2 : Symbol(ok2, Decl(a.ts, 15, 12)) +>DocNode : Symbol(DocNode, Decl(a.ts, 5, 85)) + +export const ok3: Tree = ["root", 1, ["leaf", 2]]; +>ok3 : Symbol(ok3, Decl(a.ts, 16, 12)) +>Tree : Symbol(Tree, Decl(a.ts, 10, 52)) + +export const bad1: Labeled = ["i", 1]; +>bad1 : Symbol(bad1, Decl(a.ts, 18, 12)) +>Labeled : Symbol(Labeled, Decl(a.ts, 0, 55)) + +export const bad2: DocNode = ["li", "0"]; +>bad2 : Symbol(bad2, Decl(a.ts, 19, 12)) +>DocNode : Symbol(DocNode, Decl(a.ts, 5, 85)) + +export const bad3: Tree = ["root", "1"]; +>bad3 : Symbol(bad3, Decl(a.ts, 20, 12)) +>Tree : Symbol(Tree, Decl(a.ts, 10, 52)) + +=== b.js === +/** @typedef {string | ["i", ...rest: JSDocLabeled[]]} JSDocLabeled */ + +/** @type {JSDocLabeled} */ +export const ok4 = ["i", "a", ["i"]]; +>ok4 : Symbol(ok4, Decl(b.js, 3, 12)) + +/** @type {JSDocLabeled} */ +export const bad4 = ["i", 1]; +>bad4 : Symbol(bad4, Decl(b.js, 6, 12)) + diff --git a/tsc/testdata/baselines/reference/compiler/recursiveTupleTypeLabeledRestElement.types b/tsc/testdata/baselines/reference/compiler/recursiveTupleTypeLabeledRestElement.types new file mode 100644 index 0000000000000..fbf6300913723 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/recursiveTupleTypeLabeledRestElement.types @@ -0,0 +1,92 @@ +//// [tests/cases/compiler/recursiveTupleTypeLabeledRestElement.ts] //// + +=== a.ts === +export type Unlabeled = string | ["i", ...Unlabeled[]]; +>Unlabeled : Unlabeled + +export type Labeled = string | ["i", ...rest: Labeled[]]; +>Labeled : Labeled + +export type LabeledAfterLabeled = string | ["li", index: number, ...rest: LabeledAfterLabeled[]]; +>LabeledAfterLabeled : LabeledAfterLabeled + +export type LabeledBeforeLabeled = string | ["a", ...rest: LabeledBeforeLabeled[], last: number]; +>LabeledBeforeLabeled : LabeledBeforeLabeled + +export type LabeledParenthesized = string | ["a", ...rest: (LabeledParenthesized)[]]; +>LabeledParenthesized : LabeledParenthesized + +export type DocNode = +>DocNode : DocNode + + | string + | ["i", ...children: DocNode[]] + | ["li", index: number, ...children: DocNode[]]; + +export type Tree = T | [label: string, ...children: Tree[]]; +>Tree : Tree + +export const ok1: Labeled = ["i", "a", ["i", "b"]]; +>ok1 : Labeled +>["i", "a", ["i", "b"]] : ["i", string, ["i", string]] +>"i" : "i" +>"a" : "a" +>["i", "b"] : ["i", string] +>"i" : "i" +>"b" : "b" + +export const ok2: DocNode = ["li", 0, "a", ["i"]]; +>ok2 : DocNode +>["li", 0, "a", ["i"]] : ["li", number, string, ["i"]] +>"li" : "li" +>0 : 0 +>"a" : "a" +>["i"] : ["i"] +>"i" : "i" + +export const ok3: Tree = ["root", 1, ["leaf", 2]]; +>ok3 : Tree +>["root", 1, ["leaf", 2]] : [string, number, [string, number]] +>"root" : "root" +>1 : 1 +>["leaf", 2] : [string, number] +>"leaf" : "leaf" +>2 : 2 + +export const bad1: Labeled = ["i", 1]; +>bad1 : Labeled +>["i", 1] : ["i", number] +>"i" : "i" +>1 : 1 + +export const bad2: DocNode = ["li", "0"]; +>bad2 : DocNode +>["li", "0"] : ["li", string] +>"li" : "li" +>"0" : "0" + +export const bad3: Tree = ["root", "1"]; +>bad3 : Tree +>["root", "1"] : [string, string] +>"root" : "root" +>"1" : "1" + +=== b.js === +/** @typedef {string | ["i", ...rest: JSDocLabeled[]]} JSDocLabeled */ + +/** @type {JSDocLabeled} */ +export const ok4 = ["i", "a", ["i"]]; +>ok4 : JSDocLabeled +>["i", "a", ["i"]] : ["i", string, ["i"]] +>"i" : "i" +>"a" : "a" +>["i"] : ["i"] +>"i" : "i" + +/** @type {JSDocLabeled} */ +export const bad4 = ["i", 1]; +>bad4 : JSDocLabeled +>["i", 1] : ["i", number] +>"i" : "i" +>1 : 1 + diff --git a/tsc/testdata/tests/cases/compiler/recursiveTupleTypeLabeledRestElement.ts b/tsc/testdata/tests/cases/compiler/recursiveTupleTypeLabeledRestElement.ts new file mode 100644 index 0000000000000..a0bd30be74c20 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/recursiveTupleTypeLabeledRestElement.ts @@ -0,0 +1,41 @@ +// @strict: true +// @declaration: true +// @allowJs: true +// @checkJs: true +// @outDir: out + +// https://github.com/microsoft/TypeScript/issues/63584 + +// Labeling a rest element should not change whether a recursive tuple type is accepted. + +// @filename: a.ts +export type Unlabeled = string | ["i", ...Unlabeled[]]; +export type Labeled = string | ["i", ...rest: Labeled[]]; + +export type LabeledAfterLabeled = string | ["li", index: number, ...rest: LabeledAfterLabeled[]]; +export type LabeledBeforeLabeled = string | ["a", ...rest: LabeledBeforeLabeled[], last: number]; +export type LabeledParenthesized = string | ["a", ...rest: (LabeledParenthesized)[]]; + +export type DocNode = + | string + | ["i", ...children: DocNode[]] + | ["li", index: number, ...children: DocNode[]]; + +export type Tree = T | [label: string, ...children: Tree[]]; + +export const ok1: Labeled = ["i", "a", ["i", "b"]]; +export const ok2: DocNode = ["li", 0, "a", ["i"]]; +export const ok3: Tree = ["root", 1, ["leaf", 2]]; + +export const bad1: Labeled = ["i", 1]; +export const bad2: DocNode = ["li", "0"]; +export const bad3: Tree = ["root", "1"]; + +// @filename: b.js +/** @typedef {string | ["i", ...rest: JSDocLabeled[]]} JSDocLabeled */ + +/** @type {JSDocLabeled} */ +export const ok4 = ["i", "a", ["i"]]; + +/** @type {JSDocLabeled} */ +export const bad4 = ["i", 1];