Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(50551): Destructuring assignment with var bypasses "variable is used before being assigned" check (2454) #50560

Merged
merged 4 commits into from Oct 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/compiler/checker.ts
Expand Up @@ -26290,7 +26290,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 @@ -26320,6 +26320,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;