diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2768c142f5aad..562f730a0dfef 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6383,8 +6383,9 @@ namespace ts { * * @param type a type to look up property from * @param name a name of property to look up in a given type + * @param allowPartialUnions return partial unions instead of undefined */ - function getPropertyOfType(type: Type, name: __String): Symbol | undefined { + function getPropertyOfType(type: Type, name: __String, allowPartialUnions = false): Symbol | undefined { type = getApparentType(type); if (type.flags & TypeFlags.Object) { const resolved = resolveStructuredTypeMembers(type); @@ -6401,7 +6402,12 @@ namespace ts { return getPropertyOfObjectType(globalObjectType, name); } if (type.flags & TypeFlags.UnionOrIntersection) { - return getPropertyOfUnionOrIntersectionType(type, name); + if (allowPartialUnions) { + return getUnionOrIntersectionProperty(type, name); + } + else { + return getPropertyOfUnionOrIntersectionType(type, name); + } } return undefined; } @@ -7997,7 +8003,7 @@ namespace ts { getPropertyNameForKnownSymbolName(idText((accessExpression.argumentExpression).name)) : undefined; if (propName !== undefined) { - const prop = getPropertyOfType(objectType, propName); + const prop = getPropertyOfType(objectType, propName, /*allowPartialUnions*/ !accessExpression); if (prop) { if (accessExpression) { markPropertyAsReferenced(prop, accessExpression, /*isThisAccess*/ accessExpression.expression.kind === SyntaxKind.ThisKeyword); diff --git a/tests/baselines/reference/indexedAccessPartialUnions.errors.txt b/tests/baselines/reference/indexedAccessPartialUnions.errors.txt new file mode 100644 index 0000000000000..e3ce450b20123 --- /dev/null +++ b/tests/baselines/reference/indexedAccessPartialUnions.errors.txt @@ -0,0 +1,39 @@ +tests/cases/compiler/indexedAccessPartialUnions.ts(14,12): error TS7017: Element implicitly has an 'any' type because type '{ baz: string; } | { qux: number; }' has no index signature. +tests/cases/compiler/indexedAccessPartialUnions.ts(18,20): error TS2339: Property 'baz' does not exist on type '{ baz: string; } | { qux: number; }'. + Property 'baz' does not exist on type '{ qux: number; }'. + + +==== tests/cases/compiler/indexedAccessPartialUnions.ts (2 errors) ==== + // Repro from #21975 + + interface Foo { + bar: { + baz: string; + } | { + qux: number; + } + } + + type ShouldBeString = Foo['bar']['baz']; + + function f(foo: Foo) { + return foo['bar']['baz']; // Error + ~~~~~~~~~~~~~~~~~ +!!! error TS7017: Element implicitly has an 'any' type because type '{ baz: string; } | { qux: number; }' has no index signature. + } + + function g(foo: Foo) { + return foo.bar.baz; // Error + ~~~ +!!! error TS2339: Property 'baz' does not exist on type '{ baz: string; } | { qux: number; }'. +!!! error TS2339: Property 'baz' does not exist on type '{ qux: number; }'. + } + + interface HasOptionalMember { + bar?: { + baz: string; + } + } + + type ShouldBeString2 = HasOptionalMember['bar']['baz']; + \ No newline at end of file diff --git a/tests/baselines/reference/indexedAccessPartialUnions.js b/tests/baselines/reference/indexedAccessPartialUnions.js new file mode 100644 index 0000000000000..2231948825e99 --- /dev/null +++ b/tests/baselines/reference/indexedAccessPartialUnions.js @@ -0,0 +1,39 @@ +//// [indexedAccessPartialUnions.ts] +// Repro from #21975 + +interface Foo { + bar: { + baz: string; + } | { + qux: number; + } +} + +type ShouldBeString = Foo['bar']['baz']; + +function f(foo: Foo) { + return foo['bar']['baz']; // Error +} + +function g(foo: Foo) { + return foo.bar.baz; // Error +} + +interface HasOptionalMember { + bar?: { + baz: string; + } +} + +type ShouldBeString2 = HasOptionalMember['bar']['baz']; + + +//// [indexedAccessPartialUnions.js] +"use strict"; +// Repro from #21975 +function f(foo) { + return foo['bar']['baz']; // Error +} +function g(foo) { + return foo.bar.baz; // Error +} diff --git a/tests/baselines/reference/indexedAccessPartialUnions.symbols b/tests/baselines/reference/indexedAccessPartialUnions.symbols new file mode 100644 index 0000000000000..f037d6976f5a1 --- /dev/null +++ b/tests/baselines/reference/indexedAccessPartialUnions.symbols @@ -0,0 +1,58 @@ +=== tests/cases/compiler/indexedAccessPartialUnions.ts === +// Repro from #21975 + +interface Foo { +>Foo : Symbol(Foo, Decl(indexedAccessPartialUnions.ts, 0, 0)) + + bar: { +>bar : Symbol(Foo.bar, Decl(indexedAccessPartialUnions.ts, 2, 15)) + + baz: string; +>baz : Symbol(baz, Decl(indexedAccessPartialUnions.ts, 3, 10)) + + } | { + qux: number; +>qux : Symbol(qux, Decl(indexedAccessPartialUnions.ts, 5, 9)) + } +} + +type ShouldBeString = Foo['bar']['baz']; +>ShouldBeString : Symbol(ShouldBeString, Decl(indexedAccessPartialUnions.ts, 8, 1)) +>Foo : Symbol(Foo, Decl(indexedAccessPartialUnions.ts, 0, 0)) + +function f(foo: Foo) { +>f : Symbol(f, Decl(indexedAccessPartialUnions.ts, 10, 40)) +>foo : Symbol(foo, Decl(indexedAccessPartialUnions.ts, 12, 11)) +>Foo : Symbol(Foo, Decl(indexedAccessPartialUnions.ts, 0, 0)) + + return foo['bar']['baz']; // Error +>foo : Symbol(foo, Decl(indexedAccessPartialUnions.ts, 12, 11)) +>'bar' : Symbol(Foo.bar, Decl(indexedAccessPartialUnions.ts, 2, 15)) +} + +function g(foo: Foo) { +>g : Symbol(g, Decl(indexedAccessPartialUnions.ts, 14, 1)) +>foo : Symbol(foo, Decl(indexedAccessPartialUnions.ts, 16, 11)) +>Foo : Symbol(Foo, Decl(indexedAccessPartialUnions.ts, 0, 0)) + + return foo.bar.baz; // Error +>foo.bar : Symbol(Foo.bar, Decl(indexedAccessPartialUnions.ts, 2, 15)) +>foo : Symbol(foo, Decl(indexedAccessPartialUnions.ts, 16, 11)) +>bar : Symbol(Foo.bar, Decl(indexedAccessPartialUnions.ts, 2, 15)) +} + +interface HasOptionalMember { +>HasOptionalMember : Symbol(HasOptionalMember, Decl(indexedAccessPartialUnions.ts, 18, 1)) + + bar?: { +>bar : Symbol(HasOptionalMember.bar, Decl(indexedAccessPartialUnions.ts, 20, 29)) + + baz: string; +>baz : Symbol(baz, Decl(indexedAccessPartialUnions.ts, 21, 11)) + } +} + +type ShouldBeString2 = HasOptionalMember['bar']['baz']; +>ShouldBeString2 : Symbol(ShouldBeString2, Decl(indexedAccessPartialUnions.ts, 24, 1)) +>HasOptionalMember : Symbol(HasOptionalMember, Decl(indexedAccessPartialUnions.ts, 18, 1)) + diff --git a/tests/baselines/reference/indexedAccessPartialUnions.types b/tests/baselines/reference/indexedAccessPartialUnions.types new file mode 100644 index 0000000000000..e7f7a9193b84e --- /dev/null +++ b/tests/baselines/reference/indexedAccessPartialUnions.types @@ -0,0 +1,63 @@ +=== tests/cases/compiler/indexedAccessPartialUnions.ts === +// Repro from #21975 + +interface Foo { +>Foo : Foo + + bar: { +>bar : { baz: string; } | { qux: number; } + + baz: string; +>baz : string + + } | { + qux: number; +>qux : number + } +} + +type ShouldBeString = Foo['bar']['baz']; +>ShouldBeString : string +>Foo : Foo + +function f(foo: Foo) { +>f : (foo: Foo) => any +>foo : Foo +>Foo : Foo + + return foo['bar']['baz']; // Error +>foo['bar']['baz'] : any +>foo['bar'] : { baz: string; } | { qux: number; } +>foo : Foo +>'bar' : "bar" +>'baz' : "baz" +} + +function g(foo: Foo) { +>g : (foo: Foo) => any +>foo : Foo +>Foo : Foo + + return foo.bar.baz; // Error +>foo.bar.baz : any +>foo.bar : { baz: string; } | { qux: number; } +>foo : Foo +>bar : { baz: string; } | { qux: number; } +>baz : any +} + +interface HasOptionalMember { +>HasOptionalMember : HasOptionalMember + + bar?: { +>bar : { baz: string; } | undefined + + baz: string; +>baz : string + } +} + +type ShouldBeString2 = HasOptionalMember['bar']['baz']; +>ShouldBeString2 : string +>HasOptionalMember : HasOptionalMember + diff --git a/tests/cases/compiler/indexedAccessPartialUnions.ts b/tests/cases/compiler/indexedAccessPartialUnions.ts new file mode 100644 index 0000000000000..8c7c437fce414 --- /dev/null +++ b/tests/cases/compiler/indexedAccessPartialUnions.ts @@ -0,0 +1,29 @@ +// @strict: true + +// Repro from #21975 + +interface Foo { + bar: { + baz: string; + } | { + qux: number; + } +} + +type ShouldBeString = Foo['bar']['baz']; + +function f(foo: Foo) { + return foo['bar']['baz']; // Error +} + +function g(foo: Foo) { + return foo.bar.baz; // Error +} + +interface HasOptionalMember { + bar?: { + baz: string; + } +} + +type ShouldBeString2 = HasOptionalMember['bar']['baz'];