diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 967ad86941b40..3ee4df7035114 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13765,6 +13765,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { undefined; } if (t.flags & TypeFlags.Index) { + if ((t as IndexType).type.flags & TypeFlags.IndexedAccess) { + const indexedAccess = (t as IndexType).type as IndexedAccessType; + // 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; } if (t.flags & TypeFlags.TemplateLiteral) { diff --git a/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.errors.txt b/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.errors.txt new file mode 100644 index 0000000000000..e9dcd2a09e388 --- /dev/null +++ b/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.errors.txt @@ -0,0 +1,33 @@ +keyofAndGenericIndexedAccessNarrowableConstraint.ts(15,15): error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. +keyofAndGenericIndexedAccessNarrowableConstraint.ts(15,20): error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. +keyofAndGenericIndexedAccessNarrowableConstraint.ts(19,16): error TS2731: Implicit conversion of a 'symbol' to a 'string' will fail at runtime. Consider wrapping this expression in 'String(...)'. + + +==== 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 new file mode 100644 index 0000000000000..7b4d4ed489a45 --- /dev/null +++ b/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.symbols @@ -0,0 +1,77 @@ +//// [tests/cases/conformance/types/keyof/keyofAndGenericIndexedAccessNarrowableConstraint.ts] //// + +=== 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, 22)) +>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}` // 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 new file mode 100644 index 0000000000000..1666207ee3eaf --- /dev/null +++ b/tests/baselines/reference/keyofAndGenericIndexedAccessNarrowableConstraint.types @@ -0,0 +1,55 @@ +//// [tests/cases/conformance/types/keyof/keyofAndGenericIndexedAccessNarrowableConstraint.ts] //// + +=== 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}` // 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 new file mode 100644 index 0000000000000..326435dcc79e8 --- /dev/null +++ b/tests/cases/conformance/types/keyof/keyofAndGenericIndexedAccessNarrowableConstraint.ts @@ -0,0 +1,23 @@ +// @noEmit: true +// @strict: true + +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 +} + +export function path4(k: K) { + return `.${k}` // error +}