Slightly more conservative check in isConstraintPosition function (#44621)

* Slightly more conservative check in isConstraintPosition function

* Update comment

* Add tests
This commit is contained in:
Anders Hejlsberg
2021-06-16 17:16:32 -07:00
committed by GitHub
parent 97b4063d73
commit d46d82c9bd
6 changed files with 396 additions and 7 deletions

View File

@@ -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;
}

View File

@@ -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<T extends string | undefined>(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<T, K extends keyof T>(obj: T, key: K) {
const x1 = obj[key];
const x2 = obj && obj[key];
}
function fx2<T extends Record<keyof T, string>, K extends keyof T>(obj: T, key: K) {
const x1 = obj[key];
const x2 = obj && obj[key];
}
function fx3<T extends Record<keyof T, string> | 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<keyof InternalSpec, any>,
InternalSpec extends Record<keyof PublicSpec, any> | 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];
}
}

View File

@@ -138,6 +138,42 @@ function once<ET, T extends EventEmitter<ET>>(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<T, K extends keyof T>(obj: T, key: K) {
const x1 = obj[key];
const x2 = obj && obj[key];
}
function fx2<T extends Record<keyof T, string>, K extends keyof T>(obj: T, key: K) {
const x1 = obj[key];
const x2 = obj && obj[key];
}
function fx3<T extends Record<keyof T, string> | 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<keyof InternalSpec, any>,
InternalSpec extends Record<keyof PublicSpec, any> | 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;
}());

View File

@@ -405,3 +405,123 @@ function once<ET, T extends EventEmitter<ET>>(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<T, K extends keyof T>(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<T extends Record<keyof T, string>, 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<T extends Record<keyof T, string> | 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<keyof InternalSpec, any>,
>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<keyof PublicSpec, any> | 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))
}
}

View File

@@ -388,3 +388,120 @@ function once<ET, T extends EventEmitter<ET>>(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<T, K extends keyof T>(obj: T, key: K) {
>fx1 : <T, 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 fx2<T extends Record<keyof T, string>, K extends keyof T>(obj: T, key: K) {
>fx2 : <T extends Record<keyof T, string>, 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<T extends Record<keyof T, string> | undefined, K extends keyof T>(obj: T, key: K) {
>fx3 : <T extends Record<keyof T, string> | undefined, K extends keyof T>(obj: T, key: K) => void
>obj : T
>key : K
const x1 = obj[key]; // Error
>x1 : NonNullable<Record<keyof T, string>>[K]
>obj[key] : NonNullable<Record<keyof T, string>>[K]
>obj : Record<keyof T, string> | undefined
>key : K
const x2 = obj && obj[key];
>x2 : Record<keyof T, string>[K]
>obj && obj[key] : Record<keyof T, string>[K]
>obj : T
>obj[key] : Record<keyof T, string>[K]
>obj : Record<keyof T, string>
>key : K
}
// Repro from #44166
class TableBaseEnum<
>TableBaseEnum : TableBaseEnum<PublicSpec, InternalSpec>
PublicSpec extends Record<keyof InternalSpec, any>,
InternalSpec extends Record<keyof PublicSpec, any> | 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<Record<keyof PublicSpec, any>>[keyof InternalSpec]
>iSpec : Record<keyof PublicSpec, any> | 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<Record<keyof PublicSpec, any>>[keyof PublicSpec]
>iSpec : Record<keyof PublicSpec, any> | 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 PublicSpec, any>[keyof InternalSpec]
>iSpec : Record<keyof PublicSpec, any>
>null! as keyof InternalSpec : keyof InternalSpec
>null! : never
>null : null
iSpec[null! as keyof PublicSpec];
>iSpec[null! as keyof PublicSpec] : Record<keyof PublicSpec, any>[keyof PublicSpec]
>iSpec : Record<keyof PublicSpec, any>
>null! as keyof PublicSpec : keyof PublicSpec
>null! : never
>null : null
}
}

View File

@@ -139,3 +139,39 @@ function once<ET, T extends EventEmitter<ET>>(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<T, K extends keyof T>(obj: T, key: K) {
const x1 = obj[key];
const x2 = obj && obj[key];
}
function fx2<T extends Record<keyof T, string>, K extends keyof T>(obj: T, key: K) {
const x1 = obj[key];
const x2 = obj && obj[key];
}
function fx3<T extends Record<keyof T, string> | 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<keyof InternalSpec, any>,
InternalSpec extends Record<keyof PublicSpec, any> | 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];
}
}