Skip to content

Commit d46d82c

Browse files
authored
Slightly more conservative check in isConstraintPosition function (#44621)
* Slightly more conservative check in isConstraintPosition function * Update comment * Add tests
1 parent 97b4063 commit d46d82c

File tree

6 files changed

+396
-7
lines changed

6 files changed

+396
-7
lines changed

src/compiler/checker.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24140,21 +24140,25 @@ namespace ts {
2414024140
}
2414124141
}
2414224142

24143-
function isConstraintPosition(node: Node) {
24143+
function isConstraintPosition(type: Type, node: Node) {
2414424144
const parent = node.parent;
24145-
// In an element access obj[x], we consider obj to be in a constraint position only when x is not
24146-
// of a generic type. This is because when both obj and x are of generic types T and K, we want
24147-
// the resulting type to be T[K].
24145+
// In an element access obj[x], we consider obj to be in a constraint position, except when obj is of
24146+
// a generic type without a nullable constraint and x is a generic type. This is because when both obj
24147+
// and x are of generic types T and K, we want the resulting type to be T[K].
2414824148
return parent.kind === SyntaxKind.PropertyAccessExpression ||
2414924149
parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === node ||
2415024150
parent.kind === SyntaxKind.ElementAccessExpression && (parent as ElementAccessExpression).expression === node &&
24151-
!isGenericIndexType(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression));
24151+
!(isGenericTypeWithoutNullableConstraint(type) && isGenericIndexType(getTypeOfExpression((parent as ElementAccessExpression).argumentExpression)));
2415224152
}
2415324153

2415424154
function isGenericTypeWithUnionConstraint(type: Type) {
2415524155
return !!(type.flags & TypeFlags.Instantiable && getBaseConstraintOrType(type).flags & (TypeFlags.Nullable | TypeFlags.Union));
2415624156
}
2415724157

24158+
function isGenericTypeWithoutNullableConstraint(type: Type) {
24159+
return !!(type.flags & TypeFlags.Instantiable && !maybeTypeOfKind(getBaseConstraintOrType(type), TypeFlags.Nullable));
24160+
}
24161+
2415824162
function containsGenericType(type: Type): boolean {
2415924163
return !!(type.flags & TypeFlags.Instantiable || type.flags & TypeFlags.UnionOrIntersection && some((type as UnionOrIntersectionType).types, containsGenericType));
2416024164
}
@@ -24178,7 +24182,7 @@ namespace ts {
2417824182
// 'string | undefined' to give control flow analysis the opportunity to narrow to type 'string'.
2417924183
const substituteConstraints = !(checkMode && checkMode & CheckMode.Inferential) &&
2418024184
someType(type, isGenericTypeWithUnionConstraint) &&
24181-
(isConstraintPosition(reference) || hasNonBindingPatternContextualTypeWithNoGenericTypes(reference));
24185+
(isConstraintPosition(type, reference) || hasNonBindingPatternContextualTypeWithNoGenericTypes(reference));
2418224186
return substituteConstraints ? mapType(type, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOrType(t) : t) : type;
2418324187
}
2418424188

tests/baselines/reference/controlFlowGenericTypes.errors.txt

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(81,11): error TS2
55
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(90,44): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
66
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(91,11): error TS2339: Property 'foo' does not exist on type 'MyUnion'.
77
Property 'foo' does not exist on type 'AA'.
8+
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(156,16): error TS2532: Object is possibly 'undefined'.
9+
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(167,9): error TS2532: Object is possibly 'undefined'.
10+
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(168,9): error TS2532: Object is possibly 'undefined'.
811

912

10-
==== tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts (5 errors) ====
13+
==== tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts (8 errors) ====
1114
function f1<T extends string | undefined>(x: T, y: { a: T }, z: [T]): string {
1215
if (x) {
1316
x;
@@ -159,4 +162,46 @@ tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(91,11): error TS2
159162
emittingObject.off(eventName, 0);
160163
emittingObject.off(eventName as typeof eventName, 0);
161164
}
165+
166+
// In an element access obj[x], we consider obj to be in a constraint position, except when obj is of
167+
// a generic type without a nullable constraint and x is a generic type. This is because when both obj
168+
// and x are of generic types T and K, we want the resulting type to be T[K].
169+
170+
function fx1<T, K extends keyof T>(obj: T, key: K) {
171+
const x1 = obj[key];
172+
const x2 = obj && obj[key];
173+
}
174+
175+
function fx2<T extends Record<keyof T, string>, K extends keyof T>(obj: T, key: K) {
176+
const x1 = obj[key];
177+
const x2 = obj && obj[key];
178+
}
179+
180+
function fx3<T extends Record<keyof T, string> | undefined, K extends keyof T>(obj: T, key: K) {
181+
const x1 = obj[key]; // Error
182+
~~~
183+
!!! error TS2532: Object is possibly 'undefined'.
184+
const x2 = obj && obj[key];
185+
}
186+
187+
// Repro from #44166
188+
189+
class TableBaseEnum<
190+
PublicSpec extends Record<keyof InternalSpec, any>,
191+
InternalSpec extends Record<keyof PublicSpec, any> | undefined = undefined> {
192+
m() {
193+
let iSpec = null! as InternalSpec;
194+
iSpec[null! as keyof InternalSpec]; // Error, object possibly undefined
195+
~~~~~
196+
!!! error TS2532: Object is possibly 'undefined'.
197+
iSpec[null! as keyof PublicSpec]; // Error, object possibly undefined
198+
~~~~~
199+
!!! error TS2532: Object is possibly 'undefined'.
200+
if (iSpec === undefined) {
201+
return;
202+
}
203+
iSpec[null! as keyof InternalSpec];
204+
iSpec[null! as keyof PublicSpec];
205+
}
206+
}
162207

tests/baselines/reference/controlFlowGenericTypes.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,42 @@ function once<ET, T extends EventEmitter<ET>>(emittingObject: T, eventName: keyo
138138
emittingObject.off(eventName, 0);
139139
emittingObject.off(eventName as typeof eventName, 0);
140140
}
141+
142+
// In an element access obj[x], we consider obj to be in a constraint position, except when obj is of
143+
// a generic type without a nullable constraint and x is a generic type. This is because when both obj
144+
// and x are of generic types T and K, we want the resulting type to be T[K].
145+
146+
function fx1<T, K extends keyof T>(obj: T, key: K) {
147+
const x1 = obj[key];
148+
const x2 = obj && obj[key];
149+
}
150+
151+
function fx2<T extends Record<keyof T, string>, K extends keyof T>(obj: T, key: K) {
152+
const x1 = obj[key];
153+
const x2 = obj && obj[key];
154+
}
155+
156+
function fx3<T extends Record<keyof T, string> | undefined, K extends keyof T>(obj: T, key: K) {
157+
const x1 = obj[key]; // Error
158+
const x2 = obj && obj[key];
159+
}
160+
161+
// Repro from #44166
162+
163+
class TableBaseEnum<
164+
PublicSpec extends Record<keyof InternalSpec, any>,
165+
InternalSpec extends Record<keyof PublicSpec, any> | undefined = undefined> {
166+
m() {
167+
let iSpec = null! as InternalSpec;
168+
iSpec[null! as keyof InternalSpec]; // Error, object possibly undefined
169+
iSpec[null! as keyof PublicSpec]; // Error, object possibly undefined
170+
if (iSpec === undefined) {
171+
return;
172+
}
173+
iSpec[null! as keyof InternalSpec];
174+
iSpec[null! as keyof PublicSpec];
175+
}
176+
}
141177

142178

143179
//// [controlFlowGenericTypes.js]
@@ -246,3 +282,34 @@ function once(emittingObject, eventName) {
246282
emittingObject.off(eventName, 0);
247283
emittingObject.off(eventName, 0);
248284
}
285+
// In an element access obj[x], we consider obj to be in a constraint position, except when obj is of
286+
// a generic type without a nullable constraint and x is a generic type. This is because when both obj
287+
// and x are of generic types T and K, we want the resulting type to be T[K].
288+
function fx1(obj, key) {
289+
var x1 = obj[key];
290+
var x2 = obj && obj[key];
291+
}
292+
function fx2(obj, key) {
293+
var x1 = obj[key];
294+
var x2 = obj && obj[key];
295+
}
296+
function fx3(obj, key) {
297+
var x1 = obj[key]; // Error
298+
var x2 = obj && obj[key];
299+
}
300+
// Repro from #44166
301+
var TableBaseEnum = /** @class */ (function () {
302+
function TableBaseEnum() {
303+
}
304+
TableBaseEnum.prototype.m = function () {
305+
var iSpec = null;
306+
iSpec[null]; // Error, object possibly undefined
307+
iSpec[null]; // Error, object possibly undefined
308+
if (iSpec === undefined) {
309+
return;
310+
}
311+
iSpec[null];
312+
iSpec[null];
313+
};
314+
return TableBaseEnum;
315+
}());

tests/baselines/reference/controlFlowGenericTypes.symbols

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,3 +405,123 @@ function once<ET, T extends EventEmitter<ET>>(emittingObject: T, eventName: keyo
405405
>eventName : Symbol(eventName, Decl(controlFlowGenericTypes.ts, 135, 64))
406406
}
407407

408+
// In an element access obj[x], we consider obj to be in a constraint position, except when obj is of
409+
// a generic type without a nullable constraint and x is a generic type. This is because when both obj
410+
// and x are of generic types T and K, we want the resulting type to be T[K].
411+
412+
function fx1<T, K extends keyof T>(obj: T, key: K) {
413+
>fx1 : Symbol(fx1, Decl(controlFlowGenericTypes.ts, 138, 1))
414+
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 144, 13))
415+
>K : Symbol(K, Decl(controlFlowGenericTypes.ts, 144, 15))
416+
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 144, 13))
417+
>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 144, 35))
418+
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 144, 13))
419+
>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 144, 42))
420+
>K : Symbol(K, Decl(controlFlowGenericTypes.ts, 144, 15))
421+
422+
const x1 = obj[key];
423+
>x1 : Symbol(x1, Decl(controlFlowGenericTypes.ts, 145, 9))
424+
>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 144, 35))
425+
>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 144, 42))
426+
427+
const x2 = obj && obj[key];
428+
>x2 : Symbol(x2, Decl(controlFlowGenericTypes.ts, 146, 9))
429+
>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 144, 35))
430+
>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 144, 35))
431+
>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 144, 42))
432+
}
433+
434+
function fx2<T extends Record<keyof T, string>, K extends keyof T>(obj: T, key: K) {
435+
>fx2 : Symbol(fx2, Decl(controlFlowGenericTypes.ts, 147, 1))
436+
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 149, 13))
437+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
438+
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 149, 13))
439+
>K : Symbol(K, Decl(controlFlowGenericTypes.ts, 149, 47))
440+
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 149, 13))
441+
>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 149, 67))
442+
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 149, 13))
443+
>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 149, 74))
444+
>K : Symbol(K, Decl(controlFlowGenericTypes.ts, 149, 47))
445+
446+
const x1 = obj[key];
447+
>x1 : Symbol(x1, Decl(controlFlowGenericTypes.ts, 150, 9))
448+
>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 149, 67))
449+
>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 149, 74))
450+
451+
const x2 = obj && obj[key];
452+
>x2 : Symbol(x2, Decl(controlFlowGenericTypes.ts, 151, 9))
453+
>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 149, 67))
454+
>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 149, 67))
455+
>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 149, 74))
456+
}
457+
458+
function fx3<T extends Record<keyof T, string> | undefined, K extends keyof T>(obj: T, key: K) {
459+
>fx3 : Symbol(fx3, Decl(controlFlowGenericTypes.ts, 152, 1))
460+
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 154, 13))
461+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
462+
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 154, 13))
463+
>K : Symbol(K, Decl(controlFlowGenericTypes.ts, 154, 59))
464+
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 154, 13))
465+
>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 154, 79))
466+
>T : Symbol(T, Decl(controlFlowGenericTypes.ts, 154, 13))
467+
>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 154, 86))
468+
>K : Symbol(K, Decl(controlFlowGenericTypes.ts, 154, 59))
469+
470+
const x1 = obj[key]; // Error
471+
>x1 : Symbol(x1, Decl(controlFlowGenericTypes.ts, 155, 9))
472+
>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 154, 79))
473+
>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 154, 86))
474+
475+
const x2 = obj && obj[key];
476+
>x2 : Symbol(x2, Decl(controlFlowGenericTypes.ts, 156, 9))
477+
>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 154, 79))
478+
>obj : Symbol(obj, Decl(controlFlowGenericTypes.ts, 154, 79))
479+
>key : Symbol(key, Decl(controlFlowGenericTypes.ts, 154, 86))
480+
}
481+
482+
// Repro from #44166
483+
484+
class TableBaseEnum<
485+
>TableBaseEnum : Symbol(TableBaseEnum, Decl(controlFlowGenericTypes.ts, 157, 1))
486+
487+
PublicSpec extends Record<keyof InternalSpec, any>,
488+
>PublicSpec : Symbol(PublicSpec, Decl(controlFlowGenericTypes.ts, 161, 20))
489+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
490+
>InternalSpec : Symbol(InternalSpec, Decl(controlFlowGenericTypes.ts, 162, 55))
491+
492+
InternalSpec extends Record<keyof PublicSpec, any> | undefined = undefined> {
493+
>InternalSpec : Symbol(InternalSpec, Decl(controlFlowGenericTypes.ts, 162, 55))
494+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
495+
>PublicSpec : Symbol(PublicSpec, Decl(controlFlowGenericTypes.ts, 161, 20))
496+
497+
m() {
498+
>m : Symbol(TableBaseEnum.m, Decl(controlFlowGenericTypes.ts, 163, 82))
499+
500+
let iSpec = null! as InternalSpec;
501+
>iSpec : Symbol(iSpec, Decl(controlFlowGenericTypes.ts, 165, 11))
502+
>InternalSpec : Symbol(InternalSpec, Decl(controlFlowGenericTypes.ts, 162, 55))
503+
504+
iSpec[null! as keyof InternalSpec]; // Error, object possibly undefined
505+
>iSpec : Symbol(iSpec, Decl(controlFlowGenericTypes.ts, 165, 11))
506+
>InternalSpec : Symbol(InternalSpec, Decl(controlFlowGenericTypes.ts, 162, 55))
507+
508+
iSpec[null! as keyof PublicSpec]; // Error, object possibly undefined
509+
>iSpec : Symbol(iSpec, Decl(controlFlowGenericTypes.ts, 165, 11))
510+
>PublicSpec : Symbol(PublicSpec, Decl(controlFlowGenericTypes.ts, 161, 20))
511+
512+
if (iSpec === undefined) {
513+
>iSpec : Symbol(iSpec, Decl(controlFlowGenericTypes.ts, 165, 11))
514+
>undefined : Symbol(undefined)
515+
516+
return;
517+
}
518+
iSpec[null! as keyof InternalSpec];
519+
>iSpec : Symbol(iSpec, Decl(controlFlowGenericTypes.ts, 165, 11))
520+
>InternalSpec : Symbol(InternalSpec, Decl(controlFlowGenericTypes.ts, 162, 55))
521+
522+
iSpec[null! as keyof PublicSpec];
523+
>iSpec : Symbol(iSpec, Decl(controlFlowGenericTypes.ts, 165, 11))
524+
>PublicSpec : Symbol(PublicSpec, Decl(controlFlowGenericTypes.ts, 161, 20))
525+
}
526+
}
527+

0 commit comments

Comments
 (0)