Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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<number>'.
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> = T | [label: string, ...children: Tree<T>[]];

export const ok1: Labeled = ["i", "a", ["i", "b"]];
export const ok2: DocNode = ["li", 0, "a", ["i"]];
export const ok3: Tree<number> = ["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<number> = ["root", "1"];
~~~
!!! error TS2322: Type 'string' is not assignable to type 'Tree<number>'.

==== 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'.

Original file line number Diff line number Diff line change
@@ -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> = T | [label: string, ...children: Tree<T>[]];

export const ok1: Labeled = ["i", "a", ["i", "b"]];
export const ok2: DocNode = ["li", 0, "a", ["i"]];
export const ok3: Tree<number> = ["root", 1, ["leaf", 2]];

export const bad1: Labeled = ["i", 1];
export const bad2: DocNode = ["li", "0"];
export const bad3: Tree<number> = ["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> = T | [label: string, ...children: Tree<T>[]];
export declare const ok1: Labeled;
export declare const ok2: DocNode;
export declare const ok3: Tree<number>;
export declare const bad1: Labeled;
export declare const bad2: DocNode;
export declare const bad3: Tree<number>;
//// [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;
Original file line number Diff line number Diff line change
@@ -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> = T | [label: string, ...children: Tree<T>[]];
>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<number> = ["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<number> = ["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))

Original file line number Diff line number Diff line change
@@ -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> = T | [label: string, ...children: Tree<T>[]];
>Tree : Tree<T>

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<number> = ["root", 1, ["leaf", 2]];
>ok3 : Tree<number>
>["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<number> = ["root", "1"];
>bad3 : Tree<number>
>["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

Original file line number Diff line number Diff line change
@@ -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> = T | [label: string, ...children: Tree<T>[]];

export const ok1: Labeled = ["i", "a", ["i", "b"]];
export const ok2: DocNode = ["li", 0, "a", ["i"]];
export const ok3: Tree<number> = ["root", 1, ["leaf", 2]];

export const bad1: Labeled = ["i", 1];
export const bad2: DocNode = ["li", "0"];
export const bad3: Tree<number> = ["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];