From f4d69b9a8921f8a264f7e10408e2f61ec39298d3 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 4 May 2020 15:28:00 -0700 Subject: [PATCH] Properly handle private/protected members in unions of object types (#38277) * Property handle private/protected properties in unions of object types * Add regression test --- src/compiler/checker.ts | 31 ++++++++++++------ .../reference/privatePropertyInUnion.js | 24 ++++++++++++++ .../reference/privatePropertyInUnion.symbols | 32 +++++++++++++++++++ .../reference/privatePropertyInUnion.types | 20 ++++++++++++ .../cases/compiler/privatePropertyInUnion.ts | 15 +++++++++ 5 files changed, 112 insertions(+), 10 deletions(-) create mode 100644 tests/baselines/reference/privatePropertyInUnion.js create mode 100644 tests/baselines/reference/privatePropertyInUnion.symbols create mode 100644 tests/baselines/reference/privatePropertyInUnion.types create mode 100644 tests/cases/compiler/privatePropertyInUnion.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dd9805c497296..57e1ebec5df5f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10473,10 +10473,10 @@ namespace ts { } function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String): Symbol | undefined { - const propSet = createMap(); + let singleProp: Symbol | undefined; + let propSet: Map | undefined; let indexTypes: Type[] | undefined; const isUnion = containingType.flags & TypeFlags.Union; - const excludeModifiers = isUnion ? ModifierFlags.NonPublicAccessibilityModifier : 0; // Flags we want to propagate to the result if they exist in all source symbols let optionalFlag = isUnion ? SymbolFlags.None : SymbolFlags.Optional; let syntheticFlag = CheckFlags.SyntheticMethod; @@ -10486,16 +10486,25 @@ namespace ts { if (!(type === errorType || type.flags & TypeFlags.Never)) { const prop = getPropertyOfType(type, name); const modifiers = prop ? getDeclarationModifierFlagsFromSymbol(prop) : 0; - if (prop && !(modifiers & excludeModifiers)) { + if (prop) { if (isUnion) { optionalFlag |= (prop.flags & SymbolFlags.Optional); } else { optionalFlag &= prop.flags; } - const id = "" + getSymbolId(prop); - if (!propSet.has(id)) { - propSet.set(id, prop); + if (!singleProp) { + singleProp = prop; + } + else if (prop !== singleProp) { + if (!propSet) { + propSet = createMap(); + propSet.set("" + getSymbolId(singleProp), singleProp); + } + const id = "" + getSymbolId(prop); + if (!propSet.has(id)) { + propSet.set(id, prop); + } } checkFlags |= (isReadonlySymbol(prop) ? CheckFlags.Readonly : 0) | (!(modifiers & ModifierFlags.NonPublicAccessibilityModifier) ? CheckFlags.ContainsPublic : 0) | @@ -10522,13 +10531,15 @@ namespace ts { } } } - if (!propSet.size) { + if (!singleProp || isUnion && (propSet || checkFlags & CheckFlags.Partial) && checkFlags & (CheckFlags.ContainsPrivate | CheckFlags.ContainsProtected)) { + // No property was found, or, in a union, a property has a private or protected declaration in one + // constituent, but is missing or has a different declaration in another constituent. return undefined; } - const props = arrayFrom(propSet.values()); - if (props.length === 1 && !(checkFlags & CheckFlags.ReadPartial) && !indexTypes) { - return props[0]; + if (!propSet && !(checkFlags & CheckFlags.ReadPartial) && !indexTypes) { + return singleProp; } + const props = propSet ? arrayFrom(propSet.values()) : [singleProp]; let declarations: Declaration[] | undefined; let firstType: Type | undefined; let nameType: Type | undefined; diff --git a/tests/baselines/reference/privatePropertyInUnion.js b/tests/baselines/reference/privatePropertyInUnion.js new file mode 100644 index 0000000000000..f4208e51fe4af --- /dev/null +++ b/tests/baselines/reference/privatePropertyInUnion.js @@ -0,0 +1,24 @@ +//// [privatePropertyInUnion.ts] +// Repro from #38236 + +type Type = string | object; + +class SyncableObject { + private foo: unknown; +} + +interface SyncableRef {} + +interface ISyncableObject extends SyncableObject {} + +type __ValueDescriptorType = T extends ISyncableObject ? SyncableRef : T; + + +//// [privatePropertyInUnion.js] +"use strict"; +// Repro from #38236 +var SyncableObject = /** @class */ (function () { + function SyncableObject() { + } + return SyncableObject; +}()); diff --git a/tests/baselines/reference/privatePropertyInUnion.symbols b/tests/baselines/reference/privatePropertyInUnion.symbols new file mode 100644 index 0000000000000..01b4fdea82529 --- /dev/null +++ b/tests/baselines/reference/privatePropertyInUnion.symbols @@ -0,0 +1,32 @@ +=== tests/cases/compiler/privatePropertyInUnion.ts === +// Repro from #38236 + +type Type = string | object; +>Type : Symbol(Type, Decl(privatePropertyInUnion.ts, 0, 0)) + +class SyncableObject { +>SyncableObject : Symbol(SyncableObject, Decl(privatePropertyInUnion.ts, 2, 28)) + + private foo: unknown; +>foo : Symbol(SyncableObject.foo, Decl(privatePropertyInUnion.ts, 4, 22)) +} + +interface SyncableRef {} +>SyncableRef : Symbol(SyncableRef, Decl(privatePropertyInUnion.ts, 6, 1)) +>T : Symbol(T, Decl(privatePropertyInUnion.ts, 8, 22)) +>ISyncableObject : Symbol(ISyncableObject, Decl(privatePropertyInUnion.ts, 8, 51)) + +interface ISyncableObject extends SyncableObject {} +>ISyncableObject : Symbol(ISyncableObject, Decl(privatePropertyInUnion.ts, 8, 51)) +>T : Symbol(T, Decl(privatePropertyInUnion.ts, 10, 26)) +>SyncableObject : Symbol(SyncableObject, Decl(privatePropertyInUnion.ts, 2, 28)) + +type __ValueDescriptorType = T extends ISyncableObject ? SyncableRef : T; +>__ValueDescriptorType : Symbol(__ValueDescriptorType, Decl(privatePropertyInUnion.ts, 10, 63)) +>T : Symbol(T, Decl(privatePropertyInUnion.ts, 12, 27)) +>T : Symbol(T, Decl(privatePropertyInUnion.ts, 12, 27)) +>ISyncableObject : Symbol(ISyncableObject, Decl(privatePropertyInUnion.ts, 8, 51)) +>SyncableRef : Symbol(SyncableRef, Decl(privatePropertyInUnion.ts, 6, 1)) +>T : Symbol(T, Decl(privatePropertyInUnion.ts, 12, 27)) +>T : Symbol(T, Decl(privatePropertyInUnion.ts, 12, 27)) + diff --git a/tests/baselines/reference/privatePropertyInUnion.types b/tests/baselines/reference/privatePropertyInUnion.types new file mode 100644 index 0000000000000..1bc4ea394773a --- /dev/null +++ b/tests/baselines/reference/privatePropertyInUnion.types @@ -0,0 +1,20 @@ +=== tests/cases/compiler/privatePropertyInUnion.ts === +// Repro from #38236 + +type Type = string | object; +>Type : string | object + +class SyncableObject { +>SyncableObject : SyncableObject + + private foo: unknown; +>foo : unknown +} + +interface SyncableRef {} + +interface ISyncableObject extends SyncableObject {} + +type __ValueDescriptorType = T extends ISyncableObject ? SyncableRef : T; +>__ValueDescriptorType : __ValueDescriptorType + diff --git a/tests/cases/compiler/privatePropertyInUnion.ts b/tests/cases/compiler/privatePropertyInUnion.ts new file mode 100644 index 0000000000000..59069cb5de1ef --- /dev/null +++ b/tests/cases/compiler/privatePropertyInUnion.ts @@ -0,0 +1,15 @@ +// @strict: true + +// Repro from #38236 + +type Type = string | object; + +class SyncableObject { + private foo: unknown; +} + +interface SyncableRef {} + +interface ISyncableObject extends SyncableObject {} + +type __ValueDescriptorType = T extends ISyncableObject ? SyncableRef : T;