From 3ab6809e38099d39a52f612b3ae460328cfeb04d Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 14 Apr 2021 16:55:45 -0700 Subject: [PATCH] Consider identical instances of the same symbol equivalent when creating union and intersection properties (#43560) * Consider identical instances of the same symbol equivalent when creating union and intersection properties * Also copy over mapper and type (if available) on cloned symbols * Editorial feedback --- src/compiler/checker.ts | 39 +++++++++++++++---- .../reference/bivariantInferences.symbols | 4 +- .../reference/bivariantInferences.types | 4 +- .../completionEntryForUnionMethod.baseline | 6 +-- .../reference/conditionalTypeClassMembers.js | 13 +++++++ .../conditionalTypeClassMembers.symbols | 32 +++++++++++++++ .../conditionalTypeClassMembers.types | 22 +++++++++++ .../reference/mixinAccessModifiers.symbols | 8 ++-- .../reference/mixinAccessModifiers.types | 8 ++-- .../mixinIntersectionIsValidbaseType.types | 6 +-- tests/baselines/reference/sliceResultCast.js | 7 ++++ .../reference/sliceResultCast.symbols | 9 +++++ .../baselines/reference/sliceResultCast.types | 12 ++++++ .../typeParameterExtendingUnion1.symbols | 4 +- .../typeParameterExtendingUnion1.types | 4 +- .../typeParameterExtendingUnion2.symbols | 8 ++-- .../typeParameterExtendingUnion2.types | 8 ++-- .../compiler/conditionalTypeClassMembers.ts | 10 +++++ tests/cases/compiler/sliceResultCast.ts | 3 ++ 19 files changed, 170 insertions(+), 37 deletions(-) create mode 100644 tests/baselines/reference/conditionalTypeClassMembers.js create mode 100644 tests/baselines/reference/conditionalTypeClassMembers.symbols create mode 100644 tests/baselines/reference/conditionalTypeClassMembers.types create mode 100644 tests/baselines/reference/sliceResultCast.js create mode 100644 tests/baselines/reference/sliceResultCast.symbols create mode 100644 tests/baselines/reference/sliceResultCast.types create mode 100644 tests/cases/compiler/conditionalTypeClassMembers.ts create mode 100644 tests/cases/compiler/sliceResultCast.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ad8c6b99915..6e0ed33153f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11641,6 +11641,7 @@ namespace ts { let optionalFlag = isUnion ? SymbolFlags.None : SymbolFlags.Optional; let syntheticFlag = CheckFlags.SyntheticMethod; let checkFlags = 0; + let mergedInstantiations = false; for (const current of containingType.types) { const type = getApparentType(current); if (!(type === errorType || type.flags & TypeFlags.Never)) { @@ -11657,13 +11658,25 @@ namespace ts { singleProp = prop; } else if (prop !== singleProp) { - if (!propSet) { - propSet = new Map(); - propSet.set(getSymbolId(singleProp), singleProp); + const isInstantiation = (getTargetSymbol(prop) || prop) === (getTargetSymbol(singleProp) || singleProp); + // If the symbols are instances of one another with identical types - consider the symbols + // equivalent and just use the first one, which thus allows us to avoid eliding private + // members when intersecting a (this-)instantiations of a class with it's raw base or another instance + if (isInstantiation && isPropertyIdenticalTo(singleProp, prop)) { + // If we merged instantiations of a generic type, we replicate the symbol parent resetting behavior we used + // to do when we recorded multiple distinct symbols so that we still get, eg, `Array.length` printed + // back and not `Array.length` when we're looking at a `.length` access on a `string[] | number[]` + mergedInstantiations = !!singleProp.parent && !!length(getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(singleProp.parent)); } - const id = getSymbolId(prop); - if (!propSet.has(id)) { - propSet.set(id, prop); + else { + if (!propSet) { + propSet = new Map(); + propSet.set(getSymbolId(singleProp), singleProp); + } + const id = getSymbolId(prop); + if (!propSet.has(id)) { + propSet.set(id, prop); + } } } checkFlags |= (isReadonlySymbol(prop) ? CheckFlags.Readonly : 0) | @@ -11697,7 +11710,19 @@ namespace ts { return undefined; } if (!propSet && !(checkFlags & CheckFlags.ReadPartial) && !indexTypes) { - return singleProp; + if (mergedInstantiations) { + // No symbol from a union/intersection should have a `.parent` set (since unions/intersections don't act as symbol parents) + // Unless that parent is "reconstituted" from the "first value declaration" on the symbol (which is likely different than its instantiated parent!) + // They also have a `.containingType` set, which affects some services endpoints behavior, like `getRootSymbol` + const clone = createSymbolWithType(singleProp, (singleProp as TransientSymbol).type); + clone.parent = singleProp.valueDeclaration?.symbol?.parent; + clone.containingType = containingType; + clone.mapper = (singleProp as TransientSymbol).mapper; + return clone; + } + else { + return singleProp; + } } const props = propSet ? arrayFrom(propSet.values()) : [singleProp]; let declarations: Declaration[] | undefined; diff --git a/tests/baselines/reference/bivariantInferences.symbols b/tests/baselines/reference/bivariantInferences.symbols index 0697c171c54..f7b25569d8c 100644 --- a/tests/baselines/reference/bivariantInferences.symbols +++ b/tests/baselines/reference/bivariantInferences.symbols @@ -24,8 +24,8 @@ declare const b: (string | number)[] | null[] | undefined[] | {}[]; let x = a.equalsShallow(b); >x : Symbol(x, Decl(bivariantInferences.ts, 9, 3)) ->a.equalsShallow : Symbol(Array.equalsShallow, Decl(bivariantInferences.ts, 2, 20), Decl(bivariantInferences.ts, 2, 20), Decl(bivariantInferences.ts, 2, 20), Decl(bivariantInferences.ts, 2, 20)) +>a.equalsShallow : Symbol(Array.equalsShallow, Decl(bivariantInferences.ts, 2, 20)) >a : Symbol(a, Decl(bivariantInferences.ts, 6, 13)) ->equalsShallow : Symbol(Array.equalsShallow, Decl(bivariantInferences.ts, 2, 20), Decl(bivariantInferences.ts, 2, 20), Decl(bivariantInferences.ts, 2, 20), Decl(bivariantInferences.ts, 2, 20)) +>equalsShallow : Symbol(Array.equalsShallow, Decl(bivariantInferences.ts, 2, 20)) >b : Symbol(b, Decl(bivariantInferences.ts, 7, 13)) diff --git a/tests/baselines/reference/bivariantInferences.types b/tests/baselines/reference/bivariantInferences.types index ac987cbddac..7ad35660c55 100644 --- a/tests/baselines/reference/bivariantInferences.types +++ b/tests/baselines/reference/bivariantInferences.types @@ -19,8 +19,8 @@ declare const b: (string | number)[] | null[] | undefined[] | {}[]; let x = a.equalsShallow(b); >x : boolean >a.equalsShallow(b) : boolean ->a.equalsShallow : ((this: readonly T[], other: readonly T[]) => boolean) | ((this: readonly T[], other: readonly T[]) => boolean) | ((this: readonly T[], other: readonly T[]) => boolean) | ((this: readonly T[], other: readonly T[]) => boolean) +>a.equalsShallow : (this: readonly T[], other: readonly T[]) => boolean >a : (string | number)[] | null[] | undefined[] | {}[] ->equalsShallow : ((this: readonly T[], other: readonly T[]) => boolean) | ((this: readonly T[], other: readonly T[]) => boolean) | ((this: readonly T[], other: readonly T[]) => boolean) | ((this: readonly T[], other: readonly T[]) => boolean) +>equalsShallow : (this: readonly T[], other: readonly T[]) => boolean >b : (string | number)[] | null[] | undefined[] | {}[] diff --git a/tests/baselines/reference/completionEntryForUnionMethod.baseline b/tests/baselines/reference/completionEntryForUnionMethod.baseline index d7cfb032e5b..8a2eb2859ed 100644 --- a/tests/baselines/reference/completionEntryForUnionMethod.baseline +++ b/tests/baselines/reference/completionEntryForUnionMethod.baseline @@ -124,7 +124,7 @@ }, { "text": "toString", - "kind": "propertyName" + "kind": "methodName" }, { "text": "(", @@ -198,7 +198,7 @@ }, { "text": "toLocaleString", - "kind": "propertyName" + "kind": "methodName" }, { "text": "(", @@ -981,7 +981,7 @@ }, { "text": "join", - "kind": "propertyName" + "kind": "methodName" }, { "text": "(", diff --git a/tests/baselines/reference/conditionalTypeClassMembers.js b/tests/baselines/reference/conditionalTypeClassMembers.js new file mode 100644 index 00000000000..f586c34aebf --- /dev/null +++ b/tests/baselines/reference/conditionalTypeClassMembers.js @@ -0,0 +1,13 @@ +//// [conditionalTypeClassMembers.ts] +declare class MyRecord { + private a(); + b(): unknown; +} + +declare class MySet { + public item(): TSet; +} + +type DS = TRec extends MyRecord ? MySet : TRec[]; + +//// [conditionalTypeClassMembers.js] diff --git a/tests/baselines/reference/conditionalTypeClassMembers.symbols b/tests/baselines/reference/conditionalTypeClassMembers.symbols new file mode 100644 index 00000000000..ea04683d059 --- /dev/null +++ b/tests/baselines/reference/conditionalTypeClassMembers.symbols @@ -0,0 +1,32 @@ +=== tests/cases/compiler/conditionalTypeClassMembers.ts === +declare class MyRecord { +>MyRecord : Symbol(MyRecord, Decl(conditionalTypeClassMembers.ts, 0, 0)) + + private a(); +>a : Symbol(MyRecord.a, Decl(conditionalTypeClassMembers.ts, 0, 24)) + + b(): unknown; +>b : Symbol(MyRecord.b, Decl(conditionalTypeClassMembers.ts, 1, 16)) +} + +declare class MySet { +>MySet : Symbol(MySet, Decl(conditionalTypeClassMembers.ts, 3, 1)) +>TSet : Symbol(TSet, Decl(conditionalTypeClassMembers.ts, 5, 20)) +>MyRecord : Symbol(MyRecord, Decl(conditionalTypeClassMembers.ts, 0, 0)) + + public item(): TSet; +>item : Symbol(MySet.item, Decl(conditionalTypeClassMembers.ts, 5, 44)) +>TSet : Symbol(TSet, Decl(conditionalTypeClassMembers.ts, 5, 20)) +} + +type DS = TRec extends MyRecord ? MySet : TRec[]; +>DS : Symbol(DS, Decl(conditionalTypeClassMembers.ts, 7, 1)) +>TRec : Symbol(TRec, Decl(conditionalTypeClassMembers.ts, 9, 8)) +>MyRecord : Symbol(MyRecord, Decl(conditionalTypeClassMembers.ts, 0, 0)) +>key : Symbol(key, Decl(conditionalTypeClassMembers.ts, 9, 35)) +>TRec : Symbol(TRec, Decl(conditionalTypeClassMembers.ts, 9, 8)) +>MyRecord : Symbol(MyRecord, Decl(conditionalTypeClassMembers.ts, 0, 0)) +>MySet : Symbol(MySet, Decl(conditionalTypeClassMembers.ts, 3, 1)) +>TRec : Symbol(TRec, Decl(conditionalTypeClassMembers.ts, 9, 8)) +>TRec : Symbol(TRec, Decl(conditionalTypeClassMembers.ts, 9, 8)) + diff --git a/tests/baselines/reference/conditionalTypeClassMembers.types b/tests/baselines/reference/conditionalTypeClassMembers.types new file mode 100644 index 00000000000..0b09fe6849d --- /dev/null +++ b/tests/baselines/reference/conditionalTypeClassMembers.types @@ -0,0 +1,22 @@ +=== tests/cases/compiler/conditionalTypeClassMembers.ts === +declare class MyRecord { +>MyRecord : MyRecord + + private a(); +>a : () => any + + b(): unknown; +>b : () => unknown +} + +declare class MySet { +>MySet : MySet + + public item(): TSet; +>item : () => TSet +} + +type DS = TRec extends MyRecord ? MySet : TRec[]; +>DS : DS +>key : string + diff --git a/tests/baselines/reference/mixinAccessModifiers.symbols b/tests/baselines/reference/mixinAccessModifiers.symbols index e7847dbcfd3..41cc19129a2 100644 --- a/tests/baselines/reference/mixinAccessModifiers.symbols +++ b/tests/baselines/reference/mixinAccessModifiers.symbols @@ -387,13 +387,13 @@ function f9(x: ProtectedGeneric<{a: void;}> & ProtectedGeneric<{a:void;b:void;}> >b : Symbol(b, Decl(mixinAccessModifiers.ts, 127, 71)) x.privateMethod(); // Error, private constituent makes method inaccessible ->x.privateMethod : Symbol(ProtectedGeneric.privateMethod, Decl(mixinAccessModifiers.ts, 107, 27), Decl(mixinAccessModifiers.ts, 107, 27)) +>x.privateMethod : Symbol(ProtectedGeneric.privateMethod, Decl(mixinAccessModifiers.ts, 107, 27)) >x : Symbol(x, Decl(mixinAccessModifiers.ts, 127, 12)) ->privateMethod : Symbol(ProtectedGeneric.privateMethod, Decl(mixinAccessModifiers.ts, 107, 27), Decl(mixinAccessModifiers.ts, 107, 27)) +>privateMethod : Symbol(ProtectedGeneric.privateMethod, Decl(mixinAccessModifiers.ts, 107, 27)) x.protectedMethod(); // Error, protected when all constituents are protected ->x.protectedMethod : Symbol(ProtectedGeneric.protectedMethod, Decl(mixinAccessModifiers.ts, 108, 27), Decl(mixinAccessModifiers.ts, 108, 27)) +>x.protectedMethod : Symbol(ProtectedGeneric.protectedMethod, Decl(mixinAccessModifiers.ts, 108, 27)) >x : Symbol(x, Decl(mixinAccessModifiers.ts, 127, 12)) ->protectedMethod : Symbol(ProtectedGeneric.protectedMethod, Decl(mixinAccessModifiers.ts, 108, 27), Decl(mixinAccessModifiers.ts, 108, 27)) +>protectedMethod : Symbol(ProtectedGeneric.protectedMethod, Decl(mixinAccessModifiers.ts, 108, 27)) } diff --git a/tests/baselines/reference/mixinAccessModifiers.types b/tests/baselines/reference/mixinAccessModifiers.types index 9b4cb432ab2..4ff6c4de289 100644 --- a/tests/baselines/reference/mixinAccessModifiers.types +++ b/tests/baselines/reference/mixinAccessModifiers.types @@ -373,14 +373,14 @@ function f9(x: ProtectedGeneric<{a: void;}> & ProtectedGeneric<{a:void;b:void;}> x.privateMethod(); // Error, private constituent makes method inaccessible >x.privateMethod() : void ->x.privateMethod : (() => void) & (() => void) +>x.privateMethod : () => void >x : ProtectedGeneric<{ a: void; }> & ProtectedGeneric<{ a: void; b: void; }> ->privateMethod : (() => void) & (() => void) +>privateMethod : () => void x.protectedMethod(); // Error, protected when all constituents are protected >x.protectedMethod() : void ->x.protectedMethod : (() => void) & (() => void) +>x.protectedMethod : () => void >x : ProtectedGeneric<{ a: void; }> & ProtectedGeneric<{ a: void; b: void; }> ->protectedMethod : (() => void) & (() => void) +>protectedMethod : () => void } diff --git a/tests/baselines/reference/mixinIntersectionIsValidbaseType.types b/tests/baselines/reference/mixinIntersectionIsValidbaseType.types index c828188dcd6..ec594ece620 100644 --- a/tests/baselines/reference/mixinIntersectionIsValidbaseType.types +++ b/tests/baselines/reference/mixinIntersectionIsValidbaseType.types @@ -56,9 +56,9 @@ const AMixin = & Initable>(SuperClass: K) => { }; let SomeHowNotOkay = class A extends Serializable(SuperClass) { ->SomeHowNotOkay : { new (...args: any[]): A; prototype: AMixin.A; init: (...args: any[]) => void; } & K ->class A extends Serializable(SuperClass) { } : { new (...args: any[]): A; prototype: AMixin.A; init: (...args: any[]) => void; } & K ->A : { new (...args: any[]): A; prototype: AMixin.A; init: (...args: any[]) => void; } & K +>SomeHowNotOkay : { new (...args: any[]): A; prototype: AMixin.A; init(...args: any[]): void; } & K +>class A extends Serializable(SuperClass) { } : { new (...args: any[]): A; prototype: AMixin.A; init(...args: any[]): void; } & K +>A : { new (...args: any[]): A; prototype: AMixin.A; init(...args: any[]): void; } & K >Serializable(SuperClass) : Serializable.SerializableLocal & Initable >Serializable : & Initable>(SuperClass: K) => { new (...args: any[]): SerializableLocal; prototype: Serializable.SerializableLocal; init(...args: any[]): void; } & K >SuperClass : K diff --git a/tests/baselines/reference/sliceResultCast.js b/tests/baselines/reference/sliceResultCast.js new file mode 100644 index 00000000000..49bcfc2ccae --- /dev/null +++ b/tests/baselines/reference/sliceResultCast.js @@ -0,0 +1,7 @@ +//// [sliceResultCast.ts] +declare var x: [number, string] | [number, string, string]; + +x.slice(1) as readonly string[]; + +//// [sliceResultCast.js] +x.slice(1); diff --git a/tests/baselines/reference/sliceResultCast.symbols b/tests/baselines/reference/sliceResultCast.symbols new file mode 100644 index 00000000000..0f8a4761baa --- /dev/null +++ b/tests/baselines/reference/sliceResultCast.symbols @@ -0,0 +1,9 @@ +=== tests/cases/compiler/sliceResultCast.ts === +declare var x: [number, string] | [number, string, string]; +>x : Symbol(x, Decl(sliceResultCast.ts, 0, 11)) + +x.slice(1) as readonly string[]; +>x.slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --)) +>x : Symbol(x, Decl(sliceResultCast.ts, 0, 11)) +>slice : Symbol(Array.slice, Decl(lib.es5.d.ts, --, --)) + diff --git a/tests/baselines/reference/sliceResultCast.types b/tests/baselines/reference/sliceResultCast.types new file mode 100644 index 00000000000..221046e037d --- /dev/null +++ b/tests/baselines/reference/sliceResultCast.types @@ -0,0 +1,12 @@ +=== tests/cases/compiler/sliceResultCast.ts === +declare var x: [number, string] | [number, string, string]; +>x : [number, string] | [number, string, string] + +x.slice(1) as readonly string[]; +>x.slice(1) as readonly string[] : readonly string[] +>x.slice(1) : (string | number)[] +>x.slice : (start?: number, end?: number) => (string | number)[] +>x : [number, string] | [number, string, string] +>slice : (start?: number, end?: number) => (string | number)[] +>1 : 1 + diff --git a/tests/baselines/reference/typeParameterExtendingUnion1.symbols b/tests/baselines/reference/typeParameterExtendingUnion1.symbols index cb36861feba..c7dd566f7f5 100644 --- a/tests/baselines/reference/typeParameterExtendingUnion1.symbols +++ b/tests/baselines/reference/typeParameterExtendingUnion1.symbols @@ -33,9 +33,9 @@ function f(a: T) { >T : Symbol(T, Decl(typeParameterExtendingUnion1.ts, 8, 11)) a.run(); ->a.run : Symbol(Animal.run, Decl(typeParameterExtendingUnion1.ts, 0, 14), Decl(typeParameterExtendingUnion1.ts, 0, 14)) +>a.run : Symbol(Animal.run, Decl(typeParameterExtendingUnion1.ts, 0, 14)) >a : Symbol(a, Decl(typeParameterExtendingUnion1.ts, 8, 32)) ->run : Symbol(Animal.run, Decl(typeParameterExtendingUnion1.ts, 0, 14), Decl(typeParameterExtendingUnion1.ts, 0, 14)) +>run : Symbol(Animal.run, Decl(typeParameterExtendingUnion1.ts, 0, 14)) run(a); >run : Symbol(run, Decl(typeParameterExtendingUnion1.ts, 2, 33)) diff --git a/tests/baselines/reference/typeParameterExtendingUnion1.types b/tests/baselines/reference/typeParameterExtendingUnion1.types index 5cc8d010b78..feda22d1a9d 100644 --- a/tests/baselines/reference/typeParameterExtendingUnion1.types +++ b/tests/baselines/reference/typeParameterExtendingUnion1.types @@ -30,9 +30,9 @@ function f(a: T) { a.run(); >a.run() : void ->a.run : (() => void) | (() => void) +>a.run : () => void >a : Cat | Dog ->run : (() => void) | (() => void) +>run : () => void run(a); >run(a) : void diff --git a/tests/baselines/reference/typeParameterExtendingUnion2.symbols b/tests/baselines/reference/typeParameterExtendingUnion2.symbols index b9c2eed4034..2728cf62570 100644 --- a/tests/baselines/reference/typeParameterExtendingUnion2.symbols +++ b/tests/baselines/reference/typeParameterExtendingUnion2.symbols @@ -20,9 +20,9 @@ function run(a: Cat | Dog) { >Dog : Symbol(Dog, Decl(typeParameterExtendingUnion2.ts, 1, 33)) a.run(); ->a.run : Symbol(Animal.run, Decl(typeParameterExtendingUnion2.ts, 0, 14), Decl(typeParameterExtendingUnion2.ts, 0, 14)) +>a.run : Symbol(Animal.run, Decl(typeParameterExtendingUnion2.ts, 0, 14)) >a : Symbol(a, Decl(typeParameterExtendingUnion2.ts, 4, 13)) ->run : Symbol(Animal.run, Decl(typeParameterExtendingUnion2.ts, 0, 14), Decl(typeParameterExtendingUnion2.ts, 0, 14)) +>run : Symbol(Animal.run, Decl(typeParameterExtendingUnion2.ts, 0, 14)) } function f(a: T) { @@ -34,9 +34,9 @@ function f(a: T) { >T : Symbol(T, Decl(typeParameterExtendingUnion2.ts, 8, 11)) a.run(); ->a.run : Symbol(Animal.run, Decl(typeParameterExtendingUnion2.ts, 0, 14), Decl(typeParameterExtendingUnion2.ts, 0, 14)) +>a.run : Symbol(Animal.run, Decl(typeParameterExtendingUnion2.ts, 0, 14)) >a : Symbol(a, Decl(typeParameterExtendingUnion2.ts, 8, 32)) ->run : Symbol(Animal.run, Decl(typeParameterExtendingUnion2.ts, 0, 14), Decl(typeParameterExtendingUnion2.ts, 0, 14)) +>run : Symbol(Animal.run, Decl(typeParameterExtendingUnion2.ts, 0, 14)) run(a); >run : Symbol(run, Decl(typeParameterExtendingUnion2.ts, 2, 33)) diff --git a/tests/baselines/reference/typeParameterExtendingUnion2.types b/tests/baselines/reference/typeParameterExtendingUnion2.types index 475e418f410..2539d4ec3bd 100644 --- a/tests/baselines/reference/typeParameterExtendingUnion2.types +++ b/tests/baselines/reference/typeParameterExtendingUnion2.types @@ -19,9 +19,9 @@ function run(a: Cat | Dog) { a.run(); >a.run() : void ->a.run : (() => void) | (() => void) +>a.run : () => void >a : Cat | Dog ->run : (() => void) | (() => void) +>run : () => void } function f(a: T) { @@ -30,9 +30,9 @@ function f(a: T) { a.run(); >a.run() : void ->a.run : (() => void) | (() => void) +>a.run : () => void >a : Cat | Dog ->run : (() => void) | (() => void) +>run : () => void run(a); >run(a) : void diff --git a/tests/cases/compiler/conditionalTypeClassMembers.ts b/tests/cases/compiler/conditionalTypeClassMembers.ts new file mode 100644 index 00000000000..fa96613617d --- /dev/null +++ b/tests/cases/compiler/conditionalTypeClassMembers.ts @@ -0,0 +1,10 @@ +declare class MyRecord { + private a(); + b(): unknown; +} + +declare class MySet { + public item(): TSet; +} + +type DS = TRec extends MyRecord ? MySet : TRec[]; \ No newline at end of file diff --git a/tests/cases/compiler/sliceResultCast.ts b/tests/cases/compiler/sliceResultCast.ts new file mode 100644 index 00000000000..570e4079154 --- /dev/null +++ b/tests/cases/compiler/sliceResultCast.ts @@ -0,0 +1,3 @@ +declare var x: [number, string] | [number, string, string]; + +x.slice(1) as readonly string[]; \ No newline at end of file