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
This commit is contained in:
Wesley Wigham
2021-04-14 16:55:45 -07:00
committed by GitHub
parent 6c7c5e9ec2
commit 3ab6809e38
19 changed files with 170 additions and 37 deletions

View File

@@ -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<SymbolId, Symbol>();
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<T>.length` printed
// back and not `Array<string>.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<SymbolId, Symbol>();
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;

View File

@@ -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))

View File

@@ -19,8 +19,8 @@ declare const b: (string | number)[] | null[] | undefined[] | {}[];
let x = a.equalsShallow(b);
>x : boolean
>a.equalsShallow(b) : boolean
>a.equalsShallow : (<T>(this: readonly T[], other: readonly T[]) => boolean) | (<T>(this: readonly T[], other: readonly T[]) => boolean) | (<T>(this: readonly T[], other: readonly T[]) => boolean) | (<T>(this: readonly T[], other: readonly T[]) => boolean)
>a.equalsShallow : <T>(this: readonly T[], other: readonly T[]) => boolean
>a : (string | number)[] | null[] | undefined[] | {}[]
>equalsShallow : (<T>(this: readonly T[], other: readonly T[]) => boolean) | (<T>(this: readonly T[], other: readonly T[]) => boolean) | (<T>(this: readonly T[], other: readonly T[]) => boolean) | (<T>(this: readonly T[], other: readonly T[]) => boolean)
>equalsShallow : <T>(this: readonly T[], other: readonly T[]) => boolean
>b : (string | number)[] | null[] | undefined[] | {}[]

View File

@@ -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": "(",

View File

@@ -0,0 +1,13 @@
//// [conditionalTypeClassMembers.ts]
declare class MyRecord {
private a();
b(): unknown;
}
declare class MySet<TSet extends MyRecord> {
public item(): TSet;
}
type DS<TRec extends MyRecord | { [key: string]: unknown }> = TRec extends MyRecord ? MySet<TRec> : TRec[];
//// [conditionalTypeClassMembers.js]

View File

@@ -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<TSet extends MyRecord> {
>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 | { [key: string]: unknown }> = TRec extends MyRecord ? MySet<TRec> : 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))

View File

@@ -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<TSet extends MyRecord> {
>MySet : MySet<TSet>
public item(): TSet;
>item : () => TSet
}
type DS<TRec extends MyRecord | { [key: string]: unknown }> = TRec extends MyRecord ? MySet<TRec> : TRec[];
>DS : DS<TRec>
>key : string

View File

@@ -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))
}

View File

@@ -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
}

View File

@@ -56,9 +56,9 @@ const AMixin = <K extends Constructor<Initable> & Initable>(SuperClass: K) => {
};
let SomeHowNotOkay = class A extends Serializable(SuperClass) {
>SomeHowNotOkay : { new (...args: any[]): A; prototype: AMixin<any>.A; init: (...args: any[]) => void; } & K
>class A extends Serializable(SuperClass) { } : { new (...args: any[]): A; prototype: AMixin<any>.A; init: (...args: any[]) => void; } & K
>A : { new (...args: any[]): A; prototype: AMixin<any>.A; init: (...args: any[]) => void; } & K
>SomeHowNotOkay : { new (...args: any[]): A; prototype: AMixin<any>.A; init(...args: any[]): void; } & K
>class A extends Serializable(SuperClass) { } : { new (...args: any[]): A; prototype: AMixin<any>.A; init(...args: any[]): void; } & K
>A : { new (...args: any[]): A; prototype: AMixin<any>.A; init(...args: any[]): void; } & K
>Serializable(SuperClass) : Serializable<K>.SerializableLocal & Initable
>Serializable : <K extends Constructor<Initable> & Initable>(SuperClass: K) => { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
>SuperClass : K

View File

@@ -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);

View File

@@ -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, --, --))

View File

@@ -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

View File

@@ -33,9 +33,9 @@ function f<T extends Cat | Dog>(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))

View File

@@ -30,9 +30,9 @@ function f<T extends Cat | Dog>(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

View File

@@ -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<T extends Cat | Dog>(a: T) {
@@ -34,9 +34,9 @@ function f<T extends Cat | Dog>(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))

View File

@@ -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<T extends Cat | Dog>(a: T) {
@@ -30,9 +30,9 @@ function f<T extends Cat | Dog>(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

View File

@@ -0,0 +1,10 @@
declare class MyRecord {
private a();
b(): unknown;
}
declare class MySet<TSet extends MyRecord> {
public item(): TSet;
}
type DS<TRec extends MyRecord | { [key: string]: unknown }> = TRec extends MyRecord ? MySet<TRec> : TRec[];

View File

@@ -0,0 +1,3 @@
declare var x: [number, string] | [number, string, string];
x.slice(1) as readonly string[];