From 75f6a1fbf85d5c5dd548aa60cf0306f064ef9b29 Mon Sep 17 00:00:00 2001 From: Kevin Verdieck Date: Wed, 21 Feb 2018 12:21:48 -0800 Subject: [PATCH 1/2] Allow partial unions for indexed type access --- src/compiler/checker.ts | 12 ++++-- .../reference/indexedAccessPartialUnions.js | 25 +++++++++++++ .../indexedAccessPartialUnions.symbols | 37 +++++++++++++++++++ .../indexedAccessPartialUnions.types | 37 +++++++++++++++++++ .../compiler/indexedAccessPartialUnions.ts | 21 +++++++++++ 5 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/indexedAccessPartialUnions.js create mode 100644 tests/baselines/reference/indexedAccessPartialUnions.symbols create mode 100644 tests/baselines/reference/indexedAccessPartialUnions.types create mode 100644 tests/cases/compiler/indexedAccessPartialUnions.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2768c142f5aad..bf5cc5adca198 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*/ true); if (prop) { if (accessExpression) { markPropertyAsReferenced(prop, accessExpression, /*isThisAccess*/ accessExpression.expression.kind === SyntaxKind.ThisKeyword); diff --git a/tests/baselines/reference/indexedAccessPartialUnions.js b/tests/baselines/reference/indexedAccessPartialUnions.js new file mode 100644 index 0000000000000..c0e96020d836c --- /dev/null +++ b/tests/baselines/reference/indexedAccessPartialUnions.js @@ -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 diff --git a/tests/baselines/reference/indexedAccessPartialUnions.symbols b/tests/baselines/reference/indexedAccessPartialUnions.symbols new file mode 100644 index 0000000000000..5c11f6431287e --- /dev/null +++ b/tests/baselines/reference/indexedAccessPartialUnions.symbols @@ -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)) + diff --git a/tests/baselines/reference/indexedAccessPartialUnions.types b/tests/baselines/reference/indexedAccessPartialUnions.types new file mode 100644 index 0000000000000..691e469ed2ae0 --- /dev/null +++ b/tests/baselines/reference/indexedAccessPartialUnions.types @@ -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 + diff --git a/tests/cases/compiler/indexedAccessPartialUnions.ts b/tests/cases/compiler/indexedAccessPartialUnions.ts new file mode 100644 index 0000000000000..4c3a58ea1e05f --- /dev/null +++ b/tests/cases/compiler/indexedAccessPartialUnions.ts @@ -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']; From 29312f98d4ab27f55bccba1fbe03e68126eb54a4 Mon Sep 17 00:00:00 2001 From: Kevin Verdieck Date: Tue, 27 Feb 2018 11:05:46 -0800 Subject: [PATCH 2/2] Fix over-permissive indexed access and add test --- src/compiler/checker.ts | 2 +- .../indexedAccessPartialUnions.errors.txt | 39 +++++++++++++++++++ .../reference/indexedAccessPartialUnions.js | 14 +++++++ .../indexedAccessPartialUnions.symbols | 31 ++++++++++++--- .../indexedAccessPartialUnions.types | 26 +++++++++++++ .../compiler/indexedAccessPartialUnions.ts | 8 ++++ 6 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 tests/baselines/reference/indexedAccessPartialUnions.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bf5cc5adca198..562f730a0dfef 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8003,7 +8003,7 @@ namespace ts { getPropertyNameForKnownSymbolName(idText((accessExpression.argumentExpression).name)) : undefined; if (propName !== undefined) { - const prop = getPropertyOfType(objectType, propName, /*allowPartialUnions*/ true); + 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 index c0e96020d836c..2231948825e99 100644 --- a/tests/baselines/reference/indexedAccessPartialUnions.js +++ b/tests/baselines/reference/indexedAccessPartialUnions.js @@ -11,6 +11,14 @@ interface Foo { 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; @@ -23,3 +31,9 @@ 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 index 5c11f6431287e..f037d6976f5a1 100644 --- a/tests/baselines/reference/indexedAccessPartialUnions.symbols +++ b/tests/baselines/reference/indexedAccessPartialUnions.symbols @@ -20,18 +20,39 @@ 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, 10, 40)) +>HasOptionalMember : Symbol(HasOptionalMember, Decl(indexedAccessPartialUnions.ts, 18, 1)) bar?: { ->bar : Symbol(HasOptionalMember.bar, Decl(indexedAccessPartialUnions.ts, 12, 29)) +>bar : Symbol(HasOptionalMember.bar, Decl(indexedAccessPartialUnions.ts, 20, 29)) baz: string; ->baz : Symbol(baz, Decl(indexedAccessPartialUnions.ts, 13, 11)) +>baz : Symbol(baz, Decl(indexedAccessPartialUnions.ts, 21, 11)) } } type ShouldBeString2 = HasOptionalMember['bar']['baz']; ->ShouldBeString2 : Symbol(ShouldBeString2, Decl(indexedAccessPartialUnions.ts, 16, 1)) ->HasOptionalMember : Symbol(HasOptionalMember, Decl(indexedAccessPartialUnions.ts, 10, 40)) +>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 index 691e469ed2ae0..e7f7a9193b84e 100644 --- a/tests/baselines/reference/indexedAccessPartialUnions.types +++ b/tests/baselines/reference/indexedAccessPartialUnions.types @@ -20,6 +20,32 @@ 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 diff --git a/tests/cases/compiler/indexedAccessPartialUnions.ts b/tests/cases/compiler/indexedAccessPartialUnions.ts index 4c3a58ea1e05f..8c7c437fce414 100644 --- a/tests/cases/compiler/indexedAccessPartialUnions.ts +++ b/tests/cases/compiler/indexedAccessPartialUnions.ts @@ -12,6 +12,14 @@ interface Foo { 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;