fix(44880): allow narrowing aliased conditions for catch variables (#45797)

This commit is contained in:
Oleksandr T 2021-09-14 20:05:36 +03:00 committed by GitHub
parent 4f8aa5239e
commit 40fa0c9f11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 453 additions and 14 deletions

View file

@ -23240,9 +23240,10 @@ namespace ts {
function isConstantReference(node: Node): boolean {
switch (node.kind) {
case SyntaxKind.Identifier:
case SyntaxKind.Identifier: {
const symbol = getResolvedSymbol(node as Identifier);
return isConstVariable(symbol) || !!symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration).kind === SyntaxKind.Parameter && !isParameterAssigned(symbol);
return isConstVariable(symbol) || isParameterOrCatchClauseVariable(symbol) && !isSymbolAssigned(symbol);
}
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
// The resolvedSymbol property is initialized by checkPropertyAccess or checkElementAccess before we get here.
@ -24376,37 +24377,38 @@ namespace ts {
node.kind === SyntaxKind.PropertyDeclaration)!;
}
// Check if a parameter is assigned anywhere within its declaring function.
function isParameterAssigned(symbol: Symbol) {
// Check if a parameter or catch variable is assigned anywhere
function isSymbolAssigned(symbol: Symbol) {
if (!symbol.valueDeclaration) {
return false;
}
const func = getRootDeclaration(symbol.valueDeclaration).parent as FunctionLikeDeclaration;
const links = getNodeLinks(func);
const parent = getRootDeclaration(symbol.valueDeclaration).parent;
const links = getNodeLinks(parent);
if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) {
links.flags |= NodeCheckFlags.AssignmentsMarked;
if (!hasParentWithAssignmentsMarked(func)) {
markParameterAssignments(func);
if (!hasParentWithAssignmentsMarked(parent)) {
markNodeAssignments(parent);
}
}
return symbol.isAssigned || false;
}
function hasParentWithAssignmentsMarked(node: Node) {
return !!findAncestor(node.parent, node => isFunctionLike(node) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked));
return !!findAncestor(node.parent, node =>
(isFunctionLike(node) || isCatchClause(node)) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked));
}
function markParameterAssignments(node: Node) {
function markNodeAssignments(node: Node) {
if (node.kind === SyntaxKind.Identifier) {
if (isAssignmentTarget(node)) {
const symbol = getResolvedSymbol(node as Identifier);
if (symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration).kind === SyntaxKind.Parameter) {
if (isParameterOrCatchClauseVariable(symbol)) {
symbol.isAssigned = true;
}
}
}
else {
forEachChild(node, markParameterAssignments);
forEachChild(node, markNodeAssignments);
}
}
@ -24644,7 +24646,7 @@ namespace ts {
// analysis to include the immediately enclosing function.
while (flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression ||
flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethodOrAccessor(flowContainer)) &&
(isConstVariable(localOrExportSymbol) && type !== autoArrayType || isParameter && !isParameterAssigned(localOrExportSymbol))) {
(isConstVariable(localOrExportSymbol) && type !== autoArrayType || isParameter && !isSymbolAssigned(localOrExportSymbol))) {
flowContainer = getControlFlowContainer(flowContainer);
}
// We only look for uninitialized variables in strict null checking mode, and only when we can analyze

View file

@ -7409,4 +7409,13 @@ namespace ts {
export function isInfinityOrNaNString(name: string | __String): boolean {
return name === "Infinity" || name === "-Infinity" || name === "NaN";
}
export function isCatchClauseVariableDeclaration(node: Node) {
return node.kind === SyntaxKind.VariableDeclaration && node.parent.kind === SyntaxKind.CatchClause;
}
export function isParameterOrCatchClauseVariable(symbol: Symbol) {
const declaration = symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration);
return !!declaration && (isParameter(declaration) || isCatchClauseVariableDeclaration(declaration));
}
}

View file

@ -138,7 +138,8 @@ namespace Harness {
"skipDefaultLibCheck",
"preserveConstEnums",
"skipLibCheck",
"exactOptionalPropertyTypes"
"exactOptionalPropertyTypes",
"useUnknownInCatchVariables"
];
private fileName: string;
private justName: string;

View file

@ -0,0 +1,51 @@
//// [controlFlowAliasingCatchVariables.ts]
try {}
catch (e) {
const isString = typeof e === 'string';
if (isString) {
e.toUpperCase(); // e string
}
if (typeof e === 'string') {
e.toUpperCase(); // e string
}
}
try {}
catch (e) {
const isString = typeof e === 'string';
e = 1;
if (isString) {
e.toUpperCase(); // e any/unknown
}
if (typeof e === 'string') {
e.toUpperCase(); // e string
}
}
//// [controlFlowAliasingCatchVariables.js]
try { }
catch (e) {
var isString = typeof e === 'string';
if (isString) {
e.toUpperCase(); // e string
}
if (typeof e === 'string') {
e.toUpperCase(); // e string
}
}
try { }
catch (e) {
var isString = typeof e === 'string';
e = 1;
if (isString) {
e.toUpperCase(); // e any/unknown
}
if (typeof e === 'string') {
e.toUpperCase(); // e string
}
}

View file

@ -0,0 +1,56 @@
=== tests/cases/conformance/controlFlow/controlFlowAliasingCatchVariables.ts ===
try {}
catch (e) {
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 1, 7))
const isString = typeof e === 'string';
>isString : Symbol(isString, Decl(controlFlowAliasingCatchVariables.ts, 2, 9))
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 1, 7))
if (isString) {
>isString : Symbol(isString, Decl(controlFlowAliasingCatchVariables.ts, 2, 9))
e.toUpperCase(); // e string
>e.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 1, 7))
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
}
if (typeof e === 'string') {
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 1, 7))
e.toUpperCase(); // e string
>e.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 1, 7))
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
}
}
try {}
catch (e) {
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 13, 7))
const isString = typeof e === 'string';
>isString : Symbol(isString, Decl(controlFlowAliasingCatchVariables.ts, 14, 9))
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 13, 7))
e = 1;
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 13, 7))
if (isString) {
>isString : Symbol(isString, Decl(controlFlowAliasingCatchVariables.ts, 14, 9))
e.toUpperCase(); // e any/unknown
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 13, 7))
}
if (typeof e === 'string') {
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 13, 7))
e.toUpperCase(); // e string
>e.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 13, 7))
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
}
}

