diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e763d5703fc..067b0e00030 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -24140,21 +24140,25 @@ namespace ts { } } - function isConstraintPosition(node: Node) { + function isConstraintPosition(type: Type, node: Node) { const parent = node.parent; - // In an element access obj[x], we consider obj to be in a constraint position only when x is not - // of a generic type. This is because when both obj and x are of generic types T and K, we want - // the resulting type to be T[K]. + // In an element access obj[x], we consider obj to be in a constraint position, except when obj is of + // a generic type without a nullable constraint and x is a generic type. This is because when both obj + // and x are of generic types T and K, we want the resulting type to be T[K]. return parent.kind === SyntaxKind.PropertyAccessExpression || parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === node || parent.kind === SyntaxKind.ElementAccessExpression && (parent as ElementAccessExpression).expression === node && - !isGenericIndexType(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression)); + !(isGenericTypeWithoutNullableConstraint(type) && isGenericIndexType(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression))); } function isGenericTypeWithUnionConstraint(type: Type) { return !!(type.flags & TypeFlags.Instantiable && getBaseConstraintOrType(type).flags & (TypeFlags.Nullable | TypeFlags.Union)); } + function isGenericTypeWithoutNullableConstraint(type: Type) { + return !!(type.flags & TypeFlags.Instantiable && !maybeTypeOfKind(getBaseConstraintOrType(type), TypeFlags.Nullable)); + } + function containsGenericType(type: Type): boolean { return !!(type.flags & TypeFlags.Instantiable || type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, containsGenericType)); } @@ -24178,7 +24182,7 @@ namespace ts { // 'string | undefined' to give control flow analysis the opportunity to narrow to type 'string'. const substituteConstraints = !(checkMode && checkMode & CheckMode.Inferential) && someType(type, isGenericTypeWithUnionConstraint) && - (isConstraintPosition(reference) || hasNonBindingPatternContextualTypeWithNoGenericTypes(reference)); + (isConstraintPosition(type, reference) || hasNonBindingPatternContextualTypeWithNoGenericTypes(reference)); return substituteConstraints ? mapType(type, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOrType(t) : t) : type; } diff --git a/tests/baselines/reference/controlFlowGenericTypes.errors.txt b/tests/baselines/reference/controlFlowGenericTypes.errors.txt index 36d9e896317..d6bd2aba7be 100644 --- a/tests/baselines/reference/controlFlowGenericTypes.errors.txt +++ b/tests/baselines/reference/controlFlowGenericTypes.errors.txt @@ -5,9 +5,12 @@ tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(81,11): error TS2 tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(90,44): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(91,11): error TS2339: Property 'foo' does not exist on type 'MyUnion'. Property 'foo' does not exist on type 'AA'. +tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(156,16): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(167,9): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(168,9): error TS2532: Object is possibly 'undefined'. -==== tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts (5 errors) ==== +==== tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts (8 errors) ==== function f1(x: T, y: { a: T }, z: [T]): string { if (x) { x; @@ -159,4 +162,46 @@ tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(91,11): error TS2 emittingObject.off(eventName, 0); emittingObject.off(eventName as typeof eventName, 0); } + + // In an element access obj[x], we consider obj to be in a constraint position, except when obj is of + // a generic type without a nullable constraint and x is a generic type. This is because when both obj + // and x are of generic types T and K, we want the resulting type to be T[K]. + + function fx1(obj: T, key: K) { + const x1 = obj[key]; + const x2 = obj && obj[key]; + } + + function fx2, K extends keyof T>(obj: T, key: K) { + const x1 = obj[key]; + const x2 = obj && obj[key]; + } + + function fx3 | undefined, K extends keyof T>(obj: T, key: K) { + const x1 = obj[key]; // Error + ~~~ +!!! error TS2532: Object is possibly 'undefined'. + const x2 = obj && obj[key]; + } + + // Repro from #44166 + + class TableBaseEnum< + PublicSpec extends Record, + InternalSpec extends Record | undefined = undefined> { + m() { + let iSpec = null! as InternalSpec; + iSpec[null! as keyof InternalSpec]; // Error, object possibly undefined + ~~~~~ +!!! error TS2532: Object is possibly 'undefined'. + iSpec[null! as keyof PublicSpec]; // Error, object possibly undefined + ~~~~~ +!!! error TS2532: Object is possibly 'undefined'. + if (iSpec === undefined) { + return; + } + iSpec[null! as keyof InternalSpec]; + iSpec[null! as keyof PublicSpec]; + } + } \ No newline at end of file diff --git a/tests/baselines/reference/controlFlowGenericTypes.js b/tests/baselines/reference/controlFlowGenericTypes.js index 17b8e0dd4e0..d6acd179b69 100644 --- a/tests/baselines/reference/controlFlowGenericTypes.js +++ b/tests/baselines/reference/controlFlowGenericTypes.js @@ -138,6 +138,42 @@ function once>(emittingObject: T, eventName: keyo emittingObject.off(eventName, 0); emittingObject.off(eventName as typeof eventName, 0); } + +// In an element access obj[x], we consider obj to be in a constraint position, except when obj is of +// a generic type without a nullable constraint and x is a generic type. This is because when both obj +// and x are of generic types T and K, we want the resulting type to be T[K]. + +function fx1(obj: T, key: K) { + const x1 = obj[key]; + const x2 = obj && obj[key]; +} + +function fx2, K extends keyof T>(obj: T, key: K) { + const x1 = obj[key]; + const x2 = obj && obj[key]; +} + +function fx3 | undefined, K extends keyof T>(obj: T, key: K) { + const x1 = obj[key]; // Error + const x2 = obj && obj[key]; +} + +// Repro from #44166 + +class TableBaseEnum< + PublicSpec extends Record, + InternalSpec extends Record | undefined = undefined> { + m() { + let iSpec = null! as InternalSpec; + iSpec[null! as keyof InternalSpec]; // Error, object possibly undefined + iSpec[null! as keyof PublicSpec]; // Error, object possibly undefined + if (iSpec === undefined) { + return; + } + iSpec[null! as keyof InternalSpec]; + iSpec[null! as keyof PublicSpec]; + } +} //// [controlFlowGenericTypes.js] @@ -246,3 +282,34 @@ function once(emittingObject, eventName) { emittingObject.off(eventName, 0); emittingObject.off(eventName, 0); } +// In an element access obj[x], we consider obj to be in a constraint position, except when obj is of +// a generic type without a nullable constraint and x is a generic type. This is because when both obj +// and x are of generic types T and K, we want the resulting type to be T[K]. +function fx1(obj, key) { + var x1 = obj[key]; + var x2 = obj && obj[key]; +} +function fx2(obj, key) { + var x1 = obj[key]; + var x2 = obj && obj[key]; +} +function fx3(obj, key) { + var x1 = obj[key]; // Error + var x2 = obj && obj[key]; +} +// Repro from #44166 +var TableBaseEnum = /** @class */ (function () { + function TableBaseEnum() { + } + TableBaseEnum.prototype.m = function () { + var iSpec = null; + iSpec[null]; // Error, object possibly undefined + iSpec[null]; // Error, object possibly undefined + if (iSpec === undefined) { + return; + } + iSpec[null]; + iSpec[null]; + }; + return TableBaseEnum; +}()); diff --git a/tests/baselines/reference/controlFlowGenericTypes.symbols b/tests/baselines/reference/controlFlowGenericTypes.symbols index 61597dfdf9d..0ea62c53221 100644 --- a/tests/baselines/reference/controlFlowGenericTypes.symbols +++ b/tests/baselines/reference/controlFlowGenericTypes.symbols @@ -405,3 +405,123 @@ function once>(emittingObject: T, eventName: keyo >eventName : Symbol(eventName, Decl(controlFlowGenericTypes.ts, 135, 64)) } +// In an element access obj[x], we consider obj to be in a constraint position, except when obj is of +// a generic type without a nullable constraint and x is a generic type. This is because when both obj +// and x are of generic types T and K, we want the resulting type to be T[K]. + +function fx1(obj: T, key: K) { +>fx1 : Symbol(fx1, Decl(controlFlowGenericTypes.ts, 138, 1)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 144, 13)) +>K : Symbol(K, Decl(controlFlowGenericTypes.ts, 144, 15)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 144, 13)) +>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 144, 35)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 144, 13)) +>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 144, 42)) +>K : Symbol(K, Decl(controlFlowGenericTypes.ts, 144, 15)) + + const x1 = obj[key]; +>x1 : Symbol(x1, Decl(controlFlowGenericTypes.ts, 145, 9)) +>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 144, 35)) +>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 144, 42)) + + const x2 = obj && obj[key]; +>x2 : Symbol(x2, Decl(controlFlowGenericTypes.ts, 146, 9)) +>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 144, 35)) +>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 144, 35)) +>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 144, 42)) +} + +function fx2, K extends keyof T>(obj: T, key: K) { +>fx2 : Symbol(fx2, Decl(controlFlowGenericTypes.ts, 147, 1)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 149, 13)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 149, 13)) +>K : Symbol(K, Decl(controlFlowGenericTypes.ts, 149, 47)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 149, 13)) +>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 149, 67)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 149, 13)) +>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 149, 74)) +>K : Symbol(K, Decl(controlFlowGenericTypes.ts, 149, 47)) + + const x1 = obj[key]; +>x1 : Symbol(x1, Decl(controlFlowGenericTypes.ts, 150, 9)) +>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 149, 67)) +>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 149, 74)) + + const x2 = obj && obj[key]; +>x2 : Symbol(x2, Decl(controlFlowGenericTypes.ts, 151, 9)) +>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 149, 67)) +>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 149, 67)) +>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 149, 74)) +} + +function fx3 | undefined, K extends keyof T>(obj: T, key: K) { +>fx3 : Symbol(fx3, Decl(controlFlowGenericTypes.ts, 152, 1)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 154, 13)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 154, 13)) +>K : Symbol(K, Decl(controlFlowGenericTypes.ts, 154, 59)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 154, 13)) +>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 154, 79)) +>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 154, 13)) +>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 154, 86)) +>K : Symbol(K, Decl(controlFlowGenericTypes.ts, 154, 59)) + + const x1 = obj[key]; // Error +>x1 : Symbol(x1, Decl(controlFlowGenericTypes.ts, 155, 9)) +>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 154, 79)) +>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 154, 86)) + + const x2 = obj && obj[key]; +>x2 : Symbol(x2, Decl(controlFlowGenericTypes.ts, 156, 9)) +>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 154, 79)) +>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 154, 79)) +>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 154, 86)) +} + +// Repro from #44166 + +class TableBaseEnum< +>TableBaseEnum : Symbol(TableBaseEnum, Decl(controlFlowGenericTypes.ts, 157, 1)) + + PublicSpec extends Record, +>PublicSpec : Symbol(PublicSpec, Decl(controlFlowGenericTypes.ts, 161, 20)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>InternalSpec : Symbol(InternalSpec, Decl(controlFlowGenericTypes.ts, 162, 55)) + + InternalSpec extends Record | undefined = undefined> { +>InternalSpec : Symbol(InternalSpec, Decl(controlFlowGenericTypes.ts, 162, 55)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>PublicSpec : Symbol(PublicSpec, Decl(controlFlowGenericTypes.ts, 161, 20)) + + m() { +>m : Symbol(TableBaseEnum.m, Decl(controlFlowGenericTypes.ts, 163, 82)) + + let iSpec = null! as InternalSpec; +>iSpec : Symbol(iSpec, Decl(controlFlowGenericTypes.ts, 165, 11)) +>InternalSpec : Symbol(InternalSpec, Decl(controlFlowGenericTypes.ts, 162, 55)) + + iSpec[null! as keyof InternalSpec]; // Error, object possibly undefined +>iSpec : Symbol(iSpec, Decl(controlFlowGenericTypes.ts, 165, 11)) +>InternalSpec : Symbol(InternalSpec, Decl(controlFlowGenericTypes.ts, 162, 55)) + + iSpec[null! as keyof PublicSpec]; // Error, object possibly undefined +>iSpec : Symbol(iSpec, Decl(controlFlowGenericTypes.ts, 165, 11)) +>PublicSpec : Symbol(PublicSpec, Decl(controlFlowGenericTypes.ts, 161, 20)) + + if (iSpec === undefined) { +>iSpec : Symbol(iSpec, Decl(controlFlowGenericTypes.ts, 165, 11)) +>undefined : Symbol(undefined) + + return; + } + iSpec[null! as keyof InternalSpec]; +>iSpec : Symbol(iSpec, Decl(controlFlowGenericTypes.ts, 165, 11)) +>InternalSpec : Symbol(InternalSpec, Decl(controlFlowGenericTypes.ts, 162, 55)) + + iSpec[null! as keyof PublicSpec]; +>iSpec : Symbol(iSpec, Decl(controlFlowGenericTypes.ts, 165, 11)) +>PublicSpec : Symbol(PublicSpec, Decl(controlFlowGenericTypes.ts, 161, 20)) + } +} + diff --git a/tests/baselines/reference/controlFlowGenericTypes.types b/tests/baselines/reference/controlFlowGenericTypes.types index 8c62c6dfa12..69b5854360d 100644 --- a/tests/baselines/reference/controlFlowGenericTypes.types +++ b/tests/baselines/reference/controlFlowGenericTypes.types @@ -388,3 +388,120 @@ function once>(emittingObject: T, eventName: keyo >0 : 0 } +// In an element access obj[x], we consider obj to be in a constraint position, except when obj is of +// a generic type without a nullable constraint and x is a generic type. This is because when both obj +// and x are of generic types T and K, we want the resulting type to be T[K]. + +function fx1(obj: T, key: K) { +>fx1 : (obj: T, key: K) => void +>obj : T +>key : K + + const x1 = obj[key]; +>x1 : T[K] +>obj[key] : T[K] +>obj : T +>key : K + + const x2 = obj && obj[key]; +>x2 : T[K] +>obj && obj[key] : T[K] +>obj : T +>obj[key] : T[K] +>obj : T +>key : K +} + +function fx2, K extends keyof T>(obj: T, key: K) { +>fx2 : , K extends keyof T>(obj: T, key: K) => void +>obj : T +>key : K + + const x1 = obj[key]; +>x1 : T[K] +>obj[key] : T[K] +>obj : T +>key : K + + const x2 = obj && obj[key]; +>x2 : T[K] +>obj && obj[key] : T[K] +>obj : T +>obj[key] : T[K] +>obj : T +>key : K +} + +function fx3 | undefined, K extends keyof T>(obj: T, key: K) { +>fx3 : | undefined, K extends keyof T>(obj: T, key: K) => void +>obj : T +>key : K + + const x1 = obj[key]; // Error +>x1 : NonNullable>[K] +>obj[key] : NonNullable>[K] +>obj : Record | undefined +>key : K + + const x2 = obj && obj[key]; +>x2 : Record[K] +>obj && obj[key] : Record[K] +>obj : T +>obj[key] : Record[K] +>obj : Record +>key : K +} + +// Repro from #44166 + +class TableBaseEnum< +>TableBaseEnum : TableBaseEnum + + PublicSpec extends Record, + InternalSpec extends Record | undefined = undefined> { + m() { +>m : () => void + + let iSpec = null! as InternalSpec; +>iSpec : InternalSpec +>null! as InternalSpec : InternalSpec +>null! : never +>null : null + + iSpec[null! as keyof InternalSpec]; // Error, object possibly undefined +>iSpec[null! as keyof InternalSpec] : NonNullable>[keyof InternalSpec] +>iSpec : Record | undefined +>null! as keyof InternalSpec : keyof InternalSpec +>null! : never +>null : null + + iSpec[null! as keyof PublicSpec]; // Error, object possibly undefined +>iSpec[null! as keyof PublicSpec] : NonNullable>[keyof PublicSpec] +>iSpec : Record | undefined +>null! as keyof PublicSpec : keyof PublicSpec +>null! : never +>null : null + + if (iSpec === undefined) { +>iSpec === undefined : boolean +>iSpec : InternalSpec +>undefined : undefined + + return; + } + iSpec[null! as keyof InternalSpec]; +>iSpec[null! as keyof InternalSpec] : Record[keyof InternalSpec] +>iSpec : Record +>null! as keyof InternalSpec : keyof InternalSpec +>null! : never +>null : null + + iSpec[null! as keyof PublicSpec]; +>iSpec[null! as keyof PublicSpec] : Record[keyof PublicSpec] +>iSpec : Record +>null! as keyof PublicSpec : keyof PublicSpec +>null! : never +>null : null + } +} + diff --git a/tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts b/tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts index 21dbde8c33c..7c38ee8c671 100644 --- a/tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts +++ b/tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts @@ -139,3 +139,39 @@ function once>(emittingObject: T, eventName: keyo emittingObject.off(eventName, 0); emittingObject.off(eventName as typeof eventName, 0); } + +// In an element access obj[x], we consider obj to be in a constraint position, except when obj is of +// a generic type without a nullable constraint and x is a generic type. This is because when both obj +// and x are of generic types T and K, we want the resulting type to be T[K]. + +function fx1(obj: T, key: K) { + const x1 = obj[key]; + const x2 = obj && obj[key]; +} + +function fx2, K extends keyof T>(obj: T, key: K) { + const x1 = obj[key]; + const x2 = obj && obj[key]; +} + +function fx3 | undefined, K extends keyof T>(obj: T, key: K) { + const x1 = obj[key]; // Error + const x2 = obj && obj[key]; +} + +// Repro from #44166 + +class TableBaseEnum< + PublicSpec extends Record, + InternalSpec extends Record | undefined = undefined> { + m() { + let iSpec = null! as InternalSpec; + iSpec[null! as keyof InternalSpec]; // Error, object possibly undefined + iSpec[null! as keyof PublicSpec]; // Error, object possibly undefined + if (iSpec === undefined) { + return; + } + iSpec[null! as keyof InternalSpec]; + iSpec[null! as keyof PublicSpec]; + } +}