Merge pull request #15576 from Microsoft/typeVariableTypeGuards

Improve type guards for type variables
This commit is contained in:
Anders Hejlsberg 2017-05-07 10:52:29 -07:00 committed by GitHub
commit c39a683504
5 changed files with 728 additions and 9 deletions

View file

@ -5775,11 +5775,11 @@ namespace ts {
const t = type.flags & TypeFlags.TypeVariable ? getBaseConstraintOfType(type) || emptyObjectType : type;
return t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(<IntersectionType>t) :
t.flags & TypeFlags.StringLike ? globalStringType :
t.flags & TypeFlags.NumberLike ? globalNumberType :
t.flags & TypeFlags.BooleanLike ? globalBooleanType :
t.flags & TypeFlags.ESSymbol ? getGlobalESSymbolType(/*reportErrors*/ languageVersion >= ScriptTarget.ES2015) :
t.flags & TypeFlags.NonPrimitive ? emptyObjectType :
t;
t.flags & TypeFlags.NumberLike ? globalNumberType :
t.flags & TypeFlags.BooleanLike ? globalBooleanType :
t.flags & TypeFlags.ESSymbol ? getGlobalESSymbolType(/*reportErrors*/ languageVersion >= ScriptTarget.ES2015) :
t.flags & TypeFlags.NonPrimitive ? emptyObjectType :
t;
}
function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: string): Symbol {
@ -10441,11 +10441,13 @@ namespace ts {
// Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers
// separated by dots). The key consists of the id of the symbol referenced by the
// leftmost identifier followed by zero or more property names separated by dots.
// The result is undefined if the reference isn't a dotted name.
// The result is undefined if the reference isn't a dotted name. We prefix nodes
// occurring in an apparent type position with '@' because the control flow type
// of such nodes may be based on the apparent type instead of the declared type.
function getFlowCacheKey(node: Node): string {
if (node.kind === SyntaxKind.Identifier) {
const symbol = getResolvedSymbol(<Identifier>node);
return symbol !== unknownSymbol ? "" + getSymbolId(symbol) : undefined;
return symbol !== unknownSymbol ? (isApparentTypePosition(node) ? "@" : "") + getSymbolId(symbol) : undefined;
}
if (node.kind === SyntaxKind.ThisKeyword) {
return "0";
@ -11710,6 +11712,29 @@ namespace ts {
return annotationIncludesUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : declaredType;
}
function isApparentTypePosition(node: Node) {
const parent = node.parent;
return parent.kind === SyntaxKind.PropertyAccessExpression ||
parent.kind === SyntaxKind.CallExpression && (<CallExpression>parent).expression === node ||
parent.kind === SyntaxKind.ElementAccessExpression && (<ElementAccessExpression>parent).expression === node;
}
function typeHasNullableConstraint(type: Type) {
return type.flags & TypeFlags.TypeVariable && maybeTypeOfKind(getBaseConstraintOfType(type) || emptyObjectType, TypeFlags.Nullable);
}
function getDeclaredOrApparentType(symbol: Symbol, node: Node) {
// When a node is the left hand expression of a property access, element access, or call expression,
// and the type of the node includes type variables with constraints that are nullable, we fetch the
// apparent type of the node *before* performing control flow analysis such that narrowings apply to
// the constraint type.
const type = getTypeOfSymbol(symbol);
if (isApparentTypePosition(node) && forEachType(type, typeHasNullableConstraint)) {
return mapType(getWidenedType(type), getApparentType);
}
return type;
}
function checkIdentifier(node: Identifier): Type {
const symbol = getResolvedSymbol(node);
if (symbol === unknownSymbol) {
@ -11785,7 +11810,7 @@ namespace ts {
checkCollisionWithCapturedNewTargetVariable(node, node);
checkNestedBlockScopedBinding(node, symbol);
const type = getTypeOfSymbol(localOrExportSymbol);
const type = getDeclaredOrApparentType(localOrExportSymbol, node);
const declaration = localOrExportSymbol.valueDeclaration;
const assignmentKind = getAssignmentTargetKind(node);
@ -14158,7 +14183,7 @@ namespace ts {
checkPropertyAccessibility(node, left, apparentType, prop);
const propType = getTypeOfSymbol(prop);
const propType = getDeclaredOrApparentType(prop, node);
const assignmentKind = getAssignmentTargetKind(node);
if (assignmentKind) {

View file

@ -0,0 +1,158 @@
//// [typeVariableTypeGuards.ts]
// Repro from #14091
interface Foo {
foo(): void
}
class A<P extends Partial<Foo>> {
props: Readonly<P>
doSomething() {
this.props.foo && this.props.foo()
}
}
// Repro from #14415
interface Banana {
color: 'yellow';
}
class Monkey<T extends Banana | undefined> {
a: T;
render() {
if (this.a) {
this.a.color;
}
}
}
interface BigBanana extends Banana {
}
class BigMonkey extends Monkey<BigBanana> {
render() {
if (this.a) {
this.a.color;
}
}
}
// Another repro
type Item = {
(): string;
x: string;
}
function f1<T extends Item | undefined>(obj: T) {
if (obj) {
obj.x;
obj["x"];
obj();
}
}
function f2<T extends Item | undefined>(obj: T | undefined) {
if (obj) {
obj.x;
obj["x"];
obj();
}
}
function f3<T extends Item | undefined>(obj: T | null) {
if (obj) {
obj.x;
obj["x"];
obj();
}
}
function f4<T extends string[] | undefined>(obj: T | undefined, x: number) {
if (obj) {
obj[x].length;
}
}
function f5<T, K extends keyof T>(obj: T | undefined, key: K) {
if (obj) {
obj[key];
}
}
//// [typeVariableTypeGuards.js]
"use strict";
// Repro from #14091
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var A = (function () {
function A() {
}
A.prototype.doSomething = function () {
this.props.foo && this.props.foo();
};
return A;
}());
var Monkey = (function () {
function Monkey() {
}
Monkey.prototype.render = function () {
if (this.a) {
this.a.color;
}
};
return Monkey;
}());
var BigMonkey = (function (_super) {
__extends(BigMonkey, _super);
function BigMonkey() {
return _super !== null && _super.apply(this, arguments) || this;
}
BigMonkey.prototype.render = function () {
if (this.a) {
this.a.color;
}
};
return BigMonkey;
}(Monkey));
function f1(obj) {
if (obj) {
obj.x;
obj["x"];
obj();
}
}
function f2(obj) {
if (obj) {
obj.x;
obj["x"];
obj();
}
}
function f3(obj) {
if (obj) {
obj.x;
obj["x"];
obj();
}
}
function f4(obj, x) {
if (obj) {
obj[x].length;
}
}
function f5(obj, key) {
if (obj) {
obj[key];
}
}

View file

@ -0,0 +1,221 @@
=== tests/cases/compiler/typeVariableTypeGuards.ts ===
// Repro from #14091
interface Foo {
>Foo : Symbol(Foo, Decl(typeVariableTypeGuards.ts, 0, 0))
foo(): void
>foo : Symbol(Foo.foo, Decl(typeVariableTypeGuards.ts, 2, 15))
}
class A<P extends Partial<Foo>> {
>A : Symbol(A, Decl(typeVariableTypeGuards.ts, 4, 1))
>P : Symbol(P, Decl(typeVariableTypeGuards.ts, 6, 8))
>Partial : Symbol(Partial, Decl(lib.d.ts, --, --))
>Foo : Symbol(Foo, Decl(typeVariableTypeGuards.ts, 0, 0))
props: Readonly<P>
>props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 6, 33))
>Readonly : Symbol(Readonly, Decl(lib.d.ts, --, --))
>P : Symbol(P, Decl(typeVariableTypeGuards.ts, 6, 8))
doSomething() {
>doSomething : Symbol(A.doSomething, Decl(typeVariableTypeGuards.ts, 7, 22))
this.props.foo && this.props.foo()
>this.props.foo : Symbol(foo)
>this.props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 6, 33))
>this : Symbol(A, Decl(typeVariableTypeGuards.ts, 4, 1))
>props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 6, 33))
>foo : Symbol(foo)
>this.props.foo : Symbol(foo)
>this.props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 6, 33))
>this : Symbol(A, Decl(typeVariableTypeGuards.ts, 4, 1))
>props : Symbol(A.props, Decl(typeVariableTypeGuards.ts, 6, 33))
>foo : Symbol(foo)
}
}
// Repro from #14415
interface Banana {
>Banana : Symbol(Banana, Decl(typeVariableTypeGuards.ts, 11, 1))
color: 'yellow';
>color : Symbol(Banana.color, Decl(typeVariableTypeGuards.ts, 15, 18))
}
class Monkey<T extends Banana | undefined> {
>Monkey : Symbol(Monkey, Decl(typeVariableTypeGuards.ts, 17, 1))
>T : Symbol(T, Decl(typeVariableTypeGuards.ts, 19, 13))
>Banana : Symbol(Banana, Decl(typeVariableTypeGuards.ts, 11, 1))
a: T;
>a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44))
>T : Symbol(T, Decl(typeVariableTypeGuards.ts, 19, 13))
render() {
>render : Symbol(Monkey.render, Decl(typeVariableTypeGuards.ts, 20, 9))
if (this.a) {
>this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44))
>this : Symbol(Monkey, Decl(typeVariableTypeGuards.ts, 17, 1))
>a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44))
this.a.color;
>this.a.color : Symbol(Banana.color, Decl(typeVariableTypeGuards.ts, 15, 18))
>this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44))
>this : Symbol(Monkey, Decl(typeVariableTypeGuards.ts, 17, 1))
>a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44))
>color : Symbol(Banana.color, Decl(typeVariableTypeGuards.ts, 15, 18))
}
}
}
interface BigBanana extends Banana {
>BigBanana : Symbol(BigBanana, Decl(typeVariableTypeGuards.ts, 26, 1))
>Banana : Symbol(Banana, Decl(typeVariableTypeGuards.ts, 11, 1))
}
class BigMonkey extends Monkey<BigBanana> {
>BigMonkey : Symbol(BigMonkey, Decl(typeVariableTypeGuards.ts, 29, 1))
>Monkey : Symbol(Monkey, Decl(typeVariableTypeGuards.ts, 17, 1))
>BigBanana : Symbol(BigBanana, Decl(typeVariableTypeGuards.ts, 26, 1))
render() {
>render : Symbol(BigMonkey.render, Decl(typeVariableTypeGuards.ts, 31, 43))
if (this.a) {
>this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44))
>this : Symbol(BigMonkey, Decl(typeVariableTypeGuards.ts, 29, 1))
>a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44))
this.a.color;
>this.a.color : Symbol(Banana.color, Decl(typeVariableTypeGuards.ts, 15, 18))
>this.a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44))
>this : Symbol(BigMonkey, Decl(typeVariableTypeGuards.ts, 29, 1))
>a : Symbol(Monkey.a, Decl(typeVariableTypeGuards.ts, 19, 44))
>color : Symbol(Banana.color, Decl(typeVariableTypeGuards.ts, 15, 18))
}
}
}
// Another repro
type Item = {
>Item : Symbol(Item, Decl(typeVariableTypeGuards.ts, 37, 1))
(): string;
x: string;
>x : Symbol(x, Decl(typeVariableTypeGuards.ts, 42, 15))
}
function f1<T extends Item | undefined>(obj: T) {
>f1 : Symbol(f1, Decl(typeVariableTypeGuards.ts, 44, 1))
>T : Symbol(T, Decl(typeVariableTypeGuards.ts, 46, 12))
>Item : Symbol(Item, Decl(typeVariableTypeGuards.ts, 37, 1))
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 46, 40))
>T : Symbol(T, Decl(typeVariableTypeGuards.ts, 46, 12))
if (obj) {
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 46, 40))
obj.x;
>obj.x : Symbol(x, Decl(typeVariableTypeGuards.ts, 42, 15))
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 46, 40))
>x : Symbol(x, Decl(typeVariableTypeGuards.ts, 42, 15))
obj["x"];
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 46, 40))
>"x" : Symbol(x, Decl(typeVariableTypeGuards.ts, 42, 15))
obj();
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 46, 40))
}
}
function f2<T extends Item | undefined>(obj: T | undefined) {
>f2 : Symbol(f2, Decl(typeVariableTypeGuards.ts, 52, 1))
>T : Symbol(T, Decl(typeVariableTypeGuards.ts, 54, 12))
>Item : Symbol(Item, Decl(typeVariableTypeGuards.ts, 37, 1))
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 54, 40))
>T : Symbol(T, Decl(typeVariableTypeGuards.ts, 54, 12))
if (obj) {
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 54, 40))
obj.x;
>obj.x : Symbol(x, Decl(typeVariableTypeGuards.ts, 42, 15))
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 54, 40))
>x : Symbol(x, Decl(typeVariableTypeGuards.ts, 42, 15))
obj["x"];
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 54, 40))
>"x" : Symbol(x, Decl(typeVariableTypeGuards.ts, 42, 15))
obj();
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 54, 40))
}
}
function f3<T extends Item | undefined>(obj: T | null) {
>f3 : Symbol(f3, Decl(typeVariableTypeGuards.ts, 60, 1))
>T : Symbol(T, Decl(typeVariableTypeGuards.ts, 62, 12))
>Item : Symbol(Item, Decl(typeVariableTypeGuards.ts, 37, 1))
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 62, 40))
>T : Symbol(T, Decl(typeVariableTypeGuards.ts, 62, 12))
if (obj) {
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 62, 40))
obj.x;
>obj.x : Symbol(x, Decl(typeVariableTypeGuards.ts, 42, 15))
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 62, 40))
>x : Symbol(x, Decl(typeVariableTypeGuards.ts, 42, 15))
obj["x"];
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 62, 40))
>"x" : Symbol(x, Decl(typeVariableTypeGuards.ts, 42, 15))
obj();
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 62, 40))
}
}
function f4<T extends string[] | undefined>(obj: T | undefined, x: number) {
>f4 : Symbol(f4, Decl(typeVariableTypeGuards.ts, 68, 1))
>T : Symbol(T, Decl(typeVariableTypeGuards.ts, 70, 12))
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 70, 44))
>T : Symbol(T, Decl(typeVariableTypeGuards.ts, 70, 12))
>x : Symbol(x, Decl(typeVariableTypeGuards.ts, 70, 63))
if (obj) {
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 70, 44))
obj[x].length;
>obj[x].length : Symbol(String.length, Decl(lib.d.ts, --, --))
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 70, 44))
>x : Symbol(x, Decl(typeVariableTypeGuards.ts, 70, 63))
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
}
}
function f5<T, K extends keyof T>(obj: T | undefined, key: K) {
>f5 : Symbol(f5, Decl(typeVariableTypeGuards.ts, 74, 1))
>T : Symbol(T, Decl(typeVariableTypeGuards.ts, 76, 12))
>K : Symbol(K, Decl(typeVariableTypeGuards.ts, 76, 14))
>T : Symbol(T, Decl(typeVariableTypeGuards.ts, 76, 12))
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 76, 34))
>T : Symbol(T, Decl(typeVariableTypeGuards.ts, 76, 12))
>key : Symbol(key, Decl(typeVariableTypeGuards.ts, 76, 53))
>K : Symbol(K, Decl(typeVariableTypeGuards.ts, 76, 14))
if (obj) {
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 76, 34))
obj[key];
>obj : Symbol(obj, Decl(typeVariableTypeGuards.ts, 76, 34))
>key : Symbol(key, Decl(typeVariableTypeGuards.ts, 76, 53))
}
}

