Skip to content
Merged
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
44 changes: 36 additions & 8 deletions tsc/internal/checker/nodebuilderimpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ type SerializedTypeEntry struct {
}

type CompositeTypeCacheIdentity struct {
typeId TypeId
flags nodebuilder.Flags
internalFlags nodebuilder.InternalFlags
typeId TypeId
flags nodebuilder.Flags
internalFlags nodebuilder.InternalFlags
inferTypeParameters CacheHashKey
}

type NodeBuilderLinks struct {
Expand Down Expand Up @@ -3084,7 +3085,7 @@ func (b *NodeBuilderImpl) getParentSymbolOfTypeParameter(typeParameter *TypePara
return b.ch.getSymbolOfNode(host)
}

func (b *NodeBuilderImpl) typeReferenceToTypeNode(t *Type) *ast.TypeNode {
func (b *NodeBuilderImpl) arrayOrTupleTypeToNode(t *Type) *ast.TypeNode {
var typeArguments []*Type = b.ch.getTypeArguments(t)
if t.Target() == b.ch.globalArrayType || t.Target() == b.ch.globalReadonlyArrayType {
if b.ctx.flags&nodebuilder.FlagsWriteArrayAsGenericType != 0 {
Expand All @@ -3101,7 +3102,8 @@ func (b *NodeBuilderImpl) typeReferenceToTypeNode(t *Type) *ast.TypeNode {
} else {
return b.f.NewTypeOperatorNode(ast.KindReadonlyKeyword, arrayType)
}
} else if t.Target().objectFlags&ObjectFlagsTuple != 0 {
} else {
debug.Assert(t.Target().objectFlags&ObjectFlagsTuple != 0)
typeArguments = core.SameMapIndex(typeArguments, func(arg *Type, i int) *Type {
isOptional := false
if i < len(t.Target().AsTupleType().elementInfos) {
Expand Down Expand Up @@ -3154,7 +3156,12 @@ func (b *NodeBuilderImpl) typeReferenceToTypeNode(t *Type) *ast.TypeNode {
b.ctx.encounteredError = true
return nil
// TODO: GH#18217
} else if b.ctx.flags&nodebuilder.FlagsWriteClassExpressionAsTypeLiteral != 0 && t.symbol.ValueDeclaration != nil && ast.IsClassLike(t.symbol.ValueDeclaration) && !b.ch.IsValueSymbolAccessible(t.symbol, b.ctx.enclosingDeclaration) {
}
}

func (b *NodeBuilderImpl) typeReferenceToTypeNode(t *Type) *ast.TypeNode {
var typeArguments []*Type = b.ch.getTypeArguments(t)
if b.ctx.flags&nodebuilder.FlagsWriteClassExpressionAsTypeLiteral != 0 && t.symbol.ValueDeclaration != nil && ast.IsClassLike(t.symbol.ValueDeclaration) && !b.ch.IsValueSymbolAccessible(t.symbol, b.ctx.enclosingDeclaration) {
return b.createAnonymousTypeNode(t)
} else {
outerTypeParameters := t.Target().AsInterfaceType().OuterTypeParameters()
Expand Down Expand Up @@ -3232,9 +3239,21 @@ func (b *NodeBuilderImpl) visitAndTransformType(t *Type, transform func(b *NodeB
}

typeId := t.id
isArrayOrTuple := b.ch.isArrayOrTupleType(t)
if isArrayOrTuple {
// Deferred and regular references share a cycle identity.
typeId = b.ch.createTypeReference(t.Target(), b.ch.getTypeArguments(t)).id
}
if b.ctx.visitedTypes.Has(typeId) {
return b.createCyclicStructurePlaceholder()
}

isConstructorObject := t.objectFlags&ObjectFlagsAnonymous != 0 && t.symbol != nil && t.symbol.Flags&ast.SymbolFlagsClass != 0
var id *CompositeSymbolIdentity
switch {
case isArrayOrTuple:
// Do not bound finite container nesting by the shared Array symbol or tuple origin.
id = nil
case t.objectFlags&ObjectFlagsReference != 0 && t.AsTypeReference().node != nil:
id = &CompositeSymbolIdentity{false, 0, ast.GetNodeId(t.AsTypeReference().node)}
case t.flags&TypeFlagsConditional != 0:
Expand All @@ -3247,7 +3266,14 @@ func (b *NodeBuilderImpl) visitAndTransformType(t *Type, transform func(b *NodeB
// Since instantiations of the same anonymous type have the same symbol, tracking symbols instead
// of types allows us to catch circular references to instantiations of the same anonymous type

key := CompositeTypeCacheIdentity{typeId, b.ctx.flags, b.ctx.internalFlags}
key := CompositeTypeCacheIdentity{
typeId: typeId,
flags: b.ctx.flags,
internalFlags: b.ctx.internalFlags,
}
if len(b.ctx.inferTypeParameters) != 0 {
key.inferTypeParameters = getTypeListKey(b.ctx.inferTypeParameters)
}
// Don't rely on type cache if we're expanding a type, because we need to compute `canIncreaseExpansionDepth`.
canUseCache := b.ctx.maxExpansionDepth < 0
if canUseCache && b.ctx.enclosingDeclaration != nil && b.links.Has(b.ctx.enclosingDeclaration) {
Expand Down Expand Up @@ -3518,7 +3544,9 @@ func (b *NodeBuilderImpl) typeToTypeNode(t *Type) *ast.TypeNode {
b.ctx.depth--
return result
}
if t.AsTypeReference().node != nil {
if b.ch.isArrayOrTupleType(t) {
return b.visitAndTransformType(t, (*NodeBuilderImpl).arrayOrTupleTypeToNode)
} else if t.AsTypeReference().node != nil {
return b.visitAndTransformType(t, (*NodeBuilderImpl).typeReferenceToTypeNode)
} else {
return b.typeReferenceToTypeNode(t)
Expand Down
52 changes: 52 additions & 0 deletions tsc/internal/execute/tsctests/tsc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2432,6 +2432,58 @@ func TestTscIncremental(t *testing.T) {
noChange,
},
},
{
subScenario: "recursive tagged tuple after incremental edits",
files: FileMap{
"/home/src/workspaces/project/tsconfig.json": `{"compilerOptions": {"strict": true, "incremental": true, "noEmit": true, "module": "esnext", "moduleResolution": "bundler"}}`,
tscLibPath + "/lib.es2026.full.d.ts": libWithReadonlyArray,
"/home/src/workspaces/project/doc.ts": stringtestutil.Dedent(`
type Doc =
| string
| { [k: string]: Doc }
| readonly ["array", Doc]
| readonly ["array", Doc, { length: number }]
| readonly ["array", Doc, { min?: number; max?: number }]
| readonly ["union", Doc, ...Doc[]];
export declare const doc: Doc;
`),
"/home/src/workspaces/project/consumer.ts": stringtestutil.Dedent(`
import { doc } from "./doc";
export const value = doc;
`),
},
edits: []*tscEdit{
noChange,
{
caption: "add a comment to the recursive type",
edit: func(sys *TestSys) {
sys.appendFile("/home/src/workspaces/project/doc.ts", "\n// comment-only edit\n")
},
},
noChange,
{
caption: "add a union constituent",
edit: func(sys *TestSys) {
sys.replaceFileText("/home/src/workspaces/project/doc.ts", "| string", "| number\n | string")
},
},
noChange,
{
caption: "verify the consumer type was not weakened",
edit: func(sys *TestSys) {
sys.appendFile("/home/src/workspaces/project/consumer.ts", "\nexport const invalid: number = value;\n")
},
},
noChange,
{
caption: "delete build info and check the edited source afresh",
edit: func(sys *TestSys) {
sys.removeNoError("/home/src/workspaces/project/tsconfig.tsbuildinfo")
},
},
noChange,
},
},
{
subScenario: "json module diagnostics are cleared after fixing the json file",
files: FileMap{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -536,5 +536,5 @@ type DeeplyMapped/*6*/ = {[K in keyof Foo]: {[K2 in keyof Foo]: [K, K2, Foo[K],
f.GoToMarker(t, "5")
f.VerifyQuickInfoIs(t, "type LongTuple = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17.18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70]", "")
f.GoToMarker(t, "6")
f.VerifyQuickInfoIs(t, "type DeeplyMapped = {\n _0: {\n _0: [\"_0\", \"_0\", 0, 0];\n _1: [\"_0\", \"_1\", 0, 1];\n _2: [\"_0\", \"_2\", 0, 2];\n _3: [\"_0\", \"_3\", 0, 3];\n _4: [\"_0\", \"_4\", 0, 4];\n _5: [\"_0\", \"_5\", 0, 5];\n _6: [\"_0\", \"_6\", 0, 6];\n _7: [\"_0\", \"_7\", 0, 7];\n _8: [\"_0\", \"_8\", 0, 8];\n _9: [\"_0\", \"_9\", 0, 9];\n _10: [\"_0\", \"_10\", 0, 10];\n _11: [\"_0\", \"_11\", 0, 11];\n _12: [\"_0\", \"_12\", 0, 12];\n _13: [\"_0\", \"_13\", 0, 13];\n _14: [\"_0\", \"_14\", 0, 14];\n _15: [\"_0\", \"_15\", 0, 15];\n _16: [\"_0\", \"_16\", 0, 16];\n _17: [\"_0\", \"_17\", 0, 17];\n _18: [\"_0\", \"_18\", 0, 18];\n _19: [\"_0\", \"_19\", 0, 19];\n _20: [\"_0\", \"_20\", 0, 20];\n _21: [\"_0\", \"_21\", 0, 21];\n ... 477 more ...;\n _499: [...];\n };\n ... 498 more ...;\n _499: ...;\n}", "")
f.VerifyQuickInfoIs(t, "type DeeplyMapped = {\n _0: {\n _0: [\"_0\", \"_0\", 0, 0];\n _1: [\"_0\", \"_1\", 0, 1];\n _2: [\"_0\", \"_2\", 0, 2];\n _3: [\"_0\", \"_3\", 0, 3];\n _4: [\"_0\", \"_4\", 0, 4];\n _5: [\"_0\", \"_5\", 0, 5];\n _6: [\"_0\", \"_6\", 0, 6];\n _7: [\"_0\", \"_7\", 0, 7];\n _8: [\"_0\", \"_8\", 0, 8];\n _9: [\"_0\", \"_9\", 0, 9];\n _10: [\"_0\", \"_10\", 0, 10];\n _11: [\"_0\", \"_11\", 0, 11];\n _12: [\"_0\", \"_12\", 0, 12];\n _13: [\"_0\", \"_13\", 0, 13];\n _14: [\"_0\", \"_14\", 0, 14];\n _15: [\"_0\", \"_15\", 0, 15];\n _16: [\"_0\", \"_16\", 0, 16];\n _17: [\"_0\", \"_17\", 0, 17];\n _18: [\"_0\", \"_18\", 0, 18];\n _19: [\"_0\", \"_19\", 0, 19];\n _20: [\"_0\", \"_20\", 0, 20];\n _21: [\"_0\", \"_21\", 0, 21];\n ... 477 more ...;\n _499: ...;\n };\n ... 498 more ...;\n _499: ...;\n}", "")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
local.ts(1,17): error TS5088: The inferred type of 'array' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary.
local.ts(5,17): error TS5088: The inferred type of 'tuple' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary.
local.ts(9,17): error TS5088: The inferred type of 'readonlyTuple' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary.
local.ts(13,17): error TS5088: The inferred type of 'union' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary.


==== local.ts (4 errors) ====
export function array() {
~~~~~
!!! error TS5088: The inferred type of 'array' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary.
type Recursive = Recursive[];
return null as unknown as Recursive;
}
export function tuple() {
~~~~~
!!! error TS5088: The inferred type of 'tuple' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary.
type Recursive = [Recursive];
return null as unknown as Recursive;
}
export function readonlyTuple() {
~~~~~~~~~~~~~
!!! error TS5088: The inferred type of 'readonlyTuple' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary.
type Recursive = readonly [Recursive];
return null as unknown as Recursive;
}
export function union() {
~~~~~
!!! error TS5088: The inferred type of 'union' references a type with a cyclic structure which cannot be trivially serialized. A type annotation is necessary.
type Recursive = string | Recursive[];
return null as unknown as Recursive;
}

==== nameable.ts (0 errors) ====
export type RecursiveArray = RecursiveArray[];
export type RecursiveTuple = readonly [RecursiveTuple];
declare const array: RecursiveArray;
declare const tuple: RecursiveTuple;
export const namedArray = array;
export const namedTuple = tuple;
export function finite() {
type Nested = [[[[[[[[[[[[[number]]]]]]]]]]]]];
return null as unknown as Nested;
}
export function finiteArray() {
type Nested = number[][][][][][][][][][][][][];
return null as unknown as Nested;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//// [tests/cases/compiler/recursiveContainerDeclarationSerialization.ts] ////

//// [local.ts]
export function array() {
type Recursive = Recursive[];
return null as unknown as Recursive;
}
export function tuple() {
type Recursive = [Recursive];
return null as unknown as Recursive;
}
export function readonlyTuple() {
type Recursive = readonly [Recursive];
return null as unknown as Recursive;
}
export function union() {
type Recursive = string | Recursive[];
return null as unknown as Recursive;
}

//// [nameable.ts]
export type RecursiveArray = RecursiveArray[];
export type RecursiveTuple = readonly [RecursiveTuple];
declare const array: RecursiveArray;
declare const tuple: RecursiveTuple;
export const namedArray = array;
export const namedTuple = tuple;
export function finite() {
type Nested = [[[[[[[[[[[[[number]]]]]]]]]]]]];
return null as unknown as Nested;
}
export function finiteArray() {
type Nested = number[][][][][][][][][][][][][];
return null as unknown as Nested;
}


//// [local.js]
export function array() {
return null;
}
export function tuple() {
return null;
}
export function readonlyTuple() {
return null;
}
export function union() {
return null;
}
//// [nameable.js]
export const namedArray = array;
export const namedTuple = tuple;
export function finite() {
return null;
}
export function finiteArray() {
return null;
}


//// [nameable.d.ts]
export type RecursiveArray = RecursiveArray[];
export type RecursiveTuple = readonly [RecursiveTuple];
export declare const namedArray: RecursiveArray;
export declare const namedTuple: RecursiveTuple;
export declare function finite(): [[[[[[[[[[[[[number]]]]]]]]]]]]];
export declare function finiteArray(): number[][][][][][][][][][][][][];
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//// [tests/cases/compiler/recursiveContainerDeclarationSerialization.ts] ////

=== local.ts ===
export function array() {
>array : Symbol(array, Decl(local.ts, 0, 0))

type Recursive = Recursive[];
>Recursive : Symbol(Recursive, Decl(local.ts, 0, 25))
>Recursive : Symbol(Recursive, Decl(local.ts, 0, 25))

return null as unknown as Recursive;
>Recursive : Symbol(Recursive, Decl(local.ts, 0, 25))
}
export function tuple() {
>tuple : Symbol(tuple, Decl(local.ts, 3, 1))

type Recursive = [Recursive];
>Recursive : Symbol(Recursive, Decl(local.ts, 4, 25))
>Recursive : Symbol(Recursive, Decl(local.ts, 4, 25))

return null as unknown as Recursive;
>Recursive : Symbol(Recursive, Decl(local.ts, 4, 25))
}
export function readonlyTuple() {
>readonlyTuple : Symbol(readonlyTuple, Decl(local.ts, 7, 1))

type Recursive = readonly [Recursive];
>Recursive : Symbol(Recursive, Decl(local.ts, 8, 33))
>Recursive : Symbol(Recursive, Decl(local.ts, 8, 33))

return null as unknown as Recursive;
>Recursive : Symbol(Recursive, Decl(local.ts, 8, 33))
}
export function union() {
>union : Symbol(union, Decl(local.ts, 11, 1))

type Recursive = string | Recursive[];
>Recursive : Symbol(Recursive, Decl(local.ts, 12, 25))
>Recursive : Symbol(Recursive, Decl(local.ts, 12, 25))

return null as unknown as Recursive;
>Recursive : Symbol(Recursive, Decl(local.ts, 12, 25))
}

=== nameable.ts ===
export type RecursiveArray = RecursiveArray[];
>RecursiveArray : Symbol(RecursiveArray, Decl(nameable.ts, 0, 0))
>RecursiveArray : Symbol(RecursiveArray, Decl(nameable.ts, 0, 0))

export type RecursiveTuple = readonly [RecursiveTuple];
>RecursiveTuple : Symbol(RecursiveTuple, Decl(nameable.ts, 0, 46))
>RecursiveTuple : Symbol(RecursiveTuple, Decl(nameable.ts, 0, 46))

declare const array: RecursiveArray;
>array : Symbol(array, Decl(nameable.ts, 2, 13))
>RecursiveArray : Symbol(RecursiveArray, Decl(nameable.ts, 0, 0))

declare const tuple: RecursiveTuple;
>tuple : Symbol(tuple, Decl(nameable.ts, 3, 13))
>RecursiveTuple : Symbol(RecursiveTuple, Decl(nameable.ts, 0, 46))

export const namedArray = array;
>namedArray : Symbol(namedArray, Decl(nameable.ts, 4, 12))
>array : Symbol(array, Decl(nameable.ts, 2, 13))

export const namedTuple = tuple;
>namedTuple : Symbol(namedTuple, Decl(nameable.ts, 5, 12))
>tuple : Symbol(tuple, Decl(nameable.ts, 3, 13))

export function finite() {
>finite : Symbol(finite, Decl(nameable.ts, 5, 32))

type Nested = [[[[[[[[[[[[[number]]]]]]]]]]]]];
>Nested : Symbol(Nested, Decl(nameable.ts, 6, 26))

return null as unknown as Nested;
>Nested : Symbol(Nested, Decl(nameable.ts, 6, 26))
}
export function finiteArray() {
>finiteArray : Symbol(finiteArray, Decl(nameable.ts, 9, 1))

type Nested = number[][][][][][][][][][][][][];
>Nested : Symbol(Nested, Decl(nameable.ts, 10, 31))

return null as unknown as Nested;
>Nested : Symbol(Nested, Decl(nameable.ts, 10, 31))
}

Loading
Loading