mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-04 21:53:42 -06:00
Preserve type refinements in closures created past last assignment (#56908)
This commit is contained in:
parent
f9cb96c03d
commit
8ff77fbc48
@ -668,7 +668,6 @@ import {
|
||||
isOutermostOptionalChain,
|
||||
isParameter,
|
||||
isParameterDeclaration,
|
||||
isParameterOrCatchClauseVariable,
|
||||
isParameterPropertyDeclaration,
|
||||
isParenthesizedExpression,
|
||||
isParenthesizedTypeNode,
|
||||
@ -27481,7 +27480,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
case SyntaxKind.Identifier:
|
||||
if (!isThisInTypeQuery(node)) {
|
||||
const symbol = getResolvedSymbol(node as Identifier);
|
||||
return isConstantVariable(symbol) || isParameterOrCatchClauseVariable(symbol) && !isSymbolAssigned(symbol);
|
||||
return isConstantVariable(symbol) || isParameterOrMutableLocalVariable(symbol) && !isSymbolAssigned(symbol);
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
@ -28760,10 +28759,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
|
||||
// Check if a parameter or catch variable is assigned anywhere
|
||||
function isSymbolAssigned(symbol: Symbol) {
|
||||
if (!symbol.valueDeclaration) {
|
||||
return !isPastLastAssignment(symbol, /*location*/ undefined);
|
||||
}
|
||||
|
||||
// Return true if there are no assignments to the given symbol or if the given location
|
||||
// is past the last assignment to the symbol.
|
||||
function isPastLastAssignment(symbol: Symbol, location: Node | undefined) {
|
||||
const parent = findAncestor(symbol.valueDeclaration, isFunctionOrSourceFile);
|
||||
if (!parent) {
|
||||
return false;
|
||||
}
|
||||
const parent = getRootDeclaration(symbol.valueDeclaration).parent;
|
||||
const links = getNodeLinks(parent);
|
||||
if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) {
|
||||
links.flags |= NodeCheckFlags.AssignmentsMarked;
|
||||
@ -28771,7 +28776,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
markNodeAssignments(parent);
|
||||
}
|
||||
}
|
||||
return symbol.isAssigned || false;
|
||||
return !symbol.lastAssignmentPos || location && symbol.lastAssignmentPos < location.pos;
|
||||
}
|
||||
|
||||
// Check if a parameter or catch variable (or their bindings elements) is assigned anywhere
|
||||
@ -28789,27 +28794,98 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
|
||||
function hasParentWithAssignmentsMarked(node: Node) {
|
||||
return !!findAncestor(node.parent, node => (isFunctionLike(node) || isCatchClause(node)) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked));
|
||||
return !!findAncestor(node.parent, node => isFunctionOrSourceFile(node) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked));
|
||||
}
|
||||
|
||||
function isFunctionOrSourceFile(node: Node) {
|
||||
return isFunctionLikeDeclaration(node) || isSourceFile(node);
|
||||
}
|
||||
|
||||
// For all assignments within the given root node, record the last assignment source position for all
|
||||
// referenced parameters and mutable local variables. When assignments occur in nested functions or
|
||||
// references occur in export specifiers, record Number.MAX_VALUE as the assignment position. When
|
||||
// assignments occur in compound statements, record the ending source position of the compound statement
|
||||
// as the assignment position (this is more conservative than full control flow analysis, but requires
|
||||
// only a single walk over the AST).
|
||||
function markNodeAssignments(node: Node) {
|
||||
if (node.kind === SyntaxKind.Identifier) {
|
||||
if (isAssignmentTarget(node)) {
|
||||
const symbol = getResolvedSymbol(node as Identifier);
|
||||
if (isParameterOrCatchClauseVariable(symbol)) {
|
||||
symbol.isAssigned = true;
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.Identifier:
|
||||
if (isAssignmentTarget(node)) {
|
||||
const symbol = getResolvedSymbol(node as Identifier);
|
||||
if (isParameterOrMutableLocalVariable(symbol) && symbol.lastAssignmentPos !== Number.MAX_VALUE) {
|
||||
const referencingFunction = findAncestor(node, isFunctionOrSourceFile);
|
||||
const declaringFunction = findAncestor(symbol.valueDeclaration, isFunctionOrSourceFile);
|
||||
symbol.lastAssignmentPos = referencingFunction === declaringFunction ? extendAssignmentPosition(node, symbol.valueDeclaration!) : Number.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
return;
|
||||
case SyntaxKind.ExportSpecifier:
|
||||
const exportDeclaration = (node as ExportSpecifier).parent.parent;
|
||||
if (!(node as ExportSpecifier).isTypeOnly && !exportDeclaration.isTypeOnly && !exportDeclaration.moduleSpecifier) {
|
||||
const symbol = resolveEntityName((node as ExportSpecifier).propertyName || (node as ExportSpecifier).name, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true);
|
||||
if (symbol && isParameterOrMutableLocalVariable(symbol)) {
|
||||
symbol.lastAssignmentPos = Number.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
return;
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.TypeAliasDeclaration:
|
||||
case SyntaxKind.EnumDeclaration:
|
||||
return;
|
||||
}
|
||||
if (isTypeNode(node)) {
|
||||
return;
|
||||
}
|
||||
forEachChild(node, markNodeAssignments);
|
||||
}
|
||||
|
||||
// Extend the position of the given assignment target node to the end of any intervening variable statement,
|
||||
// expression statement, compound statement, or class declaration occurring between the node and the given
|
||||
// declaration node.
|
||||
function extendAssignmentPosition(node: Node, declaration: Declaration) {
|
||||
let pos = node.pos;
|
||||
while (node && node.pos > declaration.pos) {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.VariableStatement:
|
||||
case SyntaxKind.ExpressionStatement:
|
||||
case SyntaxKind.IfStatement:
|
||||
case SyntaxKind.DoStatement:
|
||||
case SyntaxKind.WhileStatement:
|
||||
case SyntaxKind.ForStatement:
|
||||
case SyntaxKind.ForInStatement:
|
||||
case SyntaxKind.ForOfStatement:
|
||||
case SyntaxKind.WithStatement:
|
||||
case SyntaxKind.SwitchStatement:
|
||||
case SyntaxKind.TryStatement:
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
pos = node.end;
|
||||
}
|
||||
node = node.parent;
|
||||
}
|
||||
else {
|
||||
forEachChild(node, markNodeAssignments);
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
function isConstantVariable(symbol: Symbol) {
|
||||
return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Constant) !== 0;
|
||||
}
|
||||
|
||||
function isParameterOrMutableLocalVariable(symbol: Symbol) {
|
||||
// Return true if symbol is a parameter, a catch clause variable, or a mutable local variable
|
||||
const declaration = symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration);
|
||||
return !!declaration && (
|
||||
isParameter(declaration) ||
|
||||
isVariableDeclaration(declaration) && (isCatchClause(declaration.parent) || isMutableLocalVariableDeclaration(declaration))
|
||||
);
|
||||
}
|
||||
|
||||
function isMutableLocalVariableDeclaration(declaration: VariableDeclaration) {
|
||||
// Return true if symbol is a non-exported and non-global `let` variable
|
||||
return !!(declaration.parent.flags & NodeFlags.Let) && !(
|
||||
getCombinedModifierFlags(declaration) & ModifierFlags.Export ||
|
||||
declaration.parent.parent.kind === SyntaxKind.VariableStatement && isGlobalSourceFile(declaration.parent.parent.parent)
|
||||
);
|
||||
}
|
||||
|
||||
function parameterInitializerContainsUndefined(declaration: ParameterDeclaration): boolean {
|
||||
const links = getNodeLinks(declaration);
|
||||
|
||||
@ -29160,13 +29236,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
const isModuleExports = symbol.flags & SymbolFlags.ModuleExports;
|
||||
const typeIsAutomatic = type === autoType || type === autoArrayType;
|
||||
const isAutomaticTypeInNonNull = typeIsAutomatic && node.parent.kind === SyntaxKind.NonNullExpression;
|
||||
// When the control flow originates in a function expression or arrow function and we are referencing
|
||||
// a const variable or parameter from an outer function, we extend the origin of the control flow
|
||||
// analysis to include the immediately enclosing function.
|
||||
// When the control flow originates in a function expression, arrow function, method, or accessor, and
|
||||
// we are referencing a closed-over const variable or parameter or mutable local variable past its last
|
||||
// assignment, we extend the origin of the control flow analysis to include the immediately enclosing
|
||||
// control flow container.
|
||||
while (
|
||||
flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression ||
|
||||
flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethodOrAccessor(flowContainer)) &&
|
||||
(isConstantVariable(localOrExportSymbol) && type !== autoArrayType || isParameter && !isSymbolAssigned(localOrExportSymbol))
|
||||
flowContainer !== declarationContainer && (
|
||||
flowContainer.kind === SyntaxKind.FunctionExpression ||
|
||||
flowContainer.kind === SyntaxKind.ArrowFunction ||
|
||||
isObjectLiteralOrClassExpressionMethodOrAccessor(flowContainer)
|
||||
) && (
|
||||
isConstantVariable(localOrExportSymbol) && type !== autoArrayType ||
|
||||
isParameterOrMutableLocalVariable(localOrExportSymbol) && isPastLastAssignment(localOrExportSymbol, node)
|
||||
)
|
||||
) {
|
||||
flowContainer = getControlFlowContainer(flowContainer);
|
||||
}
|
||||
|
||||
@ -5825,8 +5825,8 @@ export interface Symbol {
|
||||
/** @internal */ exportSymbol?: Symbol; // Exported symbol associated with this symbol
|
||||
/** @internal */ constEnumOnlyModule: boolean | undefined; // True if module contains only const enums or other modules with only const enums
|
||||
/** @internal */ isReferenced?: SymbolFlags; // True if the symbol is referenced elsewhere. Keeps track of the meaning of a reference in case a symbol is both a type parameter and parameter.
|
||||
/** @internal */ lastAssignmentPos?: number; // Source position of last node that assigns value to symbol
|
||||
/** @internal */ isReplaceableByMethod?: boolean; // Can this Javascript class property be replaced by a method symbol?
|
||||
/** @internal */ isAssigned?: boolean; // True if the symbol is a parameter with assignments
|
||||
/** @internal */ assignmentDeclarationMembers?: Map<number, Declaration>; // detected late-bound assignment declarations associated with the symbol
|
||||
}
|
||||
|
||||
|
||||
@ -8178,7 +8178,7 @@ function Symbol(this: Symbol, flags: SymbolFlags, name: __String) {
|
||||
this.exportSymbol = undefined;
|
||||
this.constEnumOnlyModule = undefined;
|
||||
this.isReferenced = undefined;
|
||||
this.isAssigned = undefined;
|
||||
this.lastAssignmentPos = undefined;
|
||||
(this as any).links = undefined; // used by TransientSymbol
|
||||
}
|
||||
|
||||
@ -10351,12 +10351,6 @@ export function isCatchClauseVariableDeclaration(node: Node) {
|
||||
return node.kind === SyntaxKind.VariableDeclaration && node.parent.kind === SyntaxKind.CatchClause;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function isParameterOrCatchClauseVariable(symbol: Symbol) {
|
||||
const declaration = symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration);
|
||||
return !!declaration && (isParameter(declaration) || isCatchClauseVariableDeclaration(declaration));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function isFunctionExpressionOrArrowFunction(node: Node): node is FunctionExpression | ArrowFunction {
|
||||
return node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.ArrowFunction;
|
||||
|
||||
@ -14,10 +14,6 @@ controlFlowAliasing.ts(112,13): error TS2339: Property 'foo' does not exist on t
|
||||
Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'.
|
||||
controlFlowAliasing.ts(115,13): error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
|
||||
Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'.
|
||||
controlFlowAliasing.ts(134,13): error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
|
||||
Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'.
|
||||
controlFlowAliasing.ts(137,13): error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
|
||||
Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'.
|
||||
controlFlowAliasing.ts(154,19): error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
|
||||
Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'.
|
||||
controlFlowAliasing.ts(157,19): error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
|
||||
@ -28,7 +24,7 @@ controlFlowAliasing.ts(280,5): error TS2448: Block-scoped variable 'a' used befo
|
||||
controlFlowAliasing.ts(280,5): error TS2454: Variable 'a' is used before being assigned.
|
||||
|
||||
|
||||
==== controlFlowAliasing.ts (15 errors) ====
|
||||
==== controlFlowAliasing.ts (13 errors) ====
|
||||
// Narrowing by aliased conditional expressions
|
||||
|
||||
function f10(x: string | number) {
|
||||
@ -186,16 +182,10 @@ controlFlowAliasing.ts(280,5): error TS2454: Variable 'a' is used before being a
|
||||
let obj = arg;
|
||||
const isFoo = obj.kind === 'foo';
|
||||
if (isFoo) {
|
||||
obj.foo; // Not narrowed because obj is mutable
|
||||
~~~
|
||||
!!! error TS2339: Property 'foo' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
|
||||
!!! error TS2339: Property 'foo' does not exist on type '{ kind: "bar"; bar: number; }'.
|
||||
obj.foo;
|
||||
}
|
||||
else {
|
||||
obj.bar; // Not narrowed because obj is mutable
|
||||
~~~
|
||||
!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }'.
|
||||
!!! error TS2339: Property 'bar' does not exist on type '{ kind: "foo"; foo: string; }'.
|
||||
obj.bar;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -134,10 +134,10 @@ function f25(arg: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) {
|
||||
let obj = arg;
|
||||
const isFoo = obj.kind === 'foo';
|
||||
if (isFoo) {
|
||||
obj.foo; // Not narrowed because obj is mutable
|
||||
obj.foo;
|
||||
}
|
||||
else {
|
||||
obj.bar; // Not narrowed because obj is mutable
|
||||
obj.bar;
|
||||
}
|
||||
}
|
||||
|
||||
@ -423,10 +423,10 @@ function f25(arg) {
|
||||
var obj = arg;
|
||||
var isFoo = obj.kind === 'foo';
|
||||
if (isFoo) {
|
||||
obj.foo; // Not narrowed because obj is mutable
|
||||
obj.foo;
|
||||
}
|
||||
else {
|
||||
obj.bar; // Not narrowed because obj is mutable
|
||||
obj.bar;
|
||||
}
|
||||
}
|
||||
function f26(outer) {
|
||||
|
||||
@ -370,12 +370,16 @@ function f25(arg: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) {
|
||||
if (isFoo) {
|
||||
>isFoo : Symbol(isFoo, Decl(controlFlowAliasing.ts, 131, 9))
|
||||
|
||||
obj.foo; // Not narrowed because obj is mutable
|
||||
obj.foo;
|
||||
>obj.foo : Symbol(foo, Decl(controlFlowAliasing.ts, 129, 32))
|
||||
>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 130, 7))
|
||||
>foo : Symbol(foo, Decl(controlFlowAliasing.ts, 129, 32))
|
||||
}
|
||||
else {
|
||||
obj.bar; // Not narrowed because obj is mutable
|
||||
obj.bar;
|
||||
>obj.bar : Symbol(bar, Decl(controlFlowAliasing.ts, 129, 63))
|
||||
>obj : Symbol(obj, Decl(controlFlowAliasing.ts, 130, 7))
|
||||
>bar : Symbol(bar, Decl(controlFlowAliasing.ts, 129, 63))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -440,16 +440,16 @@ function f25(arg: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) {
|
||||
if (isFoo) {
|
||||
>isFoo : boolean
|
||||
|
||||
obj.foo; // Not narrowed because obj is mutable
|
||||
>obj.foo : any
|
||||
>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }
|
||||
>foo : any
|
||||
obj.foo;
|
||||
>obj.foo : string
|
||||
>obj : { kind: "foo"; foo: string; }
|
||||
>foo : string
|
||||
}
|
||||
else {
|
||||
obj.bar; // Not narrowed because obj is mutable
|
||||
>obj.bar : any
|
||||
>obj : { kind: "foo"; foo: string; } | { kind: "bar"; bar: number; }
|
||||
>bar : any
|
||||
obj.bar;
|
||||
>obj.bar : number
|
||||
>obj : { kind: "bar"; bar: number; }
|
||||
>bar : number
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
implicitConstParameters.ts(38,27): error TS18048: 'x' is possibly 'undefined'.
|
||||
implicitConstParameters.ts(44,27): error TS18048: 'x' is possibly 'undefined'.
|
||||
|
||||
|
||||
==== implicitConstParameters.ts (2 errors) ====
|
||||
==== implicitConstParameters.ts (1 errors) ====
|
||||
function doSomething(cb: () => void) {
|
||||
cb();
|
||||
}
|
||||
@ -38,11 +37,9 @@ implicitConstParameters.ts(44,27): error TS18048: 'x' is possibly 'undefined'.
|
||||
}
|
||||
|
||||
function f4(x: string | undefined) {
|
||||
x = "abc"; // causes x to be considered non-const
|
||||
x = "abc";
|
||||
if (x) {
|
||||
doSomething(() => x.length);
|
||||
~
|
||||
!!! error TS18048: 'x' is possibly 'undefined'.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ function f3(x: string | undefined) {
|
||||
}
|
||||
|
||||
function f4(x: string | undefined) {
|
||||
x = "abc"; // causes x to be considered non-const
|
||||
x = "abc";
|
||||
if (x) {
|
||||
doSomething(() => x.length);
|
||||
}
|
||||
@ -88,7 +88,7 @@ function f3(x) {
|
||||
}
|
||||
}
|
||||
function f4(x) {
|
||||
x = "abc"; // causes x to be considered non-const
|
||||
x = "abc";
|
||||
if (x) {
|
||||
doSomething(function () { return x.length; });
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ function f4(x: string | undefined) {
|
||||
>f4 : Symbol(f4, Decl(implicitConstParameters.ts, 32, 1))
|
||||
>x : Symbol(x, Decl(implicitConstParameters.ts, 34, 12))
|
||||
|
||||
x = "abc"; // causes x to be considered non-const
|
||||
x = "abc";
|
||||
>x : Symbol(x, Decl(implicitConstParameters.ts, 34, 12))
|
||||
|
||||
if (x) {
|
||||
|
||||
@ -103,7 +103,7 @@ function f4(x: string | undefined) {
|
||||
>f4 : (x: string | undefined) => void
|
||||
>x : string | undefined
|
||||
|
||||
x = "abc"; // causes x to be considered non-const
|
||||
x = "abc";
|
||||
>x = "abc" : "abc"
|
||||
>x : string | undefined
|
||||
>"abc" : "abc"
|
||||
@ -116,7 +116,7 @@ function f4(x: string | undefined) {
|
||||
>doSomething : (cb: () => void) => void
|
||||
>() => x.length : () => number
|
||||
>x.length : number
|
||||
>x : string | undefined
|
||||
>x : string
|
||||
>length : number
|
||||
}
|
||||
}
|
||||
|
||||
163
tests/baselines/reference/narrowingPastLastAssignment.errors.txt
Normal file
163
tests/baselines/reference/narrowingPastLastAssignment.errors.txt
Normal file
@ -0,0 +1,163 @@
|
||||
narrowingPastLastAssignment.ts(88,9): error TS7034: Variable 'x' implicitly has type 'any' in some locations where its type cannot be determined.
|
||||
narrowingPastLastAssignment.ts(90,20): error TS7005: Variable 'x' implicitly has an 'any' type.
|
||||
|
||||
|
||||
==== narrowingPastLastAssignment.ts (2 errors) ====
|
||||
function action(f: Function) {}
|
||||
|
||||
// Narrowings are preserved in closures created past last assignment
|
||||
|
||||
function f1(x: string | number) {
|
||||
x = "abc";
|
||||
action(() => { x /* string | number */ });
|
||||
x = 42;
|
||||
action(() => { x /* number */ });
|
||||
}
|
||||
|
||||
// Narrowings are not preserved in inner function and class declarations (due to hoisting)
|
||||
|
||||
function f2() {
|
||||
let x: string | number;
|
||||
x = 42;
|
||||
let a = () => { x /* number */ };
|
||||
let f = function() { x /* number */ };
|
||||
let C = class {
|
||||
foo() { x /* number */ }
|
||||
};
|
||||
let o = {
|
||||
foo() { x /* number */ }
|
||||
};
|
||||
function g() { x /* string | number */ }
|
||||
class A {
|
||||
foo() { x /* string | number */ }
|
||||
}
|
||||
}
|
||||
|
||||
// Narrowings are not preserved when assignments occur in inner functions
|
||||
|
||||
function f3(x: string | number) {
|
||||
action(() => { x = "abc" });
|
||||
x = 42;
|
||||
action(() => { x /* string | number */ });
|
||||
}
|
||||
|
||||
// Assignment effects in compoud statements extend to the entire statement
|
||||
|
||||
function f4(cond: () => boolean) {
|
||||
let x: string | number = 0;
|
||||
while (cond()) {
|
||||
x = "abc";
|
||||
action(() => { x /* string | number */ });
|
||||
x = 42;
|
||||
action(() => { x /* string | number */ });
|
||||
}
|
||||
action(() => { x /* number */ });
|
||||
}
|
||||
|
||||
function f5(x: string | number, cond: () => boolean) {
|
||||
if (cond()) {
|
||||
x = 1;
|
||||
action(() => { x /* string | number */ });
|
||||
}
|
||||
else {
|
||||
x = 2;
|
||||
action(() => { x /* string | number */ });
|
||||
}
|
||||
action(() => { x /* number */ });
|
||||
}
|
||||
|
||||
function f5a(cond: boolean) {
|
||||
if (cond) {
|
||||
let x: number | undefined;
|
||||
x = 1;
|
||||
action(() => { x /* number */ });
|
||||
}
|
||||
else {
|
||||
let x: number | undefined;
|
||||
x = 2;
|
||||
action(() => { x /* number */ });
|
||||
}
|
||||
}
|
||||
|
||||
function f5b() {
|
||||
for (let x = 0; x < 10; x++) {
|
||||
if (x === 1 || x === 2) {
|
||||
action(() => { x /* 1 | 2 */ })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implicit any variables have a known type following last assignment
|
||||
|
||||
function f6() {
|
||||
let x;
|
||||
~
|
||||
!!! error TS7034: Variable 'x' implicitly has type 'any' in some locations where its type cannot be determined.
|
||||
x = "abc";
|
||||
action(() => { x }); // Error
|
||||
~
|
||||
!!! error TS7005: Variable 'x' implicitly has an 'any' type.
|
||||
x = 42;
|
||||
action(() => { x /* number */ });
|
||||
}
|
||||
|
||||
// Narrowings on catch variables are preserved past last assignment
|
||||
|
||||
function f7() {
|
||||
try {
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Error) {
|
||||
let f = () => { e /* Error */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Narrowings are not preserved for global variables
|
||||
|
||||
let g: string | number;
|
||||
g = "abc";
|
||||
action(() => { g /* string | number */ });
|
||||
|
||||
// Narrowings are not preserved for exported namespace members
|
||||
|
||||
namespace Foo {
|
||||
export let x: string | number;
|
||||
x = "abc";
|
||||
action(() => { x /* string | number */ });
|
||||
let y: string | number;
|
||||
y = "abc";
|
||||
action(() => { y /* string */ });
|
||||
}
|
||||
|
||||
// Repros from #35124
|
||||
|
||||
function f10() {
|
||||
let i: number | undefined;
|
||||
i = 0;
|
||||
return (k: number) => k === i + 1;
|
||||
}
|
||||
|
||||
function makeAdder(n?: number) {
|
||||
n ??= 0;
|
||||
return (m: number) => n + m;
|
||||
}
|
||||
|
||||
function f11() {
|
||||
let r;
|
||||
r = "b";
|
||||
() => r;
|
||||
}
|
||||
|
||||
// Repro from #52104
|
||||
|
||||
function f12() {
|
||||
const fooMap: Map<string,Array<number>> = new Map()
|
||||
const values = [1, 2, 3, 4, 5];
|
||||
let foo = fooMap.get("a");
|
||||
if (foo == null) {
|
||||
foo = [];
|
||||
}
|
||||
values.forEach(v => foo.push(v));
|
||||
}
|
||||
|
||||
365
tests/baselines/reference/narrowingPastLastAssignment.symbols
Normal file
365
tests/baselines/reference/narrowingPastLastAssignment.symbols
Normal file
@ -0,0 +1,365 @@
|
||||
//// [tests/cases/compiler/narrowingPastLastAssignment.ts] ////
|
||||
|
||||
=== narrowingPastLastAssignment.ts ===
|
||||
function action(f: Function) {}
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0))
|
||||
>f : Symbol(f, Decl(narrowingPastLastAssignment.ts, 0, 16))
|
||||
>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.esnext.decorators.d.ts, --, --))
|
||||
|
||||
// Narrowings are preserved in closures created past last assignment
|
||||
|
||||
function f1(x: string | number) {
|
||||
>f1 : Symbol(f1, Decl(narrowingPastLastAssignment.ts, 0, 31))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 4, 12))
|
||||
|
||||
x = "abc";
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 4, 12))
|
||||
|
||||
action(() => { x /* string | number */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 4, 12))
|
||||
|
||||
x = 42;
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 4, 12))
|
||||
|
||||
action(() => { x /* number */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 4, 12))
|
||||
}
|
||||
|
||||
// Narrowings are not preserved in inner function and class declarations (due to hoisting)
|
||||
|
||||
function f2() {
|
||||
>f2 : Symbol(f2, Decl(narrowingPastLastAssignment.ts, 9, 1))
|
||||
|
||||
let x: string | number;
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 14, 7))
|
||||
|
||||
x = 42;
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 14, 7))
|
||||
|
||||
let a = () => { x /* number */ };
|
||||
>a : Symbol(a, Decl(narrowingPastLastAssignment.ts, 16, 7))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 14, 7))
|
||||
|
||||
let f = function() { x /* number */ };
|
||||
>f : Symbol(f, Decl(narrowingPastLastAssignment.ts, 17, 7))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 14, 7))
|
||||
|
||||
let C = class {
|
||||
>C : Symbol(C, Decl(narrowingPastLastAssignment.ts, 18, 7))
|
||||
|
||||
foo() { x /* number */ }
|
||||
>foo : Symbol(C.foo, Decl(narrowingPastLastAssignment.ts, 18, 19))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 14, 7))
|
||||
|
||||
};
|
||||
let o = {
|
||||
>o : Symbol(o, Decl(narrowingPastLastAssignment.ts, 21, 7))
|
||||
|
||||
foo() { x /* number */ }
|
||||
>foo : Symbol(foo, Decl(narrowingPastLastAssignment.ts, 21, 13))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 14, 7))
|
||||
|
||||
};
|
||||
function g() { x /* string | number */ }
|
||||
>g : Symbol(g, Decl(narrowingPastLastAssignment.ts, 23, 6))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 14, 7))
|
||||
|
||||
class A {
|
||||
>A : Symbol(A, Decl(narrowingPastLastAssignment.ts, 24, 44))
|
||||
|
||||
foo() { x /* string | number */ }
|
||||
>foo : Symbol(A.foo, Decl(narrowingPastLastAssignment.ts, 25, 13))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 14, 7))
|
||||
}
|
||||
}
|
||||
|
||||
// Narrowings are not preserved when assignments occur in inner functions
|
||||
|
||||
function f3(x: string | number) {
|
||||
>f3 : Symbol(f3, Decl(narrowingPastLastAssignment.ts, 28, 1))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 32, 12))
|
||||
|
||||
action(() => { x = "abc" });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 32, 12))
|
||||
|
||||
x = 42;
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 32, 12))
|
||||
|
||||
action(() => { x /* string | number */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 32, 12))
|
||||
}
|
||||
|
||||
// Assignment effects in compoud statements extend to the entire statement
|
||||
|
||||
function f4(cond: () => boolean) {
|
||||
>f4 : Symbol(f4, Decl(narrowingPastLastAssignment.ts, 36, 1))
|
||||
>cond : Symbol(cond, Decl(narrowingPastLastAssignment.ts, 40, 12))
|
||||
|
||||
let x: string | number = 0;
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 41, 7))
|
||||
|
||||
while (cond()) {
|
||||
>cond : Symbol(cond, Decl(narrowingPastLastAssignment.ts, 40, 12))
|
||||
|
||||
x = "abc";
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 41, 7))
|
||||
|
||||
action(() => { x /* string | number */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 41, 7))
|
||||
|
||||
x = 42;
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 41, 7))
|
||||
|
||||
action(() => { x /* string | number */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 41, 7))
|
||||
}
|
||||
action(() => { x /* number */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 41, 7))
|
||||
}
|
||||
|
||||
function f5(x: string | number, cond: () => boolean) {
|
||||
>f5 : Symbol(f5, Decl(narrowingPastLastAssignment.ts, 49, 1))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 51, 12))
|
||||
>cond : Symbol(cond, Decl(narrowingPastLastAssignment.ts, 51, 31))
|
||||
|
||||
if (cond()) {
|
||||
>cond : Symbol(cond, Decl(narrowingPastLastAssignment.ts, 51, 31))
|
||||
|
||||
x = 1;
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 51, 12))
|
||||
|
||||
action(() => { x /* string | number */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 51, 12))
|
||||
}
|
||||
else {
|
||||
x = 2;
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 51, 12))
|
||||
|
||||
action(() => { x /* string | number */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 51, 12))
|
||||
}
|
||||
action(() => { x /* number */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 51, 12))
|
||||
}
|
||||
|
||||
function f5a(cond: boolean) {
|
||||
>f5a : Symbol(f5a, Decl(narrowingPastLastAssignment.ts, 61, 1))
|
||||
>cond : Symbol(cond, Decl(narrowingPastLastAssignment.ts, 63, 13))
|
||||
|
||||
if (cond) {
|
||||
>cond : Symbol(cond, Decl(narrowingPastLastAssignment.ts, 63, 13))
|
||||
|
||||
let x: number | undefined;
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 65, 11))
|
||||
|
||||
x = 1;
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 65, 11))
|
||||
|
||||
action(() => { x /* number */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 65, 11))
|
||||
}
|
||||
else {
|
||||
let x: number | undefined;
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 70, 11))
|
||||
|
||||
x = 2;
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 70, 11))
|
||||
|
||||
action(() => { x /* number */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 70, 11))
|
||||
}
|
||||
}
|
||||
|
||||
function f5b() {
|
||||
>f5b : Symbol(f5b, Decl(narrowingPastLastAssignment.ts, 74, 1))
|
||||
|
||||
for (let x = 0; x < 10; x++) {
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 77, 12))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 77, 12))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 77, 12))
|
||||
|
||||
if (x === 1 || x === 2) {
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 77, 12))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 77, 12))
|
||||
|
||||
action(() => { x /* 1 | 2 */ })
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 77, 12))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implicit any variables have a known type following last assignment
|
||||
|
||||
function f6() {
|
||||
>f6 : Symbol(f6, Decl(narrowingPastLastAssignment.ts, 82, 1))
|
||||
|
||||
let x;
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 87, 7))
|
||||
|
||||
x = "abc";
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 87, 7))
|
||||
|
||||
action(() => { x }); // Error
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 87, 7))
|
||||
|
||||
x = 42;
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 87, 7))
|
||||
|
||||
action(() => { x /* number */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 87, 7))
|
||||
}
|
||||
|
||||
// Narrowings on catch variables are preserved past last assignment
|
||||
|
||||
function f7() {
|
||||
>f7 : Symbol(f7, Decl(narrowingPastLastAssignment.ts, 92, 1))
|
||||
|
||||
try {
|
||||
}
|
||||
catch (e) {
|
||||
>e : Symbol(e, Decl(narrowingPastLastAssignment.ts, 99, 11))
|
||||
|
||||
if (e instanceof Error) {
|
||||
>e : Symbol(e, Decl(narrowingPastLastAssignment.ts, 99, 11))
|
||||
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2022.error.d.ts, --, --))
|
||||
|
||||
let f = () => { e /* Error */ }
|
||||
>f : Symbol(f, Decl(narrowingPastLastAssignment.ts, 101, 15))
|
||||
>e : Symbol(e, Decl(narrowingPastLastAssignment.ts, 99, 11))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Narrowings are not preserved for global variables
|
||||
|
||||
let g: string | number;
|
||||
>g : Symbol(g, Decl(narrowingPastLastAssignment.ts, 108, 3))
|
||||
|
||||
g = "abc";
|
||||
>g : Symbol(g, Decl(narrowingPastLastAssignment.ts, 108, 3))
|
||||
|
||||
action(() => { g /* string | number */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0))
|
||||
>g : Symbol(g, Decl(narrowingPastLastAssignment.ts, 108, 3))
|
||||
|
||||
// Narrowings are not preserved for exported namespace members
|
||||
|
||||
namespace Foo {
|
||||
>Foo : Symbol(Foo, Decl(narrowingPastLastAssignment.ts, 110, 42))
|
||||
|
||||
export let x: string | number;
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 115, 14))
|
||||
|
||||
x = "abc";
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 115, 14))
|
||||
|
||||
action(() => { x /* string | number */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0))
|
||||
>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 115, 14))
|
||||
|
||||
let y: string | number;
|
||||
>y : Symbol(y, Decl(narrowingPastLastAssignment.ts, 118, 7))
|
||||
|
||||
y = "abc";
|
||||
>y : Symbol(y, Decl(narrowingPastLastAssignment.ts, 118, 7))
|
||||
|
||||
action(() => { y /* string */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0))
|
||||
>y : Symbol(y, Decl(narrowingPastLastAssignment.ts, 118, 7))
|
||||
}
|
||||
|
||||
// Repros from #35124
|
||||
|
||||
function f10() {
|
||||
>f10 : Symbol(f10, Decl(narrowingPastLastAssignment.ts, 121, 1))
|
||||
|
||||
let i: number | undefined;
|
||||
>i : Symbol(i, Decl(narrowingPastLastAssignment.ts, 126, 7))
|
||||
|
||||
i = 0;
|
||||
>i : Symbol(i, Decl(narrowingPastLastAssignment.ts, 126, 7))
|
||||
|
||||
return (k: number) => k === i + 1;
|
||||
>k : Symbol(k, Decl(narrowingPastLastAssignment.ts, 128, 12))
|
||||
>k : Symbol(k, Decl(narrowingPastLastAssignment.ts, 128, 12))
|
||||
>i : Symbol(i, Decl(narrowingPastLastAssignment.ts, 126, 7))
|
||||
}
|
||||
|
||||
function makeAdder(n?: number) {
|
||||
>makeAdder : Symbol(makeAdder, Decl(narrowingPastLastAssignment.ts, 129, 1))
|
||||
>n : Symbol(n, Decl(narrowingPastLastAssignment.ts, 131, 19))
|
||||
|
||||
n ??= 0;
|
||||
>n : Symbol(n, Decl(narrowingPastLastAssignment.ts, 131, 19))
|
||||
|
||||
return (m: number) => n + m;
|
||||
>m : Symbol(m, Decl(narrowingPastLastAssignment.ts, 133, 12))
|
||||
>n : Symbol(n, Decl(narrowingPastLastAssignment.ts, 131, 19))
|
||||
>m : Symbol(m, Decl(narrowingPastLastAssignment.ts, 133, 12))
|
||||
}
|
||||
|
||||
function f11() {
|
||||
>f11 : Symbol(f11, Decl(narrowingPastLastAssignment.ts, 134, 1))
|
||||
|
||||
let r;
|
||||
>r : Symbol(r, Decl(narrowingPastLastAssignment.ts, 137, 7))
|
||||
|
||||
r = "b";
|
||||
>r : Symbol(r, Decl(narrowingPastLastAssignment.ts, 137, 7))
|
||||
|
||||
() => r;
|
||||
>r : Symbol(r, Decl(narrowingPastLastAssignment.ts, 137, 7))
|
||||
}
|
||||
|
||||
// Repro from #52104
|
||||
|
||||
function f12() {
|
||||
>f12 : Symbol(f12, Decl(narrowingPastLastAssignment.ts, 140, 1))
|
||||
|
||||
const fooMap: Map<string,Array<number>> = new Map()
|
||||
>fooMap : Symbol(fooMap, Decl(narrowingPastLastAssignment.ts, 145, 9))
|
||||
>Map : Symbol(Map, Decl(lib.es2015.collection.d.ts, --, --), Decl(lib.es2015.collection.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
|
||||
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --) ... and 4 more)
|
||||
>Map : Symbol(Map, Decl(lib.es2015.collection.d.ts, --, --), Decl(lib.es2015.collection.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
|
||||
|
||||
const values = [1, 2, 3, 4, 5];
|
||||
>values : Symbol(values, Decl(narrowingPastLastAssignment.ts, 146, 9))
|
||||
|
||||
let foo = fooMap.get("a");
|
||||
>foo : Symbol(foo, Decl(narrowingPastLastAssignment.ts, 147, 7))
|
||||
>fooMap.get : Symbol(Map.get, Decl(lib.es2015.collection.d.ts, --, --))
|
||||
>fooMap : Symbol(fooMap, Decl(narrowingPastLastAssignment.ts, 145, 9))
|
||||
>get : Symbol(Map.get, Decl(lib.es2015.collection.d.ts, --, --))
|
||||
|
||||
if (foo == null) {
|
||||
>foo : Symbol(foo, Decl(narrowingPastLastAssignment.ts, 147, 7))
|
||||
|
||||
foo = [];
|
||||
>foo : Symbol(foo, Decl(narrowingPastLastAssignment.ts, 147, 7))
|
||||
}
|
||||
values.forEach(v => foo.push(v));
|
||||
>values.forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --))
|
||||
>values : Symbol(values, Decl(narrowingPastLastAssignment.ts, 146, 9))
|
||||
>forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --))
|
||||
>v : Symbol(v, Decl(narrowingPastLastAssignment.ts, 151, 19))
|
||||
>foo.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
|
||||
>foo : Symbol(foo, Decl(narrowingPastLastAssignment.ts, 147, 7))
|
||||
>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
|
||||
>v : Symbol(v, Decl(narrowingPastLastAssignment.ts, 151, 19))
|
||||
}
|
||||
|
||||
476
tests/baselines/reference/narrowingPastLastAssignment.types
Normal file
476
tests/baselines/reference/narrowingPastLastAssignment.types
Normal file
@ -0,0 +1,476 @@
|
||||
//// [tests/cases/compiler/narrowingPastLastAssignment.ts] ////
|
||||
|
||||
=== narrowingPastLastAssignment.ts ===
|
||||
function action(f: Function) {}
|
||||
>action : (f: Function) => void
|
||||
>f : Function
|
||||
|
||||
// Narrowings are preserved in closures created past last assignment
|
||||
|
||||
function f1(x: string | number) {
|
||||
>f1 : (x: string | number) => void
|
||||
>x : string | number
|
||||
|
||||
x = "abc";
|
||||
>x = "abc" : "abc"
|
||||
>x : string | number
|
||||
>"abc" : "abc"
|
||||
|
||||
action(() => { x /* string | number */ });
|
||||
>action(() => { x /* string | number */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x /* string | number */ } : () => void
|
||||
>x : string | number
|
||||
|
||||
x = 42;
|
||||
>x = 42 : 42
|
||||
>x : string | number
|
||||
>42 : 42
|
||||
|
||||
action(() => { x /* number */ });
|
||||
>action(() => { x /* number */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x /* number */ } : () => void
|
||||
>x : number
|
||||
}
|
||||
|
||||
// Narrowings are not preserved in inner function and class declarations (due to hoisting)
|
||||
|
||||
function f2() {
|
||||
>f2 : () => void
|
||||
|
||||
let x: string | number;
|
||||
>x : string | number
|
||||
|
||||
x = 42;
|
||||
>x = 42 : 42
|
||||
>x : string | number
|
||||
>42 : 42
|
||||
|
||||
let a = () => { x /* number */ };
|
||||
>a : () => void
|
||||
>() => { x /* number */ } : () => void
|
||||
>x : number
|
||||
|
||||
let f = function() { x /* number */ };
|
||||
>f : () => void
|
||||
>function() { x /* number */ } : () => void
|
||||
>x : number
|
||||
|
||||
let C = class {
|
||||
>C : typeof C
|
||||
>class { foo() { x /* number */ } } : typeof C
|
||||
|
||||
foo() { x /* number */ }
|
||||
>foo : () => void
|
||||
>x : number
|
||||
|
||||
};
|
||||
let o = {
|
||||
>o : { foo(): void; }
|
||||
>{ foo() { x /* number */ } } : { foo(): void; }
|
||||
|
||||
foo() { x /* number */ }
|
||||
>foo : () => void
|
||||
>x : number
|
||||
|
||||
};
|
||||
function g() { x /* string | number */ }
|
||||
>g : () => void
|
||||
>x : string | number
|
||||
|
||||
class A {
|
||||
>A : A
|
||||
|
||||
foo() { x /* string | number */ }
|
||||
>foo : () => void
|
||||
>x : string | number
|
||||
}
|
||||
}
|
||||
|
||||
// Narrowings are not preserved when assignments occur in inner functions
|
||||
|
||||
function f3(x: string | number) {
|
||||
>f3 : (x: string | number) => void
|
||||
>x : string | number
|
||||
|
||||
action(() => { x = "abc" });
|
||||
>action(() => { x = "abc" }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x = "abc" } : () => void
|
||||
>x = "abc" : "abc"
|
||||
>x : string | number
|
||||
>"abc" : "abc"
|
||||
|
||||
x = 42;
|
||||
>x = 42 : 42
|
||||
>x : string | number
|
||||
>42 : 42
|
||||
|
||||
action(() => { x /* string | number */ });
|
||||
>action(() => { x /* string | number */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x /* string | number */ } : () => void
|
||||
>x : string | number
|
||||
}
|
||||
|
||||
// Assignment effects in compoud statements extend to the entire statement
|
||||
|
||||
function f4(cond: () => boolean) {
|
||||
>f4 : (cond: () => boolean) => void
|
||||
>cond : () => boolean
|
||||
|
||||
let x: string | number = 0;
|
||||
>x : string | number
|
||||
>0 : 0
|
||||
|
||||
while (cond()) {
|
||||
>cond() : boolean
|
||||
>cond : () => boolean
|
||||
|
||||
x = "abc";
|
||||
>x = "abc" : "abc"
|
||||
>x : string | number
|
||||
>"abc" : "abc"
|
||||
|
||||
action(() => { x /* string | number */ });
|
||||
>action(() => { x /* string | number */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x /* string | number */ } : () => void
|
||||
>x : string | number
|
||||
|
||||
x = 42;
|
||||
>x = 42 : 42
|
||||
>x : string | number
|
||||
>42 : 42
|
||||
|
||||
action(() => { x /* string | number */ });
|
||||
>action(() => { x /* string | number */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x /* string | number */ } : () => void
|
||||
>x : string | number
|
||||
}
|
||||
action(() => { x /* number */ });
|
||||
>action(() => { x /* number */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x /* number */ } : () => void
|
||||
>x : number
|
||||
}
|
||||
|
||||
function f5(x: string | number, cond: () => boolean) {
|
||||
>f5 : (x: string | number, cond: () => boolean) => void
|
||||
>x : string | number
|
||||
>cond : () => boolean
|
||||
|
||||
if (cond()) {
|
||||
>cond() : boolean
|
||||
>cond : () => boolean
|
||||
|
||||
x = 1;
|
||||
>x = 1 : 1
|
||||
>x : string | number
|
||||
>1 : 1
|
||||
|
||||
action(() => { x /* string | number */ });
|
||||
>action(() => { x /* string | number */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x /* string | number */ } : () => void
|
||||
>x : string | number
|
||||
}
|
||||
else {
|
||||
x = 2;
|
||||
>x = 2 : 2
|
||||
>x : string | number
|
||||
>2 : 2
|
||||
|
||||
action(() => { x /* string | number */ });
|
||||
>action(() => { x /* string | number */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x /* string | number */ } : () => void
|
||||
>x : string | number
|
||||
}
|
||||
action(() => { x /* number */ });
|
||||
>action(() => { x /* number */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x /* number */ } : () => void
|
||||
>x : number
|
||||
}
|
||||
|
||||
function f5a(cond: boolean) {
|
||||
>f5a : (cond: boolean) => void
|
||||
>cond : boolean
|
||||
|
||||
if (cond) {
|
||||
>cond : boolean
|
||||
|
||||
let x: number | undefined;
|
||||
>x : number | undefined
|
||||
|
||||
x = 1;
|
||||
>x = 1 : 1
|
||||
>x : number | undefined
|
||||
>1 : 1
|
||||
|
||||
action(() => { x /* number */ });
|
||||
>action(() => { x /* number */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x /* number */ } : () => void
|
||||
>x : number
|
||||
}
|
||||
else {
|
||||
let x: number | undefined;
|
||||
>x : number | undefined
|
||||
|
||||
x = 2;
|
||||
>x = 2 : 2
|
||||
>x : number | undefined
|
||||
>2 : 2
|
||||
|
||||
action(() => { x /* number */ });
|
||||
>action(() => { x /* number */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x /* number */ } : () => void
|
||||
>x : number
|
||||
}
|
||||
}
|
||||
|
||||
function f5b() {
|
||||
>f5b : () => void
|
||||
|
||||
for (let x = 0; x < 10; x++) {
|
||||
>x : number
|
||||
>0 : 0
|
||||
>x < 10 : boolean
|
||||
>x : number
|
||||
>10 : 10
|
||||
>x++ : number
|
||||
>x : number
|
||||
|
||||
if (x === 1 || x === 2) {
|
||||
>x === 1 || x === 2 : boolean
|
||||
>x === 1 : boolean
|
||||
>x : number
|
||||
>1 : 1
|
||||
>x === 2 : boolean
|
||||
>x : number
|
||||
>2 : 2
|
||||
|
||||
action(() => { x /* 1 | 2 */ })
|
||||
>action(() => { x /* 1 | 2 */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x /* 1 | 2 */ } : () => void
|
||||
>x : 1 | 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implicit any variables have a known type following last assignment
|
||||
|
||||
function f6() {
|
||||
>f6 : () => void
|
||||
|
||||
let x;
|
||||
>x : any
|
||||
|
||||
x = "abc";
|
||||
>x = "abc" : "abc"
|
||||
>x : any
|
||||
>"abc" : "abc"
|
||||
|
||||
action(() => { x }); // Error
|
||||
>action(() => { x }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x } : () => void
|
||||
>x : any
|
||||
|
||||
x = 42;
|
||||
>x = 42 : 42
|
||||
>x : any
|
||||
>42 : 42
|
||||
|
||||
action(() => { x /* number */ });
|
||||
>action(() => { x /* number */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x /* number */ } : () => void
|
||||
>x : number
|
||||
}
|
||||
|
||||
// Narrowings on catch variables are preserved past last assignment
|
||||
|
||||
function f7() {
|
||||
>f7 : () => void
|
||||
|
||||
try {
|
||||
}
|
||||
catch (e) {
|
||||
>e : unknown
|
||||
|
||||
if (e instanceof Error) {
|
||||
>e instanceof Error : boolean
|
||||
>e : unknown
|
||||
>Error : ErrorConstructor
|
||||
|
||||
let f = () => { e /* Error */ }
|
||||
>f : () => void
|
||||
>() => { e /* Error */ } : () => void
|
||||
>e : Error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Narrowings are not preserved for global variables
|
||||
|
||||
let g: string | number;
|
||||
>g : string | number
|
||||
|
||||
g = "abc";
|
||||
>g = "abc" : "abc"
|
||||
>g : string | number
|
||||
>"abc" : "abc"
|
||||
|
||||
action(() => { g /* string | number */ });
|
||||
>action(() => { g /* string | number */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { g /* string | number */ } : () => void
|
||||
>g : string | number
|
||||
|
||||
// Narrowings are not preserved for exported namespace members
|
||||
|
||||
namespace Foo {
|
||||
>Foo : typeof Foo
|
||||
|
||||
export let x: string | number;
|
||||
>x : string | number
|
||||
|
||||
x = "abc";
|
||||
>x = "abc" : "abc"
|
||||
>x : string | number
|
||||
>"abc" : "abc"
|
||||
|
||||
action(() => { x /* string | number */ });
|
||||
>action(() => { x /* string | number */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x /* string | number */ } : () => void
|
||||
>x : string | number
|
||||
|
||||
let y: string | number;
|
||||
>y : string | number
|
||||
|
||||
y = "abc";
|
||||
>y = "abc" : "abc"
|
||||
>y : string | number
|
||||
>"abc" : "abc"
|
||||
|
||||
action(() => { y /* string */ });
|
||||
>action(() => { y /* string */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { y /* string */ } : () => void
|
||||
>y : string
|
||||
}
|
||||
|
||||
// Repros from #35124
|
||||
|
||||
function f10() {
|
||||
>f10 : () => (k: number) => boolean
|
||||
|
||||
let i: number | undefined;
|
||||
>i : number | undefined
|
||||
|
||||
i = 0;
|
||||
>i = 0 : 0
|
||||
>i : number | undefined
|
||||
>0 : 0
|
||||
|
||||
return (k: number) => k === i + 1;
|
||||
>(k: number) => k === i + 1 : (k: number) => boolean
|
||||
>k : number
|
||||
>k === i + 1 : boolean
|
||||
>k : number
|
||||
>i + 1 : number
|
||||
>i : number
|
||||
>1 : 1
|
||||
}
|
||||
|
||||
function makeAdder(n?: number) {
|
||||
>makeAdder : (n?: number) => (m: number) => number
|
||||
>n : number | undefined
|
||||
|
||||
n ??= 0;
|
||||
>n ??= 0 : number
|
||||
>n : number | undefined
|
||||
>0 : 0
|
||||
|
||||
return (m: number) => n + m;
|
||||
>(m: number) => n + m : (m: number) => number
|
||||
>m : number
|
||||
>n + m : number
|
||||
>n : number
|
||||
>m : number
|
||||
}
|
||||
|
||||
function f11() {
|
||||
>f11 : () => void
|
||||
|
||||
let r;
|
||||
>r : any
|
||||
|
||||
r = "b";
|
||||
>r = "b" : "b"
|
||||
>r : any
|
||||
>"b" : "b"
|
||||
|
||||
() => r;
|
||||
>() => r : () => string
|
||||
>r : string
|
||||
}
|
||||
|
||||
// Repro from #52104
|
||||
|
||||
function f12() {
|
||||
>f12 : () => void
|
||||
|
||||
const fooMap: Map<string,Array<number>> = new Map()
|
||||
>fooMap : Map<string, number[]>
|
||||
>new Map() : Map<any, any>
|
||||
>Map : MapConstructor
|
||||
|
||||
const values = [1, 2, 3, 4, 5];
|
||||
>values : number[]
|
||||
>[1, 2, 3, 4, 5] : number[]
|
||||
>1 : 1
|
||||
>2 : 2
|
||||
>3 : 3
|
||||
>4 : 4
|
||||
>5 : 5
|
||||
|
||||
let foo = fooMap.get("a");
|
||||
>foo : number[] | undefined
|
||||
>fooMap.get("a") : number[] | undefined
|
||||
>fooMap.get : (key: string) => number[] | undefined
|
||||
>fooMap : Map<string, number[]>
|
||||
>get : (key: string) => number[] | undefined
|
||||
>"a" : "a"
|
||||
|
||||
if (foo == null) {
|
||||
>foo == null : boolean
|
||||
>foo : number[] | undefined
|
||||
|
||||
foo = [];
|
||||
>foo = [] : never[]
|
||||
>foo : number[] | undefined
|
||||
>[] : never[]
|
||||
}
|
||||
values.forEach(v => foo.push(v));
|
||||
>values.forEach(v => foo.push(v)) : void
|
||||
>values.forEach : (callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any) => void
|
||||
>values : number[]
|
||||
>forEach : (callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any) => void
|
||||
>v => foo.push(v) : (v: number) => number
|
||||
>v : number
|
||||
>foo.push(v) : number
|
||||
>foo.push : (...items: number[]) => number
|
||||
>foo : number[]
|
||||
>push : (...items: number[]) => number
|
||||
>v : number
|
||||
}
|
||||
|
||||
@ -0,0 +1,70 @@
|
||||
//// [tests/cases/compiler/narrowingPastLastAssignmentInModule.ts] ////
|
||||
|
||||
=== narrowingPastLastAssignmentInModule.ts ===
|
||||
function action(f: Function) {}
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignmentInModule.ts, 0, 0))
|
||||
>f : Symbol(f, Decl(narrowingPastLastAssignmentInModule.ts, 0, 16))
|
||||
>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.esnext.decorators.d.ts, --, --))
|
||||
|
||||
// Narrowings are not preserved for exported mutable variables
|
||||
|
||||
export let x1: string | number;
|
||||
>x1 : Symbol(x1, Decl(narrowingPastLastAssignmentInModule.ts, 4, 10))
|
||||
|
||||
x1 = "abc";
|
||||
>x1 : Symbol(x1, Decl(narrowingPastLastAssignmentInModule.ts, 4, 10))
|
||||
|
||||
action(() => { x1 /* string | number */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignmentInModule.ts, 0, 0))
|
||||
>x1 : Symbol(x1, Decl(narrowingPastLastAssignmentInModule.ts, 4, 10))
|
||||
|
||||
export { x2 };
|
||||
>x2 : Symbol(x2, Decl(narrowingPastLastAssignmentInModule.ts, 8, 8))
|
||||
|
||||
let x2: string | number;
|
||||
>x2 : Symbol(x2, Decl(narrowingPastLastAssignmentInModule.ts, 9, 3))
|
||||
|
||||
x2 = "abc";
|
||||
>x2 : Symbol(x2, Decl(narrowingPastLastAssignmentInModule.ts, 9, 3))
|
||||
|
||||
action(() => { x2 /* string | number */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignmentInModule.ts, 0, 0))
|
||||
>x2 : Symbol(x2, Decl(narrowingPastLastAssignmentInModule.ts, 9, 3))
|
||||
|
||||
export { x3 as foo };
|
||||
>x3 : Symbol(x3, Decl(narrowingPastLastAssignmentInModule.ts, 14, 3))
|
||||
>foo : Symbol(foo, Decl(narrowingPastLastAssignmentInModule.ts, 13, 8))
|
||||
|
||||
let x3: string | number;
|
||||
>x3 : Symbol(x3, Decl(narrowingPastLastAssignmentInModule.ts, 14, 3))
|
||||
|
||||
x3 = "abc";
|
||||
>x3 : Symbol(x3, Decl(narrowingPastLastAssignmentInModule.ts, 14, 3))
|
||||
|
||||
action(() => { x3 /* string | number */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignmentInModule.ts, 0, 0))
|
||||
>x3 : Symbol(x3, Decl(narrowingPastLastAssignmentInModule.ts, 14, 3))
|
||||
|
||||
let x4: string | number;
|
||||
>x4 : Symbol(x4, Decl(narrowingPastLastAssignmentInModule.ts, 18, 3))
|
||||
|
||||
x4 = "abc";
|
||||
>x4 : Symbol(x4, Decl(narrowingPastLastAssignmentInModule.ts, 18, 3))
|
||||
|
||||
action(() => { x4 /* string */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignmentInModule.ts, 0, 0))
|
||||
>x4 : Symbol(x4, Decl(narrowingPastLastAssignmentInModule.ts, 18, 3))
|
||||
|
||||
export default x4;
|
||||
>x4 : Symbol(x4, Decl(narrowingPastLastAssignmentInModule.ts, 18, 3))
|
||||
|
||||
let x5: string | number;
|
||||
>x5 : Symbol(x5, Decl(narrowingPastLastAssignmentInModule.ts, 23, 3))
|
||||
|
||||
x5 = "abc";
|
||||
>x5 : Symbol(x5, Decl(narrowingPastLastAssignmentInModule.ts, 23, 3))
|
||||
|
||||
action(() => { x5 /* string */ });
|
||||
>action : Symbol(action, Decl(narrowingPastLastAssignmentInModule.ts, 0, 0))
|
||||
>x5 : Symbol(x5, Decl(narrowingPastLastAssignmentInModule.ts, 23, 3))
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
//// [tests/cases/compiler/narrowingPastLastAssignmentInModule.ts] ////
|
||||
|
||||
=== narrowingPastLastAssignmentInModule.ts ===
|
||||
function action(f: Function) {}
|
||||
>action : (f: Function) => void
|
||||
>f : Function
|
||||
|
||||
// Narrowings are not preserved for exported mutable variables
|
||||
|
||||
export let x1: string | number;
|
||||
>x1 : string | number
|
||||
|
||||
x1 = "abc";
|
||||
>x1 = "abc" : "abc"
|
||||
>x1 : string | number
|
||||
>"abc" : "abc"
|
||||
|
||||
action(() => { x1 /* string | number */ });
|
||||
>action(() => { x1 /* string | number */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x1 /* string | number */ } : () => void
|
||||
>x1 : string | number
|
||||
|
||||
export { x2 };
|
||||
>x2 : string | number
|
||||
|
||||
let x2: string | number;
|
||||
>x2 : string | number
|
||||
|
||||
x2 = "abc";
|
||||
>x2 = "abc" : "abc"
|
||||
>x2 : string | number
|
||||
>"abc" : "abc"
|
||||
|
||||
action(() => { x2 /* string | number */ });
|
||||
>action(() => { x2 /* string | number */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x2 /* string | number */ } : () => void
|
||||
>x2 : string | number
|
||||
|
||||
export { x3 as foo };
|
||||
>x3 : string | number
|
||||
>foo : string | number
|
||||
|
||||
let x3: string | number;
|
||||
>x3 : string | number
|
||||
|
||||
x3 = "abc";
|
||||
>x3 = "abc" : "abc"
|
||||
>x3 : string | number
|
||||
>"abc" : "abc"
|
||||
|
||||
action(() => { x3 /* string | number */ });
|
||||
>action(() => { x3 /* string | number */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x3 /* string | number */ } : () => void
|
||||
>x3 : string | number
|
||||
|
||||
let x4: string | number;
|
||||
>x4 : string | number
|
||||
|
||||
x4 = "abc";
|
||||
>x4 = "abc" : "abc"
|
||||
>x4 : string | number
|
||||
>"abc" : "abc"
|
||||
|
||||
action(() => { x4 /* string */ });
|
||||
>action(() => { x4 /* string */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x4 /* string */ } : () => void
|
||||
>x4 : string
|
||||
|
||||
export default x4;
|
||||
>x4 : string | number
|
||||
|
||||
let x5: string | number;
|
||||
>x5 : string | number
|
||||
|
||||
x5 = "abc";
|
||||
>x5 = "abc" : "abc"
|
||||
>x5 : string | number
|
||||
>"abc" : "abc"
|
||||
|
||||
action(() => { x5 /* string */ });
|
||||
>action(() => { x5 /* string */ }) : void
|
||||
>action : (f: Function) => void
|
||||
>() => { x5 /* string */ } : () => void
|
||||
>x5 : string
|
||||
|
||||
@ -35,7 +35,7 @@ function f3(x: string | undefined) {
|
||||
}
|
||||
|
||||
function f4(x: string | undefined) {
|
||||
x = "abc"; // causes x to be considered non-const
|
||||
x = "abc";
|
||||
if (x) {
|
||||
doSomething(() => x.length);
|
||||
}
|
||||
|
||||
157
tests/cases/compiler/narrowingPastLastAssignment.ts
Normal file
157
tests/cases/compiler/narrowingPastLastAssignment.ts
Normal file
@ -0,0 +1,157 @@
|
||||
// @strict: true
|
||||
// @noEmit: true
|
||||
// @target: esnext
|
||||
|
||||
function action(f: Function) {}
|
||||
|
||||
// Narrowings are preserved in closures created past last assignment
|
||||
|
||||
function f1(x: string | number) {
|
||||
x = "abc";
|
||||
action(() => { x /* string | number */ });
|
||||
x = 42;
|
||||
action(() => { x /* number */ });
|
||||
}
|
||||
|
||||
// Narrowings are not preserved in inner function and class declarations (due to hoisting)
|
||||
|
||||
function f2() {
|
||||
let x: string | number;
|
||||
x = 42;
|
||||
let a = () => { x /* number */ };
|
||||
let f = function() { x /* number */ };
|
||||
let C = class {
|
||||
foo() { x /* number */ }
|
||||
};
|
||||
let o = {
|
||||
foo() { x /* number */ }
|
||||
};
|
||||
function g() { x /* string | number */ }
|
||||
class A {
|
||||
foo() { x /* string | number */ }
|
||||
}
|
||||
}
|
||||
|
||||
// Narrowings are not preserved when assignments occur in inner functions
|
||||
|
||||
function f3(x: string | number) {
|
||||
action(() => { x = "abc" });
|
||||
x = 42;
|
||||
action(() => { x /* string | number */ });
|
||||
}
|
||||
|
||||
// Assignment effects in compoud statements extend to the entire statement
|
||||
|
||||
function f4(cond: () => boolean) {
|
||||
let x: string | number = 0;
|
||||
while (cond()) {
|
||||
x = "abc";
|
||||
action(() => { x /* string | number */ });
|
||||
x = 42;
|
||||
action(() => { x /* string | number */ });
|
||||
}
|
||||
action(() => { x /* number */ });
|
||||
}
|
||||
|
||||
function f5(x: string | number, cond: () => boolean) {
|
||||
if (cond()) {
|
||||
x = 1;
|
||||
action(() => { x /* string | number */ });
|
||||
}
|
||||
else {
|
||||
x = 2;
|
||||
action(() => { x /* string | number */ });
|
||||
}
|
||||
action(() => { x /* number */ });
|
||||
}
|
||||
|
||||
function f5a(cond: boolean) {
|
||||
if (cond) {
|
||||
let x: number | undefined;
|
||||
x = 1;
|
||||
action(() => { x /* number */ });
|
||||
}
|
||||
else {
|
||||
let x: number | undefined;
|
||||
x = 2;
|
||||
action(() => { x /* number */ });
|
||||
}
|
||||
}
|
||||
|
||||
function f5b() {
|
||||
for (let x = 0; x < 10; x++) {
|
||||
if (x === 1 || x === 2) {
|
||||
action(() => { x /* 1 | 2 */ })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implicit any variables have a known type following last assignment
|
||||
|
||||
function f6() {
|
||||
let x;
|
||||
x = "abc";
|
||||
action(() => { x }); // Error
|
||||
x = 42;
|
||||
action(() => { x /* number */ });
|
||||
}
|
||||
|
||||
// Narrowings on catch variables are preserved past last assignment
|
||||
|
||||
function f7() {
|
||||
try {
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Error) {
|
||||
let f = () => { e /* Error */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Narrowings are not preserved for global variables
|
||||
|
||||
let g: string | number;
|
||||
g = "abc";
|
||||
action(() => { g /* string | number */ });
|
||||
|
||||
// Narrowings are not preserved for exported namespace members
|
||||
|
||||
namespace Foo {
|
||||
export let x: string | number;
|
||||
x = "abc";
|
||||
action(() => { x /* string | number */ });
|
||||
let y: string | number;
|
||||
y = "abc";
|
||||
action(() => { y /* string */ });
|
||||
}
|
||||
|
||||
// Repros from #35124
|
||||
|
||||
function f10() {
|
||||
let i: number | undefined;
|
||||
i = 0;
|
||||
return (k: number) => k === i + 1;
|
||||
}
|
||||
|
||||
function makeAdder(n?: number) {
|
||||
n ??= 0;
|
||||
return (m: number) => n + m;
|
||||
}
|
||||
|
||||
function f11() {
|
||||
let r;
|
||||
r = "b";
|
||||
() => r;
|
||||
}
|
||||
|
||||
// Repro from #52104
|
||||
|
||||
function f12() {
|
||||
const fooMap: Map<string,Array<number>> = new Map()
|
||||
const values = [1, 2, 3, 4, 5];
|
||||
let foo = fooMap.get("a");
|
||||
if (foo == null) {
|
||||
foo = [];
|
||||
}
|
||||
values.forEach(v => foo.push(v));
|
||||
}
|
||||
30
tests/cases/compiler/narrowingPastLastAssignmentInModule.ts
Normal file
30
tests/cases/compiler/narrowingPastLastAssignmentInModule.ts
Normal file
@ -0,0 +1,30 @@
|
||||
// @strict: true
|
||||
// @noEmit: true
|
||||
// @target: esnext
|
||||
|
||||
function action(f: Function) {}
|
||||
|
||||
// Narrowings are not preserved for exported mutable variables
|
||||
|
||||
export let x1: string | number;
|
||||
x1 = "abc";
|
||||
action(() => { x1 /* string | number */ });
|
||||
|
||||
export { x2 };
|
||||
let x2: string | number;
|
||||
x2 = "abc";
|
||||
action(() => { x2 /* string | number */ });
|
||||
|
||||
export { x3 as foo };
|
||||
let x3: string | number;
|
||||
x3 = "abc";
|
||||
action(() => { x3 /* string | number */ });
|
||||
|
||||
let x4: string | number;
|
||||
x4 = "abc";
|
||||
action(() => { x4 /* string */ });
|
||||
export default x4;
|
||||
|
||||
let x5: string | number;
|
||||
x5 = "abc";
|
||||
action(() => { x5 /* string */ });
|
||||
@ -134,10 +134,10 @@ function f25(arg: { kind: 'foo', foo: string } | { kind: 'bar', bar: number }) {
|
||||
let obj = arg;
|
||||
const isFoo = obj.kind === 'foo';
|
||||
if (isFoo) {
|
||||
obj.foo; // Not narrowed because obj is mutable
|
||||
obj.foo;
|
||||
}
|
||||
else {
|
||||
obj.bar; // Not narrowed because obj is mutable
|
||||
obj.bar;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user