Skip to content

Commit

Permalink
fix(50551): Destructuring assignment with var bypasses "variable is u…
Browse files Browse the repository at this point in the history
…sed before being assigned" check (2454) (#50560)

* fix(50551): handle destructuring variables used before assignment

* skip the error in binding elements that refer to the same destructuring

* fix binding element type
  • Loading branch information
a-tarasyuk committed Oct 20, 2022
1 parent 3f28fa1 commit 1ca99b3
Show file tree
Hide file tree
Showing 10 changed files with 276 additions and 1 deletion.
9 changes: 8 additions & 1 deletion src/compiler/checker.ts
Expand Up @@ -26289,7 +26289,7 @@ namespace ts {
// We only look for uninitialized variables in strict null checking mode, and only when we can analyze
// the entire control flow graph from the variable's declaration (i.e. when the flow container and
// declaration container are the same).
const assumeInitialized = isParameter || isAlias || isOuterVariable || isSpreadDestructuringAssignmentTarget || isModuleExports || isBindingElement(declaration) ||
const assumeInitialized = isParameter || isAlias || isOuterVariable || isSpreadDestructuringAssignmentTarget || isModuleExports || isSameScopedBindingElement(node, declaration) ||
type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void)) !== 0 ||
isInTypeQuery(node) || node.parent.kind === SyntaxKind.ExportSpecifier) ||
node.parent.kind === SyntaxKind.NonNullExpression ||
Expand Down Expand Up @@ -26319,6 +26319,13 @@ namespace ts {
return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType;
}

function isSameScopedBindingElement(node: Identifier, declaration: Declaration) {
if (isBindingElement(declaration)) {
const bindingElement = findAncestor(node, isBindingElement);
return bindingElement && getRootDeclaration(bindingElement) === getRootDeclaration(declaration);
}
}

