Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow partial unions for indexed type access #22094

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(<ObjectType>type);
Expand All @@ -6401,7 +6402,12 @@ namespace ts {
return getPropertyOfObjectType(globalObjectType, name);
}
if (type.flags & TypeFlags.UnionOrIntersection) {
return getPropertyOfUnionOrIntersectionType(<UnionOrIntersectionType>type, name);
if (allowPartialUnions) {
return getUnionOrIntersectionProperty(<UnionOrIntersectionType>type, name);
}
else {
return getPropertyOfUnionOrIntersectionType(<UnionOrIntersectionType>type, name);
}
}
return undefined;
}
Expand Down Expand Up @@ -7997,7 +8003,7 @@ namespace ts {
getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>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);
Expand Down
39 changes: 39 additions & 0 deletions tests/baselines/reference/indexedAccessPartialUnions.errors.txt
Original file line number Diff line number Diff line change
@@ -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'];

39 changes: 39 additions & 0 deletions tests/baselines/reference/indexedAccessPartialUnions.js
Original file line number Diff line number Diff line change
@@ -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
}
58 changes: 58 additions & 0 deletions tests/baselines/reference/indexedAccessPartialUnions.symbols
Original file line number Diff line number Diff line change
@@ -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))

63 changes: 63 additions & 0 deletions tests/baselines/reference/indexedAccessPartialUnions.types
Original file line number Diff line number Diff line change
@@ -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

29 changes: 29 additions & 0 deletions tests/cases/compiler/indexedAccessPartialUnions.ts
Original file line number Diff line number Diff line change
@@ -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'];