From 6ba4afd15d03946bf93f67d73432511ec4fc4632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sat, 14 May 2022 10:36:41 +0200 Subject: [PATCH 1/3] Improve computed base constraint for Index type on generic IndexedAccess --- src/compiler/checker.ts | 14 +++++++++ ...cIndexedAccessNarrowableConstraint.symbols | 30 +++++++++++++++++++ ...ricIndexedAccessNarrowableConstraint.types | 24 +++++++++++++++ ...enericIndexedAccessNarrowableConstraint.ts | 11 +++++++ 4 files changed, 79 insertions(+) create mode 100644 tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.symbols create mode 100644 tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.types create mode 100644 tests/cases/conformance/types/keyof/keyofAndGenericIndexedAccessNarrowableConstraint.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ef86d21a4df67..4aae4bdcfe9c3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12181,6 +12181,20 @@ namespace ts { undefined; } if (t.flags & TypeFlags.Index) { + if ((t as IndexType).type.flags & TypeFlags.IndexedAccess) { + const indexedAccess = (t as IndexType).type as IndexedAccessType; + const baseObjectType = getBaseConstraint(indexedAccess.objectType); + const baseIndexType = getBaseConstraint(indexedAccess.indexType); + const indexedAccessType = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType); + const mappedIndexTypeOfIndexedAccess = indexedAccessType && mapType(indexedAccessType, getIndexType); + const narrowed = mappedIndexTypeOfIndexedAccess && mapType(keyofConstraintType, t => { + const intersected = getIntersectionType([t, mappedIndexTypeOfIndexedAccess]); + return intersected.flags & TypeFlags.Never ? undefined : t; + }); + if (narrowed) { + return narrowed; + } + } return keyofConstraintType; } if (t.flags & TypeFlags.TemplateLiteral) { diff --git a/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.symbols b/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.symbols new file mode 100644 index 0000000000000..dcbf342edcdd8 --- /dev/null +++ b/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.symbols @@ -0,0 +1,30 @@ +=== tests/cases/conformance/types/keyof/keyofAndGenericIndexedAccessNarrowableConstraint.ts === +export type Context = { +>Context : Symbol(Context, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 0, 0)) + + V1: { "a": string }, +>V1 : Symbol(V1, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 0, 23)) +>"a" : Symbol("a", Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 1, 9)) + + V2: { "b": string }, +>V2 : Symbol(V2, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 1, 24)) +>"b" : Symbol("b", Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 2, 9)) +} + +export function path(v: V, k: K) { +>path : Symbol(path, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 3, 1)) +>V : Symbol(V, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 5, 21)) +>Context : Symbol(Context, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 0, 0)) +>K : Symbol(K, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 5, 45)) +>Context : Symbol(Context, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 0, 0)) +>V : Symbol(V, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 5, 21)) +>v : Symbol(v, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 5, 74)) +>V : Symbol(V, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 5, 21)) +>k : Symbol(k, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 5, 79)) +>K : Symbol(K, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 5, 45)) + + return `${v}.${k}` +>v : Symbol(v, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 5, 74)) +>k : Symbol(k, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 5, 79)) +} + diff --git a/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.types b/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.types new file mode 100644 index 0000000000000..61de2d8117837 --- /dev/null +++ b/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.types @@ -0,0 +1,24 @@ +=== tests/cases/conformance/types/keyof/keyofAndGenericIndexedAccessNarrowableConstraint.ts === +export type Context = { +>Context : { V1: { "a": string;}; V2: { "b": string;}; } + + V1: { "a": string }, +>V1 : { a: string; } +>"a" : string + + V2: { "b": string }, +>V2 : { b: string; } +>"b" : string +} + +export function path(v: V, k: K) { +>path : (v: V, k: K) => string +>v : V +>k : K + + return `${v}.${k}` +>`${v}.${k}` : string +>v : V +>k : K +} + diff --git a/tests/cases/conformance/types/keyof/keyofAndGenericIndexedAccessNarrowableConstraint.ts b/tests/cases/conformance/types/keyof/keyofAndGenericIndexedAccessNarrowableConstraint.ts new file mode 100644 index 0000000000000..83fe38a29fff8 --- /dev/null +++ b/tests/cases/conformance/types/keyof/keyofAndGenericIndexedAccessNarrowableConstraint.ts @@ -0,0 +1,11 @@ +// @noEmit: true +// @strict: true + +export type Context = { + V1: { "a": string }, + V2: { "b": string }, +} + +export function path(v: V, k: K) { + return `${v}.${k}` +} From c8d16728146e949dac70d75a3f92bb870297fb5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 24 May 2022 14:30:43 +0200 Subject: [PATCH 2/3] Refactor to use `filterType` over `mapType` --- src/compiler/checker.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4aae4bdcfe9c3..8b6b734264617 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12187,9 +12187,8 @@ namespace ts { const baseIndexType = getBaseConstraint(indexedAccess.indexType); const indexedAccessType = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType); const mappedIndexTypeOfIndexedAccess = indexedAccessType && mapType(indexedAccessType, getIndexType); - const narrowed = mappedIndexTypeOfIndexedAccess && mapType(keyofConstraintType, t => { - const intersected = getIntersectionType([t, mappedIndexTypeOfIndexedAccess]); - return intersected.flags & TypeFlags.Never ? undefined : t; + const narrowed = mappedIndexTypeOfIndexedAccess && filterType(keyofConstraintType, t => { + return !(getIntersectionType([t, mappedIndexTypeOfIndexedAccess]).flags & TypeFlags.Never); }); if (narrowed) { return narrowed; From 938cd647073ce463d98a2c08d74a3087273ec22f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 30 Sep 2022 19:02:41 +0200 Subject: [PATCH 3/3] Fixed an issue with allowing too many generic accesses --- src/compiler/checker.ts | 20 ++++--- ...dexedAccessNarrowableConstraint.errors.txt | 33 +++++++++++ ...cIndexedAccessNarrowableConstraint.symbols | 57 +++++++++++++++++-- ...ricIndexedAccessNarrowableConstraint.types | 41 +++++++++++-- ...enericIndexedAccessNarrowableConstraint.ts | 18 +++++- 5 files changed, 145 insertions(+), 24 deletions(-) create mode 100644 tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 45731236226fd..3ca165daeb100 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12372,15 +12372,17 @@ namespace ts { if (t.flags & TypeFlags.Index) { if ((t as IndexType).type.flags & TypeFlags.IndexedAccess) { const indexedAccess = (t as IndexType).type as IndexedAccessType; - const baseObjectType = getBaseConstraint(indexedAccess.objectType); - const baseIndexType = getBaseConstraint(indexedAccess.indexType); - const indexedAccessType = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType); - const mappedIndexTypeOfIndexedAccess = indexedAccessType && mapType(indexedAccessType, getIndexType); - const narrowed = mappedIndexTypeOfIndexedAccess && filterType(keyofConstraintType, t => { - return !(getIntersectionType([t, mappedIndexTypeOfIndexedAccess]).flags & TypeFlags.Never); - }); - if (narrowed) { - return narrowed; + // generic objects can always be instantiated with more keys so we can't narrow down those cases + if (!isGenericObjectType(indexedAccess.objectType)) { + const baseIndexType = getBaseConstraint(indexedAccess.indexType); + const indexedAccessType = baseIndexType && getIndexedAccessTypeOrUndefined(indexedAccess.objectType, baseIndexType); + const mappedIndexTypeOfIndexedAccess = indexedAccessType && mapType(indexedAccessType, getIndexType); + const narrowed = mappedIndexTypeOfIndexedAccess && filterType(keyofConstraintType, t => { + return !(getIntersectionType([t, mappedIndexTypeOfIndexedAccess]).flags & TypeFlags.Never); + }); + if (narrowed) { + return narrowed; + } } } return keyofConstraintType; diff --git a/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.errors.txt b/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.errors.txt new file mode 100644 index 0000000000000..03bb0a2519e0a --- /dev/null +++ b/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.errors.txt @@ -0,0 +1,33 @@ +tests/cases/conformance/types/keyof/keyofAndGenericIndexedAccessNarrowableConstraint.ts(15,15): error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. +tests/cases/conformance/types/keyof/keyofAndGenericIndexedAccessNarrowableConstraint.ts(15,20): error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. +tests/cases/conformance/types/keyof/keyofAndGenericIndexedAccessNarrowableConstraint.ts(19,16): error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. + + +==== tests/cases/conformance/types/keyof/keyofAndGenericIndexedAccessNarrowableConstraint.ts (3 errors) ==== + export type Context = { + V1: { a: string }, + V2: { b: string }, + } + + export function path(v: V, k: K) { + return `${v}.${k}` // ok + } + + export function path2(k: K) { + return `.${k}` // ok + } + + export function path3(v: V, k: K) { + return `${v}.${k}` // error + ~ +!!! error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. + ~ +!!! error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. + } + + export function path4(k: K) { + return `.${k}` // error + ~ +!!! error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. + } + \ No newline at end of file diff --git a/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.symbols b/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.symbols index dcbf342edcdd8..e8cdaa2f477ba 100644 --- a/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.symbols +++ b/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.symbols @@ -2,13 +2,13 @@ export type Context = { >Context : Symbol(Context, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 0, 0)) - V1: { "a": string }, + V1: { a: string }, >V1 : Symbol(V1, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 0, 23)) ->"a" : Symbol("a", Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 1, 9)) +>a : Symbol(a, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 1, 9)) - V2: { "b": string }, ->V2 : Symbol(V2, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 1, 24)) ->"b" : Symbol("b", Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 2, 9)) + V2: { b: string }, +>V2 : Symbol(V2, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 1, 22)) +>b : Symbol(b, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 2, 9)) } export function path(v: V, k: K) { @@ -23,8 +23,53 @@ export function path(v: V, >k : Symbol(k, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 5, 79)) >K : Symbol(K, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 5, 45)) - return `${v}.${k}` + return `${v}.${k}` // ok >v : Symbol(v, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 5, 74)) >k : Symbol(k, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 5, 79)) } +export function path2(k: K) { +>path2 : Symbol(path2, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 7, 1)) +>K : Symbol(K, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 9, 22)) +>Context : Symbol(Context, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 0, 0)) +>Context : Symbol(Context, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 0, 0)) +>k : Symbol(k, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 9, 62)) +>K : Symbol(K, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 9, 22)) + + return `.${k}` // ok +>k : Symbol(k, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 9, 62)) +} + +export function path3(v: V, k: K) { +>path3 : Symbol(path3, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 11, 1)) +>O : Symbol(O, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 13, 22)) +>Context : Symbol(Context, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 0, 0)) +>V : Symbol(V, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 13, 40)) +>O : Symbol(O, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 13, 22)) +>K : Symbol(K, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 13, 59)) +>O : Symbol(O, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 13, 22)) +>V : Symbol(V, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 13, 40)) +>v : Symbol(v, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 13, 82)) +>V : Symbol(V, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 13, 40)) +>k : Symbol(k, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 13, 87)) +>K : Symbol(K, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 13, 59)) + + return `${v}.${k}` // error +>v : Symbol(v, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 13, 82)) +>k : Symbol(k, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 13, 87)) +} + +export function path4(k: K) { +>path4 : Symbol(path4, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 15, 1)) +>O : Symbol(O, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 17, 22)) +>Context : Symbol(Context, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 0, 0)) +>Context : Symbol(Context, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 0, 0)) +>K : Symbol(K, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 17, 55)) +>O : Symbol(O, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 17, 22)) +>k : Symbol(k, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 17, 75)) +>K : Symbol(K, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 17, 55)) + + return `.${k}` // error +>k : Symbol(k, Decl(keyofAndGenericIndexedAccessNarrowableConstraint.ts, 17, 75)) +} + diff --git a/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.types b/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.types index 61de2d8117837..330a36400ba8f 100644 --- a/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.types +++ b/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.types @@ -1,14 +1,14 @@ === tests/cases/conformance/types/keyof/keyofAndGenericIndexedAccessNarrowableConstraint.ts === export type Context = { ->Context : { V1: { "a": string;}; V2: { "b": string;}; } +>Context : { V1: { a: string;}; V2: { b: string;}; } - V1: { "a": string }, + V1: { a: string }, >V1 : { a: string; } ->"a" : string +>a : string - V2: { "b": string }, + V2: { b: string }, >V2 : { b: string; } ->"b" : string +>b : string } export function path(v: V, k: K) { @@ -16,9 +16,38 @@ export function path(v: V, >v : V >k : K - return `${v}.${k}` + return `${v}.${k}` // ok >`${v}.${k}` : string >v : V >k : K } +export function path2(k: K) { +>path2 : (k: K) => string +>k : K + + return `.${k}` // ok +>`.${k}` : string +>k : K +} + +export function path3(v: V, k: K) { +>path3 : (v: V, k: K) => string +>v : V +>k : K + + return `${v}.${k}` // error +>`${v}.${k}` : string +>v : V +>k : K +} + +export function path4(k: K) { +>path4 : (k: K) => string +>k : K + + return `.${k}` // error +>`.${k}` : string +>k : K +} + diff --git a/tests/cases/conformance/types/keyof/keyofAndGenericIndexedAccessNarrowableConstraint.ts b/tests/cases/conformance/types/keyof/keyofAndGenericIndexedAccessNarrowableConstraint.ts index 83fe38a29fff8..326435dcc79e8 100644 --- a/tests/cases/conformance/types/keyof/keyofAndGenericIndexedAccessNarrowableConstraint.ts +++ b/tests/cases/conformance/types/keyof/keyofAndGenericIndexedAccessNarrowableConstraint.ts @@ -2,10 +2,22 @@ // @strict: true export type Context = { - V1: { "a": string }, - V2: { "b": string }, + V1: { a: string }, + V2: { b: string }, } export function path(v: V, k: K) { - return `${v}.${k}` + return `${v}.${k}` // ok +} + +export function path2(k: K) { + return `.${k}` // ok +} + +export function path3(v: V, k: K) { + return `${v}.${k}` // error +} + +export function path4(k: K) { + return `.${k}` // error }