diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ca5d65068a0b8..2261e0f1a9209 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5775,11 +5775,11 @@ namespace ts { const t = type.flags & TypeFlags.TypeVariable ? getBaseConstraintOfType(type) || emptyObjectType : type; return t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(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 { @@ -10439,11 +10439,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(node); - return symbol !== unknownSymbol ? "" + getSymbolId(symbol) : undefined; + return symbol !== unknownSymbol ? (isApparentTypePosition(node) ? "@" : "") + getSymbolId(symbol) : undefined; } if (node.kind === SyntaxKind.ThisKeyword) { return "0"; @@ -11708,6 +11710,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 && (parent).expression === node || + parent.kind === SyntaxKind.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) { @@ -11783,7 +11808,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); @@ -14141,7 +14166,7 @@ namespace ts { checkPropertyAccessibility(node, left, apparentType, prop); - const propType = getTypeOfSymbol(prop); + const propType = getDeclaredOrApparentType(prop, node); const assignmentKind = getAssignmentTargetKind(node); if (assignmentKind) { diff --git a/tests/baselines/reference/typeVariableTypeGuards.js b/tests/baselines/reference/typeVariableTypeGuards.js new file mode 100644 index 0000000000000..8525bde0f779c --- /dev/null +++ b/tests/baselines/reference/typeVariableTypeGuards.js @@ -0,0 +1,158 @@ +//// [typeVariableTypeGuards.ts] +// Repro from #14091 + +interface Foo { + foo(): void +} + +class A

> { + props: Readonly

+ doSomething() { + this.props.foo && this.props.foo() + } +} + +// Repro from #14415 + +interface Banana { + color: 'yellow'; +} + +class Monkey { + a: T; + render() { + if (this.a) { + this.a.color; + } + } +} + +interface BigBanana extends Banana { +} + +class BigMonkey extends Monkey { + render() { + if (this.a) { + this.a.color; + } + } +} + +// Another repro + +type Item = { + (): string; + x: string; +} + +function f1(obj: T) { + if (obj) { + obj.x; + obj["x"]; + obj(); + } +} + +function f2(obj: T | undefined) { + if (obj) { + obj.x; + obj["x"]; + obj(); + } +} + +function f3(obj: T | null) { + if (obj) { + obj.x; + obj["x"]; + obj(); + } +} + +function f4(obj: T | undefined, x: number) { + if (obj) { + obj[x].length; + } +} + +function f5(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]; + } +} diff --git a/tests/baselines/reference/typeVariableTypeGuards.symbols b/tests/baselines/reference/typeVariableTypeGuards.symbols new file mode 100644 index 0000000000000..fa94cfd9462ba --- /dev/null +++ b/tests/baselines/reference/typeVariableTypeGuards.symbols @@ -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

> { +>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

+>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 { +>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 { +>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(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(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(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(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(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)) + } +} + diff --git a/tests/baselines/reference/typeVariableTypeGuards.types b/tests/baselines/reference/typeVariableTypeGuards.types new file mode 100644 index 0000000000000..bfcf851648a2a --- /dev/null +++ b/tests/baselines/reference/typeVariableTypeGuards.types @@ -0,0 +1,232 @@ +=== tests/cases/compiler/typeVariableTypeGuards.ts === +// Repro from #14091 + +interface Foo { +>Foo : Foo + + foo(): void +>foo : () => void +} + +class A

> { +>A : A

+>P : P +>Partial : Partial +>Foo : Foo + + props: Readonly

+>props : Readonly

+>Readonly : Readonly +>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

+>this : this +>props : Readonly

+>foo : P["foo"] +>this.props.foo() : void +>this.props.foo : () => void +>this.props : Readonly

+>this : this +>props : Readonly

+>foo : () => void + } +} + +// Repro from #14415 + +interface Banana { +>Banana : Banana + + color: 'yellow'; +>color : "yellow" +} + +class Monkey { +>Monkey : Monkey +>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 { +>BigMonkey : BigMonkey +>Monkey : Monkey +>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(obj: T) { +>f1 : (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(obj: T | undefined) { +>f2 : (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(obj: T | null) { +>f3 : (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(obj: T | undefined, x: number) { +>f4 : (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(obj: T | undefined, key: K) { +>f5 : (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 + } +} + diff --git a/tests/cases/compiler/typeVariableTypeGuards.ts b/tests/cases/compiler/typeVariableTypeGuards.ts new file mode 100644 index 0000000000000..0a4221c93a0aa --- /dev/null +++ b/tests/cases/compiler/typeVariableTypeGuards.ts @@ -0,0 +1,83 @@ +// @strict: true + +// Repro from #14091 + +interface Foo { + foo(): void +} + +class A

> { + props: Readonly

+ doSomething() { + this.props.foo && this.props.foo() + } +} + +// Repro from #14415 + +interface Banana { + color: 'yellow'; +} + +class Monkey { + a: T; + render() { + if (this.a) { + this.a.color; + } + } +} + +interface BigBanana extends Banana { +} + +class BigMonkey extends Monkey { + render() { + if (this.a) { + this.a.color; + } + } +} + +// Another repro + +type Item = { + (): string; + x: string; +} + +function f1(obj: T) { + if (obj) { + obj.x; + obj["x"]; + obj(); + } +} + +function f2(obj: T | undefined) { + if (obj) { + obj.x; + obj["x"]; + obj(); + } +} + +function f3(obj: T | null) { + if (obj) { + obj.x; + obj["x"]; + obj(); + } +} + +function f4(obj: T | undefined, x: number) { + if (obj) { + obj[x].length; + } +} + +function f5(obj: T | undefined, key: K) { + if (obj) { + obj[key]; + } +}