fix(44880): allow narrowing aliased conditions for catch variables (#45797)
This commit is contained in:
parent
4f8aa5239e
commit
40fa0c9f11
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,7 +138,8 @@ namespace Harness {
|
|||
"skipDefaultLibCheck",
|
||||
"preserveConstEnums",
|
||||
"skipLibCheck",
|
||||
"exactOptionalPropertyTypes"
|
||||
"exactOptionalPropertyTypes",
|
||||
"useUnknownInCatchVariables"
|
||||
];
|
||||
private fileName: string;
|
||||
private justName: string;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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, --, --))
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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, --, --))
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue