Skip to content

Commit c39a683

Browse files
authored
Merge pull request #15576 from Microsoft/typeVariableTypeGuards
Improve type guards for type variables
2 parents 83da326 + a6dfd66 commit c39a683

File tree

5 files changed

+728
-9
lines changed

5 files changed

+728
-9
lines changed

src/compiler/checker.ts

+34-9
Original file line numberDiff line numberDiff line change
@@ -5775,11 +5775,11 @@ namespace ts {
57755775
const t = type.flags & TypeFlags.TypeVariable ? getBaseConstraintOfType(type) || emptyObjectType : type;
57765776
return t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(<IntersectionType>t) :
57775777
t.flags & TypeFlags.StringLike ? globalStringType :
5778-
t.flags & TypeFlags.NumberLike ? globalNumberType :
5779-
t.flags & TypeFlags.BooleanLike ? globalBooleanType :
5780-
t.flags & TypeFlags.ESSymbol ? getGlobalESSymbolType(/*reportErrors*/ languageVersion >= ScriptTarget.ES2015) :
5781-
t.flags & TypeFlags.NonPrimitive ? emptyObjectType :
5782-
t;
5778+
t.flags & TypeFlags.NumberLike ? globalNumberType :
5779+
t.flags & TypeFlags.BooleanLike ? globalBooleanType :
5780+
t.flags & TypeFlags.ESSymbol ? getGlobalESSymbolType(/*reportErrors*/ languageVersion >= ScriptTarget.ES2015) :
5781+
t.flags & TypeFlags.NonPrimitive ? emptyObjectType :
5782+
t;
57835783
}
57845784

57855785
function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: string): Symbol {
@@ -10441,11 +10441,13 @@ namespace ts {
1044110441
// Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers
1044210442
// separated by dots). The key consists of the id of the symbol referenced by the
1044310443
// leftmost identifier followed by zero or more property names separated by dots.
10444-
// The result is undefined if the reference isn't a dotted name.
10444+
// The result is undefined if the reference isn't a dotted name. We prefix nodes
10445+
// occurring in an apparent type position with '@' because the control flow type
10446+
// of such nodes may be based on the apparent type instead of the declared type.
1044510447
function getFlowCacheKey(node: Node): string {
1044610448
if (node.kind === SyntaxKind.Identifier) {
1044710449
const symbol = getResolvedSymbol(<Identifier>node);
10448-
return symbol !== unknownSymbol ? "" + getSymbolId(symbol) : undefined;
10450+
return symbol !== unknownSymbol ? (isApparentTypePosition(node) ? "@" : "") + getSymbolId(symbol) : undefined;
1044910451
}
1045010452
if (node.kind === SyntaxKind.ThisKeyword) {
1045110453
return "0";
@@ -11710,6 +11712,29 @@ namespace ts {
1171011712
return annotationIncludesUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : declaredType;
1171111713
}
1171211714

11715+
function isApparentTypePosition(node: Node) {
11716+
const parent = node.parent;
11717+
return parent.kind === SyntaxKind.PropertyAccessExpression ||
11718+
parent.kind === SyntaxKind.CallExpression && (<CallExpression>parent).expression === node ||
11719+
parent.kind === SyntaxKind.ElementAccessExpression && (<ElementAccessExpression>parent).expression === node;
11720+
}
11721+
11722+
function typeHasNullableConstraint(type: Type) {
11723+
return type.flags & TypeFlags.TypeVariable && maybeTypeOfKind(getBaseConstraintOfType(type) || emptyObjectType, TypeFlags.Nullable);
11724+
}
11725+
11726+
function getDeclaredOrApparentType(symbol: Symbol, node: Node) {
11727+
// When a node is the left hand expression of a property access, element access, or call expression,
11728+
// and the type of the node includes type variables with constraints that are nullable, we fetch the
11729+
// apparent type of the node *before* performing control flow analysis such that narrowings apply to
11730+
// the constraint type.
11731+
const type = getTypeOfSymbol(symbol);
11732+
if (isApparentTypePosition(node) && forEachType(type, typeHasNullableConstraint)) {
11733+
return mapType(getWidenedType(type), getApparentType);
11734+
}
11735+
return type;
11736+
}
11737+
1171311738
function checkIdentifier(node: Identifier): Type {
1171411739
const symbol = getResolvedSymbol(node);
1171511740
if (symbol === unknownSymbol) {
@@ -11785,7 +11810,7 @@ namespace ts {
1178511810
checkCollisionWithCapturedNewTargetVariable(node, node);
1178611811
checkNestedBlockScopedBinding(node, symbol);
1178711812

11788-
const type = getTypeOfSymbol(localOrExportSymbol);
11813+
const type = getDeclaredOrApparentType(localOrExportSymbol, node);
1178911814
const declaration = localOrExportSymbol.valueDeclaration;
1179011815
const assignmentKind = getAssignmentTargetKind(node);
1179111816

@@ -14158,7 +14183,7 @@ namespace ts {
1415814183

1415914184
checkPropertyAccessibility(node, left, apparentType, prop);
1416014185

14161-
const propType = getTypeOfSymbol(prop);
14186+
const propType = getDeclaredOrApparentType(prop, node);
1416214187
const assignmentKind = getAssignmentTargetKind(node);
1416314188

1416414189
if (assignmentKind) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
//// [typeVariableTypeGuards.ts]
2+
// Repro from #14091
3+
4+
interface Foo {
5+
foo(): void
6+
}
7+
8+
class A<P extends Partial<Foo>> {
9+
props: Readonly<P>
10+
doSomething() {
11+
this.props.foo && this.props.foo()
12+
}
13+
}
14+
15+
// Repro from #14415
16+
17+
interface Banana {
18+
color: 'yellow';
19+
}
20+
21+
class Monkey<T extends Banana | undefined> {
22+
a: T;
23+
render() {
24+
if (this.a) {
25+
this.a.color;
26+
}
27+
}
28+
}
29+
30+
interface BigBanana extends Banana {
31+
}
32+
33+
class BigMonkey extends Monkey<BigBanana> {
34+
render() {
35+
if (this.a) {
36+
this.a.color;
37+
}
38+
}
39+
}
40+
41+
// Another repro
42+
43+
type Item = {
44+
(): string;
45+
x: string;
46+
}
47+
48+
function f1<T extends Item | undefined>(obj: T) {
49+
if (obj) {
50+
obj.x;
51+
obj["x"];
52+
obj();
53+
}
54+
}
55+
56+
function f2<T extends Item | undefined>(obj: T | undefined) {
57+
if (obj) {
58+
obj.x;
59+
obj["x"];
60+
obj();
61+
}
62+
}
63+
64+
function f3<T extends Item | undefined>(obj: T | null) {
65+
if (obj) {
66+
obj.x;
67+
obj["x"];
68+
obj();
69+
}
70+
}
71+
72+
function f4<T extends string[] | undefined>(obj: T | undefined, x: number) {
73+
if (obj) {
74+
obj[x].length;
75+
}
76+
}
77+
78+
function f5<T, K extends keyof T>(obj: T | undefined, key: K) {
79+
if (obj) {
80+
obj[key];
81+
}
82+
}
83+
84+
85+
//// [typeVariableTypeGuards.js]
86+
"use strict";
87+
// Repro from #14091
88+
var __extends = (this && this.__extends) || (function () {
89+
var extendStatics = Object.setPrototypeOf ||
90+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
91+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
92+
return function (d, b) {
93+
extendStatics(d, b);
94+
function __() { this.constructor = d; }
95+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
96+
};
97+
})();
98+
var A = (function () {
99+
function A() {
100+
}
101+
A.prototype.doSomething = function () {
102+
this.props.foo && this.props.foo();
103+
};
104+
return A;
105+
}());
106+
var Monkey = (function () {
107+
function Monkey() {
108+
}
109+
Monkey.prototype.render = function () {
110+
if (this.a) {
111+
this.a.color;
112+
}
113+
};
114+
return Monkey;
115+
}());
116+
var BigMonkey = (function (_super) {
117+
__extends(BigMonkey, _super);
118+
function BigMonkey() {
119+
return _super !== null && _super.apply(this, arguments) || this;
120+
}
121+
BigMonkey.prototype.render = function () {
122+
if (this.a) {
123+
this.a.color;
124+
}
125+
};
126+
return BigMonkey;
127+
}(Monkey));
128+
function f1(obj) {
129+
if (obj) {
130+
obj.x;
131+
obj["x"];
132+
obj();
133+
}
134+
}
135+
function f2(obj) {
136+
if (obj) {
137+
obj.x;
138+
obj["x"];
139+
obj();
140+
}
141+
}
142+
function f3(obj) {
143+
if (obj) {
144+
obj.x;
145+
obj["x"];
146+
obj();
147+
}
148+
}
149+
function f4(obj, x) {
150+
if (obj) {
151+
obj[x].length;
152+
}
153+
}
154+
function f5(obj, key) {
155+
if (obj) {
156+
obj[key];
157+
}
158+
}

0 commit comments

Comments
 (0)