View file

@ -0,0 +1,232 @@
=== tests/cases/compiler/typeVariableTypeGuards.ts ===
// Repro from #14091
interface Foo {
>Foo : Foo
foo(): void
>foo : () => void
}
class A<P extends Partial<Foo>> {
>A : A<P>
>P : P
>Partial : Partial<T>
>Foo : Foo
props: Readonly<P>
>props : Readonly<P>
>Readonly : Readonly<T>
>P : P
doSomething() {
>doSomething : () => void
this.props.foo && this.props.foo()
>this.props.foo && this.props.foo() : void
>this.props.foo : P["foo"]
>this.props : Readonly<P>
>this : this
>props : Readonly<P>
>foo : P["foo"]
>this.props.foo() : void
>this.props.foo : () => void
>this.props : Readonly<P>
>this : this
>props : Readonly<P>
>foo : () => void
}
}
// Repro from #14415
interface Banana {
>Banana : Banana
color: 'yellow';
>color : "yellow"
}
class Monkey<T extends Banana | undefined> {
>Monkey : Monkey<T>
>T : T
>Banana : Banana
a: T;
>a : T
>T : T
render() {
>render : () => void
if (this.a) {
>this.a : T
>this : this
>a : T
this.a.color;
>this.a.color : "yellow"
>this.a : Banana
>this : this
>a : Banana
>color : "yellow"
}
}
}
interface BigBanana extends Banana {
>BigBanana : BigBanana
>Banana : Banana
}
class BigMonkey extends Monkey<BigBanana> {
>BigMonkey : BigMonkey
>Monkey : Monkey<BigBanana>
>BigBanana : BigBanana
render() {
>render : () => void
if (this.a) {
>this.a : BigBanana
>this : this
>a : BigBanana
this.a.color;
>this.a.color : "yellow"
>this.a : BigBanana
>this : this
>a : BigBanana
>color : "yellow"
}
}
}
// Another repro
type Item = {
>Item : Item
(): string;
x: string;
>x : string
}
function f1<T extends Item | undefined>(obj: T) {
>f1 : <T extends Item | undefined>(obj: T) => void
>T : T
>Item : Item
>obj : T
>T : T
if (obj) {
>obj : T
obj.x;
>obj.x : string
>obj : Item
>x : string
obj["x"];
>obj["x"] : string
>obj : Item
>"x" : "x"
obj();
>obj() : string
>obj : Item
}
}
function f2<T extends Item | undefined>(obj: T | undefined) {
>f2 : <T extends Item | undefined>(obj: T | undefined) => void
>T : T
>Item : Item
>obj : T | undefined
>T : T
if (obj) {
>obj : T | undefined
obj.x;
>obj.x : string
>obj : Item
>x : string
obj["x"];
>obj["x"] : string
>obj : Item
>"x" : "x"
obj();
>obj() : string
>obj : Item
}
}
function f3<T extends Item | undefined>(obj: T | null) {
>f3 : <T extends Item | undefined>(obj: T | null) => void
>T : T
>Item : Item
>obj : T | null
>T : T
>null : null
if (obj) {
>obj : T | null
obj.x;
>obj.x : string
>obj : Item
>x : string
obj["x"];
>obj["x"] : string
>obj : Item
>"x" : "x"
obj();
>obj() : string
>obj : Item
}
}
function f4<T extends string[] | undefined>(obj: T | undefined, x: number) {
>f4 : <T extends string[] | undefined>(obj: T | undefined, x: number) => void
>T : T
>obj : T | undefined
>T : T
>x : number
if (obj) {
>obj : T | undefined
obj[x].length;
>obj[x].length : number
>obj[x] : string
>obj : string[]
>x : number
>length : number
}
}
function f5<T, K extends keyof T>(obj: T | undefined, key: K) {
>f5 : <T, K extends keyof T>(obj: T | undefined, key: K) => void
>T : T
>K : K
>T : T
>obj : T | undefined
>T : T
>key : K
>K : K
if (obj) {
>obj : T | undefined
obj[key];
>obj[key] : T[K]
>obj : T
>key : K
}
}

View file

@ -0,0 +1,83 @@
// @strict: true
// Repro from #14091
interface Foo {
foo(): void
}
class A<P extends Partial<Foo>> {
props: Readonly<P>
doSomething() {
this.props.foo && this.props.foo()
}
}
// Repro from #14415
interface Banana {
color: 'yellow';
}
class Monkey<T extends Banana | undefined> {
a: T;
render() {
if (this.a) {
this.a.color;
}
}
}
interface BigBanana extends Banana {
}
class BigMonkey extends Monkey<BigBanana> {
render() {
if (this.a) {
this.a.color;
}
}
}
// Another repro
type Item = {
(): string;
x: string;
}
function f1<T extends Item | undefined>(obj: T) {
if (obj) {
obj.x;
obj["x"];
obj();
}
}
function f2<T extends Item | undefined>(obj: T | undefined) {
if (obj) {
obj.x;
obj["x"];
obj();
}
}
function f3<T extends Item | undefined>(obj: T | null) {
if (obj) {
obj.x;
obj["x"];
obj();
}
}
function f4<T extends string[] | undefined>(obj: T | undefined, x: number) {
if (obj) {
obj[x].length;
}
}
function f5<T, K extends keyof T>(obj: T | undefined, key: K) {
if (obj) {
obj[key];
}
}