function shouldMarkIdentifierAliasReferenced(node: Identifier): boolean {
const parent = node.parent;
if (parent) {
Expand Down
@@ -0,0 +1,39 @@
tests/cases/compiler/controlFlowDestructuringVariablesInTryCatch.ts(16,1): error TS2454: Variable 'a' is used before being assigned.
tests/cases/compiler/controlFlowDestructuringVariablesInTryCatch.ts(17,1): error TS2454: Variable 'b' is used before being assigned.
tests/cases/compiler/controlFlowDestructuringVariablesInTryCatch.ts(18,1): error TS2454: Variable 'c' is used before being assigned.
tests/cases/compiler/controlFlowDestructuringVariablesInTryCatch.ts(19,1): error TS2454: Variable 'd' is used before being assigned.
tests/cases/compiler/controlFlowDestructuringVariablesInTryCatch.ts(20,1): error TS2454: Variable 'e' is used before being assigned.


==== tests/cases/compiler/controlFlowDestructuringVariablesInTryCatch.ts (5 errors) ====
declare function f1(): string;
declare function f2(): [b: string];
declare function f3(): { c: string };

try {
var a = f1();
var [b] = f2();
var { c } = f3();

var [d = 1] = [];
var { e = 1 } = { };
} catch {
console.error("error");
}

a;
~
!!! error TS2454: Variable 'a' is used before being assigned.
b;
~
!!! error TS2454: Variable 'b' is used before being assigned.
c;
~
!!! error TS2454: Variable 'c' is used before being assigned.
d;
~
!!! error TS2454: Variable 'd' is used before being assigned.
e;
~
!!! error TS2454: Variable 'e' is used before being assigned.

@@ -0,0 +1,40 @@
//// [controlFlowDestructuringVariablesInTryCatch.ts]
declare function f1(): string;
declare function f2(): [b: string];
declare function f3(): { c: string };

try {
var a = f1();
var [b] = f2();
var { c } = f3();

var [d = 1] = [];
var { e = 1 } = { };
} catch {
console.error("error");
}

a;
b;
c;
d;
e;


//// [controlFlowDestructuringVariablesInTryCatch.js]
"use strict";
try {
var a = f1();
var b = f2()[0];
var c = f3().c;
var _a = [][0], d = _a === void 0 ? 1 : _a;
var _b = {}.e, e = _b === void 0 ? 1 : _b;
}
catch (_c) {
console.error("error");
}
a;
b;
c;
d;
e;
@@ -0,0 +1,52 @@
=== tests/cases/compiler/controlFlowDestructuringVariablesInTryCatch.ts ===
declare function f1(): string;
>f1 : Symbol(f1, Decl(controlFlowDestructuringVariablesInTryCatch.ts, 0, 0))

declare function f2(): [b: string];
>f2 : Symbol(f2, Decl(controlFlowDestructuringVariablesInTryCatch.ts, 0, 30))

declare function f3(): { c: string };
>f3 : Symbol(f3, Decl(controlFlowDestructuringVariablesInTryCatch.ts, 1, 35))
>c : Symbol(c, Decl(controlFlowDestructuringVariablesInTryCatch.ts, 2, 24))

try {
var a = f1();
>a : Symbol(a, Decl(controlFlowDestructuringVariablesInTryCatch.ts, 5, 7))
>f1 : Symbol(f1, Decl(controlFlowDestructuringVariablesInTryCatch.ts, 0, 0))

var [b] = f2();
>b : Symbol(b, Decl(controlFlowDestructuringVariablesInTryCatch.ts, 6, 9))
>f2 : Symbol(f2, Decl(controlFlowDestructuringVariablesInTryCatch.ts, 0, 30))

var { c } = f3();
>c : Symbol(c, Decl(controlFlowDestructuringVariablesInTryCatch.ts, 7, 9))
>f3 : Symbol(f3, Decl(controlFlowDestructuringVariablesInTryCatch.ts, 1, 35))

var [d = 1] = [];
>d : Symbol(d, Decl(controlFlowDestructuringVariablesInTryCatch.ts, 9, 9))

var { e = 1 } = { };
>e : Symbol(e, Decl(controlFlowDestructuringVariablesInTryCatch.ts, 10, 9))

} catch {
console.error("error");
>console.error : Symbol(Console.error, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>error : Symbol(Console.error, Decl(lib.dom.d.ts, --, --))
}

a;
>a : Symbol(a, Decl(controlFlowDestructuringVariablesInTryCatch.ts, 5, 7))

b;
>b : Symbol(b, Decl(controlFlowDestructuringVariablesInTryCatch.ts, 6, 9))

c;
>c : Symbol(c, Decl(controlFlowDestructuringVariablesInTryCatch.ts, 7, 9))

d;
>d : Symbol(d, Decl(controlFlowDestructuringVariablesInTryCatch.ts, 9, 9))

e;
>e : Symbol(e, Decl(controlFlowDestructuringVariablesInTryCatch.ts, 10, 9))

@@ -0,0 +1,61 @@
=== tests/cases/compiler/controlFlowDestructuringVariablesInTryCatch.ts ===
declare function f1(): string;
>f1 : () => string

declare function f2(): [b: string];
>f2 : () => [b: string]

declare function f3(): { c: string };
>f3 : () => { c: string; }
>c : string

try {
var a = f1();
>a : string
>f1() : string
>f1 : () => string

var [b] = f2();
>b : string
>f2() : [b: string]
>f2 : () => [b: string]

var { c } = f3();
>c : string
>f3() : { c: string; }
>f3 : () => { c: string; }

var [d = 1] = [];
>d : number
>1 : 1
>[] : []

var { e = 1 } = { };
>e : number
>1 : 1
>{ } : { e?: number | undefined; }

} catch {
console.error("error");
>console.error("error") : void
>console.error : (...data: any[]) => void
>console : Console
>error : (...data: any[]) => void
>"error" : "error"
}

a;
>a : string

b;
>b : string

c;
>c : string

d;
>d : number

e;
>e : number

@@ -0,0 +1,11 @@
//// [controlFlowInitializedDestructuringVariables.ts]
declare const obj: { a?: string, b?: number };
const {
a = "0",
b = +a,
} = obj;


//// [controlFlowInitializedDestructuringVariables.js]
"use strict";
var _a = obj.a, a = _a === void 0 ? "0" : _a, _b = obj.b, b = _b === void 0 ? +a : _b;
@@ -0,0 +1,17 @@
=== tests/cases/compiler/controlFlowInitializedDestructuringVariables.ts ===
declare const obj: { a?: string, b?: number };
>obj : Symbol(obj, Decl(controlFlowInitializedDestructuringVariables.ts, 0, 13))
>a : Symbol(a, Decl(controlFlowInitializedDestructuringVariables.ts, 0, 20))
>b : Symbol(b, Decl(controlFlowInitializedDestructuringVariables.ts, 0, 32))

const {
a = "0",
>a : Symbol(a, Decl(controlFlowInitializedDestructuringVariables.ts, 1, 7))

b = +a,
>b : Symbol(b, Decl(controlFlowInitializedDestructuringVariables.ts, 2, 12))
>a : Symbol(a, Decl(controlFlowInitializedDestructuringVariables.ts, 1, 7))

} = obj;
>obj : Symbol(obj, Decl(controlFlowInitializedDestructuringVariables.ts, 0, 13))

@@ -0,0 +1,19 @@
=== tests/cases/compiler/controlFlowInitializedDestructuringVariables.ts ===
declare const obj: { a?: string, b?: number };
>obj : { a?: string | undefined; b?: number | undefined; }
>a : string | undefined
>b : number | undefined

const {
a = "0",
>a : string
>"0" : "0"

b = +a,
>b : number
>+a : number
>a : string

} = obj;
>obj : { a?: string | undefined; b?: number | undefined; }

@@ -0,0 +1,22 @@
// @strict: true

declare function f1(): string;
declare function f2(): [b: string];
declare function f3(): { c: string };

try {
var a = f1();
var [b] = f2();
var { c } = f3();

var [d = 1] = [];
var { e = 1 } = { };
} catch {
console.error("error");
}

a;
b;
c;
d;
e;
@@ -0,0 +1,7 @@
// @strict: true

declare const obj: { a?: string, b?: number };
const {
a = "0",
b = +a,
} = obj;

0 comments on commit 1ca99b3

Please sign in to comment.