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 1 commit
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*/ true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a too permissive. For example, it will allow the following to compile with no error:

interface Foo {
    bar: { baz: string } | { qux: number }
}

function f(x: Foo) {
    return x['bar']['baz'];  // Should be an error
}

It's pretty easy to fix through:

const prop = getPropertyOfType(objectType, propName, !accessExpression);

This will allow partial unions only when the indexed access doesn't originate in an expression.

Copy link
Author

@nomcopter nomcopter Feb 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good catch. Fixed! Thanks for the review @ahejlsberg

if (prop) {
if (accessExpression) {
markPropertyAsReferenced(prop, accessExpression, /*isThisAccess*/ accessExpression.expression.kind === SyntaxKind.ThisKeyword);
Expand Down
25 changes: 25 additions & 0 deletions tests/baselines/reference/indexedAccessPartialUnions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//// [indexedAccessPartialUnions.ts]
// Repro from #21975

interface Foo {
bar: {
baz: string;
} | {
qux: number;
}
}

type ShouldBeString = Foo['bar']['baz'];

interface HasOptionalMember {
bar?: {
baz: string;
}
}

type ShouldBeString2 = HasOptionalMember['bar']['baz'];


//// [indexedAccessPartialUnions.js]
"use strict";
// Repro from #21975
37 changes: 37 additions & 0 deletions tests/baselines/reference/indexedAccessPartialUnions.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
=== 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))

interface HasOptionalMember {
>HasOptionalMember : Symbol(HasOptionalMember, Decl(indexedAccessPartialUnions.ts, 10, 40))

bar?: {
>bar : Symbol(HasOptionalMember.bar, Decl(indexedAccessPartialUnions.ts, 12, 29))

baz: string;
>baz : Symbol(baz, Decl(indexedAccessPartialUnions.ts, 13, 11))
}
}

type ShouldBeString2 = HasOptionalMember['bar']['baz'];
>ShouldBeString2 : Symbol(ShouldBeString2, Decl(indexedAccessPartialUnions.ts, 16, 1))
>HasOptionalMember : Symbol(HasOptionalMember, Decl(indexedAccessPartialUnions.ts, 10, 40))

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

interface HasOptionalMember {
>HasOptionalMember : HasOptionalMember

bar?: {
>bar : { baz: string; } | undefined

baz: string;
>baz : string
}
}

type ShouldBeString2 = HasOptionalMember['bar']['baz'];
>ShouldBeString2 : string
>HasOptionalMember : HasOptionalMember

21 changes: 21 additions & 0 deletions tests/cases/compiler/indexedAccessPartialUnions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// @strict: true

// Repro from #21975

interface Foo {
bar: {
baz: string;
} | {
qux: number;
}
}

type ShouldBeString = Foo['bar']['baz'];

interface HasOptionalMember {
bar?: {
baz: string;
}
}

type ShouldBeString2 = HasOptionalMember['bar']['baz'];