View file

@ -0,0 +1,76 @@
=== tests/cases/conformance/controlFlow/controlFlowAliasingCatchVariables.ts ===
try {}
catch (e) {
>e : any
const isString = typeof e === 'string';
>isString : boolean
>typeof e === 'string' : boolean
>typeof e : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>e : any
>'string' : "string"
if (isString) {
>isString : boolean
e.toUpperCase(); // e string
>e.toUpperCase() : string
>e.toUpperCase : () => string
>e : string
>toUpperCase : () => string
}
if (typeof e === 'string') {
>typeof e === 'string' : boolean
>typeof e : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>e : any
>'string' : "string"
e.toUpperCase(); // e string
>e.toUpperCase() : string
>e.toUpperCase : () => string
>e : string
>toUpperCase : () => string
}
}
try {}
catch (e) {
>e : any
const isString = typeof e === 'string';
>isString : boolean
>typeof e === 'string' : boolean
>typeof e : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>e : any
>'string' : "string"
e = 1;
>e = 1 : 1
>e : any
>1 : 1
if (isString) {
>isString : boolean
e.toUpperCase(); // e any/unknown
>e.toUpperCase() : any
>e.toUpperCase : any
>e : any
>toUpperCase : any
}
if (typeof e === 'string') {
>typeof e === 'string' : boolean
>typeof e : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>e : any
>'string' : "string"
e.toUpperCase(); // e string
>e.toUpperCase() : string
>e.toUpperCase : () => string
>e : string
>toUpperCase : () => string
}
}

View file

@ -0,0 +1,33 @@
tests/cases/conformance/controlFlow/controlFlowAliasingCatchVariables.ts(20,11): error TS2339: Property 'toUpperCase' does not exist on type 'unknown'.
==== tests/cases/conformance/controlFlow/controlFlowAliasingCatchVariables.ts (1 errors) ====
try {}
catch (e) {
const isString = typeof e === 'string';
if (isString) {
e.toUpperCase(); // e string
}
if (typeof e === 'string') {
e.toUpperCase(); // e string
}
}
try {}
catch (e) {
const isString = typeof e === 'string';
e = 1;
if (isString) {
e.toUpperCase(); // e any/unknown
~~~~~~~~~~~
!!! error TS2339: Property 'toUpperCase' does not exist on type 'unknown'.
}
if (typeof e === 'string') {
e.toUpperCase(); // e string
}
}

