Skip to content

Commit f4d69b9

Browse files
ahejlsbergDanielRosenwasser
authored andcommitted
Properly handle private/protected members in unions of object types (#38277)
* Property handle private/protected properties in unions of object types * Add regression test
1 parent 90f12d6 commit f4d69b9

File tree

5 files changed

+112
-10
lines changed

5 files changed

+112
-10
lines changed

src/compiler/checker.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10473,10 +10473,10 @@ namespace ts {
1047310473
}
1047410474

1047510475
function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String): Symbol | undefined {
10476-
const propSet = createMap<Symbol>();
10476+
let singleProp: Symbol | undefined;
10477+
let propSet: Map<Symbol> | undefined;
1047710478
let indexTypes: Type[] | undefined;
1047810479
const isUnion = containingType.flags & TypeFlags.Union;
10479-
const excludeModifiers = isUnion ? ModifierFlags.NonPublicAccessibilityModifier : 0;
1048010480
// Flags we want to propagate to the result if they exist in all source symbols
1048110481
let optionalFlag = isUnion ? SymbolFlags.None : SymbolFlags.Optional;
1048210482
let syntheticFlag = CheckFlags.SyntheticMethod;
@@ -10486,16 +10486,25 @@ namespace ts {
1048610486
if (!(type === errorType || type.flags & TypeFlags.Never)) {
1048710487
const prop = getPropertyOfType(type, name);
1048810488
const modifiers = prop ? getDeclarationModifierFlagsFromSymbol(prop) : 0;
10489-
if (prop && !(modifiers & excludeModifiers)) {
10489+
if (prop) {
1049010490
if (isUnion) {
1049110491
optionalFlag |= (prop.flags & SymbolFlags.Optional);
1049210492
}
1049310493
else {
1049410494
optionalFlag &= prop.flags;
1049510495
}
10496-
const id = "" + getSymbolId(prop);
10497-
if (!propSet.has(id)) {
10498-
propSet.set(id, prop);
10496+
if (!singleProp) {
10497+
singleProp = prop;
10498+
}
10499+
else if (prop !== singleProp) {
10500+
if (!propSet) {
10501+
propSet = createMap<Symbol>();
10502+
propSet.set("" + getSymbolId(singleProp), singleProp);
10503+
}
10504+
const id = "" + getSymbolId(prop);
10505+
if (!propSet.has(id)) {
10506+
propSet.set(id, prop);
10507+
}
1049910508
}
1050010509
checkFlags |= (isReadonlySymbol(prop) ? CheckFlags.Readonly : 0) |
1050110510
(!(modifiers & ModifierFlags.NonPublicAccessibilityModifier) ? CheckFlags.ContainsPublic : 0) |
@@ -10522,13 +10531,15 @@ namespace ts {
1052210531
}
1052310532
}
1052410533
}
10525-
if (!propSet.size) {
10534+
if (!singleProp || isUnion && (propSet || checkFlags & CheckFlags.Partial) && checkFlags & (CheckFlags.ContainsPrivate | CheckFlags.ContainsProtected)) {
10535+
// No property was found, or, in a union, a property has a private or protected declaration in one
10536+
// constituent, but is missing or has a different declaration in another constituent.
1052610537
return undefined;
1052710538
}
10528-
const props = arrayFrom(propSet.values());
10529-
if (props.length === 1 && !(checkFlags & CheckFlags.ReadPartial) && !indexTypes) {
10530-
return props[0];
10539+
if (!propSet && !(checkFlags & CheckFlags.ReadPartial) && !indexTypes) {
10540+
return singleProp;
1053110541
}
10542+
const props = propSet ? arrayFrom(propSet.values()) : [singleProp];
1053210543
let declarations: Declaration[] | undefined;
1053310544
let firstType: Type | undefined;
1053410545
let nameType: Type | undefined;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//// [privatePropertyInUnion.ts]
2+
// Repro from #38236
3+
4+
type Type = string | object;
5+
6+
class SyncableObject {
7+
private foo: unknown;
8+
}
9+
10+
interface SyncableRef<T extends ISyncableObject> {}
11+
12+
interface ISyncableObject<T = object> extends SyncableObject {}
13+
14+
type __ValueDescriptorType<T extends string | object> = T extends ISyncableObject ? SyncableRef<T> : T;
15+
16+
17+
//// [privatePropertyInUnion.js]
18+
"use strict";
19+
// Repro from #38236
20+
var SyncableObject = /** @class */ (function () {
21+
function SyncableObject() {
22+
}
23+
return SyncableObject;
24+
}());
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
=== tests/cases/compiler/privatePropertyInUnion.ts ===
2+
// Repro from #38236
3+
4+
type Type = string | object;
5+
>Type : Symbol(Type, Decl(privatePropertyInUnion.ts, 0, 0))
6+
7+
class SyncableObject {
8+
>SyncableObject : Symbol(SyncableObject, Decl(privatePropertyInUnion.ts, 2, 28))
9+
10+
private foo: unknown;
11+
>foo : Symbol(SyncableObject.foo, Decl(privatePropertyInUnion.ts, 4, 22))
12+
}
13+
14+
interface SyncableRef<T extends ISyncableObject> {}
15+
>SyncableRef : Symbol(SyncableRef, Decl(privatePropertyInUnion.ts, 6, 1))
16+
>T : Symbol(T, Decl(privatePropertyInUnion.ts, 8, 22))
17+
>ISyncableObject : Symbol(ISyncableObject, Decl(privatePropertyInUnion.ts, 8, 51))
18+
19+
interface ISyncableObject<T = object> extends SyncableObject {}
20+
>ISyncableObject : Symbol(ISyncableObject, Decl(privatePropertyInUnion.ts, 8, 51))
21+
>T : Symbol(T, Decl(privatePropertyInUnion.ts, 10, 26))
22+
>SyncableObject : Symbol(SyncableObject, Decl(privatePropertyInUnion.ts, 2, 28))
23+
24+
type __ValueDescriptorType<T extends string | object> = T extends ISyncableObject ? SyncableRef<T> : T;
25+
>__ValueDescriptorType : Symbol(__ValueDescriptorType, Decl(privatePropertyInUnion.ts, 10, 63))
26+
>T : Symbol(T, Decl(privatePropertyInUnion.ts, 12, 27))
27+
>T : Symbol(T, Decl(privatePropertyInUnion.ts, 12, 27))
28+
>ISyncableObject : Symbol(ISyncableObject, Decl(privatePropertyInUnion.ts, 8, 51))
29+
>SyncableRef : Symbol(SyncableRef, Decl(privatePropertyInUnion.ts, 6, 1))
30+
>T : Symbol(T, Decl(privatePropertyInUnion.ts, 12, 27))
31+
>T : Symbol(T, Decl(privatePropertyInUnion.ts, 12, 27))
32+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
=== tests/cases/compiler/privatePropertyInUnion.ts ===
2+
// Repro from #38236
3+
4+
type Type = string | object;
5+
>Type : string | object
6+
7+
class SyncableObject {
8+
>SyncableObject : SyncableObject
9+
10+
private foo: unknown;
11+
>foo : unknown
12+
}
13+
14+
interface SyncableRef<T extends ISyncableObject> {}
15+
16+
interface ISyncableObject<T = object> extends SyncableObject {}
17+
18+
type __ValueDescriptorType<T extends string | object> = T extends ISyncableObject ? SyncableRef<T> : T;
19+
>__ValueDescriptorType : __ValueDescriptorType<T>
20+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// @strict: true
2+
3+
// Repro from #38236
4+
5+
type Type = string | object;
6+
7+
class SyncableObject {
8+
private foo: unknown;
9+
}
10+
11+
interface SyncableRef<T extends ISyncableObject> {}
12+
13+
interface ISyncableObject<T = object> extends SyncableObject {}
14+
15+
type __ValueDescriptorType<T extends string | object> = T extends ISyncableObject ? SyncableRef<T> : T;

0 commit comments

Comments
 (0)