View file

@ -0,0 +1,51 @@
//// [controlFlowAliasingCatchVariables.ts]
try {}
catch (e) {
const isString = typeof e === 'string';
if (isString) {
e.toUpperCase(); // e string
}
if (typeof e === 'string') {
e.toUpperCase(); // e string
}
}
try {}
catch (e) {
const isString = typeof e === 'string';
e = 1;
if (isString) {
e.toUpperCase(); // e any/unknown
}
if (typeof e === 'string') {
e.toUpperCase(); // e string
}
}
//// [controlFlowAliasingCatchVariables.js]
try { }
catch (e) {
var isString = typeof e === 'string';
if (isString) {
e.toUpperCase(); // e string
}
if (typeof e === 'string') {
e.toUpperCase(); // e string
}
}
try { }
catch (e) {
var isString = typeof e === 'string';
e = 1;
if (isString) {
e.toUpperCase(); // e any/unknown
}
if (typeof e === 'string') {
e.toUpperCase(); // e string
}
}

View file

@ -0,0 +1,56 @@
=== tests/cases/conformance/controlFlow/controlFlowAliasingCatchVariables.ts ===
try {}
catch (e) {
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 1, 7))
const isString = typeof e === 'string';
>isString : Symbol(isString, Decl(controlFlowAliasingCatchVariables.ts, 2, 9))
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 1, 7))
if (isString) {
>isString : Symbol(isString, Decl(controlFlowAliasingCatchVariables.ts, 2, 9))
e.toUpperCase(); // e string
>e.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 1, 7))
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
}
if (typeof e === 'string') {
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 1, 7))
e.toUpperCase(); // e string
>e.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 1, 7))
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
}
}
try {}
catch (e) {
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 13, 7))
const isString = typeof e === 'string';
>isString : Symbol(isString, Decl(controlFlowAliasingCatchVariables.ts, 14, 9))
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 13, 7))
e = 1;
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 13, 7))
if (isString) {
>isString : Symbol(isString, Decl(controlFlowAliasingCatchVariables.ts, 14, 9))
e.toUpperCase(); // e any/unknown
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 13, 7))
}
if (typeof e === 'string') {
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 13, 7))
e.toUpperCase(); // e string
>e.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
>e : Symbol(e, Decl(controlFlowAliasingCatchVariables.ts, 13, 7))
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
}
}

View file

@ -0,0 +1,76 @@
=== tests/cases/conformance/controlFlow/controlFlowAliasingCatchVariables.ts ===
try {}
catch (e) {
>e : unknown
const isString = typeof e === 'string';
>isString : boolean
>typeof e === 'string' : boolean
>typeof e : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>e : unknown
>'string' : "string"
if (isString) {
>isString : boolean
e.toUpperCase(); // e string
>e.toUpperCase() : string
>e.toUpperCase : () => string
>e : string
>toUpperCase : () => string
}
if (typeof e === 'string') {
>typeof e === 'string' : boolean
>typeof e : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>e : unknown
>'string' : "string"
e.toUpperCase(); // e string
>e.toUpperCase() : string
>e.toUpperCase : () => string
>e : string
>toUpperCase : () => string
}
}
try {}
catch (e) {
>e : unknown
const isString = typeof e === 'string';
>isString : boolean
>typeof e === 'string' : boolean
>typeof e : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>e : unknown
>'string' : "string"
e = 1;
>e = 1 : 1
>e : unknown
>1 : 1
if (isString) {
>isString : boolean
e.toUpperCase(); // e any/unknown
>e.toUpperCase() : any
>e.toUpperCase : any
>e : unknown
>toUpperCase : any
}
if (typeof e === 'string') {
>typeof e === 'string' : boolean
>typeof e : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>e : unknown
>'string' : "string"
e.toUpperCase(); // e string
>e.toUpperCase() : string
>e.toUpperCase : () => string
>e : string
>toUpperCase : () => string
}
}

View file

@ -0,0 +1,28 @@
// @useUnknownInCatchVariables: true,false
try {}
catch (e) {
const isString = typeof e === 'string';
if (isString) {
e.toUpperCase(); // e string
}
if (typeof e === 'string') {
e.toUpperCase(); // e string
}
}
try {}
catch (e) {
const isString = typeof e === 'string';
e = 1;
if (isString) {
e.toUpperCase(); // e any/unknown
}
if (typeof e === 'string') {
e.toUpperCase(); // e string
}
}