Skip to content

Disallow let\const declarations in the same scope with var declarations. #2027

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

Merged
merged 5 commits into from
Feb 13, 2015
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,16 @@ module ts {
break;
}
case SyntaxKind.Block:
// do not treat function block a block-scope container
// all block-scope locals that reside in this block should go to the function locals.
// Otherwise this won't be considered as redeclaration of a block scoped local:
// function foo() {
// let x;
// var x;
// }
// 'var x' will be placed into the function locals and 'let x' - into the locals of the block
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you are saying that you want vars and lets to go to one place, because you want them to collide. The choice of function locals as opposed to block locals is arbitrary, it just has to be the same for both declaration kinds. Correct?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, i'd add a comment to that effect. I think using hte function as the location makes sense though, as that's where we put parameters. (we should also add tests for parameters/locals/lets (if they collide or not))

bindChildren(node, 0, /*isBlockScopeContainer*/ !isAnyFunction(node.parent));
break;
case SyntaxKind.CatchClause:
case SyntaxKind.ForStatement:
case SyntaxKind.ForInStatement:
Expand Down
53 changes: 42 additions & 11 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8198,32 +8198,61 @@ module ts {
}
}

function checkCollisionWithConstDeclarations(node: VariableLikeDeclaration) {
function checkVarDeclaredNamesNotShadowed(node: VariableDeclaration | BindingElement) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename this?

// - ScriptBody : StatementList
// It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList
// also occurs in the VarDeclaredNames of StatementList.

// - Block : { StatementList }
// It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList
// also occurs in the VarDeclaredNames of StatementList.

// Variable declarations are hoisted to the top of their function scope. They can shadow
// block scoped declarations, which bind tighter. this will not be flagged as duplicate definition
// by the binder as the declaration scope is different.
// A non-initialized declaration is a no-op as the block declaration will resolve before the var
// declaration. the problem is if the declaration has an initializer. this will act as a write to the
// block declared value. this is fine for let, but not const.
//
// Only consider declarations with initializers, uninitialized var declarations will not
// step on a const variable.
// step on a let/const variable.
// Do not consider let and const declarations, as duplicate block-scoped declarations
// are handled by the binder.
// We are only looking for var declarations that step on const declarations from a
// We are only looking for var declarations that step on let\const declarations from a
// different scope. e.g.:
// var x = 0;
// {
// const x = 0;
// var x = 0;
// const x = 0; // localDeclarationSymbol obtained after name resolution will correspond to this declaration
// var x = 0; // symbol for this declaration will be 'symbol'
// }
if (node.initializer && (getCombinedNodeFlags(node) & NodeFlags.BlockScoped) === 0) {
var symbol = getSymbolOfNode(node);
if (symbol.flags & SymbolFlags.FunctionScopedVariable) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can a block scoped declaration have a function scoped symbol?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh sorry didn't see the === 0

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So when would this not be true?

var localDeclarationSymbol = resolveName(node, (<Identifier>node.name).text, SymbolFlags.Variable, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined);
if (localDeclarationSymbol && localDeclarationSymbol !== symbol && localDeclarationSymbol.flags & SymbolFlags.BlockScopedVariable) {
if (getDeclarationFlagsFromSymbol(localDeclarationSymbol) & NodeFlags.Const) {
error(node, Diagnostics.Cannot_redeclare_block_scoped_variable_0, symbolToString(localDeclarationSymbol));
if (localDeclarationSymbol &&
localDeclarationSymbol !== symbol &&
localDeclarationSymbol.flags & SymbolFlags.BlockScopedVariable) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a code example where the relevant declarations are labelled with 'localDeclarationSymbol' and 'symbol'

if (getDeclarationFlagsFromSymbol(localDeclarationSymbol) & NodeFlags.BlockScoped) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would this not be true? You already checked the symbol flags


var varDeclList = getAncestor(localDeclarationSymbol.valueDeclaration, SyntaxKind.VariableDeclarationList);
var container =
varDeclList.parent.kind === SyntaxKind.VariableStatement &&
varDeclList.parent.parent;

// names of block-scoped and function scoped variables can collide only
// if block scoped variable is defined in the function\module\source file scope (because of variable hoisting)
var namesShareScope =
container &&
(container.kind === SyntaxKind.Block && isAnyFunction(container.parent) ||
(container.kind === SyntaxKind.ModuleBlock && container.kind === SyntaxKind.ModuleDeclaration) ||
container.kind === SyntaxKind.SourceFile);

// here we know that function scoped variable is shadowed by block scoped one
// if they are defined in the same scope - binder has already reported redeclaration error
// otherwise if variable has an initializer - show error that initialization will fail
// since LHS will be block scoped name instead of function scoped
if (!namesShareScope) {
var name = symbolToString(localDeclarationSymbol);
error(getErrorSpanForNode(node), Diagnostics.Cannot_initialize_outer_scoped_variable_0_in_the_same_scope_as_block_scoped_declaration_1, name, name);
}
}
}
}
Expand Down Expand Up @@ -8320,7 +8349,9 @@ module ts {
if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature) {
// We know we don't have a binding pattern or computed name here
checkExportsOnMergedDeclarations(node);
checkCollisionWithConstDeclarations(node);
if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What else could it be? We already checked that PropertyDeclaration and PropertySignature are false

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i.e. EnumMember or Parameter

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please note that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, but what if the binding element is in the parameter? Then you want to skip it, right?

checkVarDeclaredNamesNotShadowed(<VariableDeclaration | BindingElement>node);
}
checkCollisionWithCapturedSuperVariable(node, <Identifier>node.name);
checkCollisionWithCapturedThisVariable(node, <Identifier>node.name);
checkCollisionWithRequireExportsInGeneratedCode(node, <Identifier>node.name);
Expand Down
1 change: 1 addition & 0 deletions src/compiler/diagnosticInformationMap.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ module ts {
const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN: { code: 4087, category: DiagnosticCategory.Error, key: "'const' enum member initializer was evaluated to disallowed value 'NaN'." },
Property_0_does_not_exist_on_const_enum_1: { code: 4088, category: DiagnosticCategory.Error, key: "Property '{0}' does not exist on 'const' enum '{1}'." },
let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations: { code: 4089, category: DiagnosticCategory.Error, key: "'let' is not allowed to be used as a name in 'let' or 'const' declarations." },
Cannot_initialize_outer_scoped_variable_0_in_the_same_scope_as_block_scoped_declaration_1: { code: 4090, category: DiagnosticCategory.Error, key: "Cannot initialize outer scoped variable '{0}' in the same scope as block scoped declaration '{1}'." },
The_current_host_does_not_support_the_0_option: { code: 5001, category: DiagnosticCategory.Error, key: "The current host does not support the '{0}' option." },
Cannot_find_the_common_subdirectory_path_for_the_input_files: { code: 5009, category: DiagnosticCategory.Error, key: "Cannot find the common subdirectory path for the input files." },
Cannot_read_file_0_Colon_1: { code: 5012, category: DiagnosticCategory.Error, key: "Cannot read file '{0}': {1}" },
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1517,6 +1517,10 @@
"category": "Error",
"code": 4089
},
"Cannot initialize outer scoped variable '{0}' in the same scope as block scoped declaration '{1}'.": {
"category": "Error",
"code": 4090
},
"The current host does not support the '{0}' option.": {
"category": "Error",
"code": 5001
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
tests/cases/compiler/constDeclarationShadowedByVarDeclaration.ts(7,9): error TS2451: Cannot redeclare block-scoped variable 'x'.
tests/cases/compiler/constDeclarationShadowedByVarDeclaration.ts(15,13): error TS2451: Cannot redeclare block-scoped variable 'y'.
tests/cases/compiler/constDeclarationShadowedByVarDeclaration.ts(22,7): error TS2451: Cannot redeclare block-scoped variable 'z'.
tests/cases/compiler/constDeclarationShadowedByVarDeclaration.ts(7,9): error TS4090: Cannot initialize outer scoped variable 'x' in the same scope as block scoped declaration 'x'.
tests/cases/compiler/constDeclarationShadowedByVarDeclaration.ts(15,13): error TS4090: Cannot initialize outer scoped variable 'y' in the same scope as block scoped declaration 'y'.
tests/cases/compiler/constDeclarationShadowedByVarDeclaration.ts(22,7): error TS4090: Cannot initialize outer scoped variable 'z' in the same scope as block scoped declaration 'z'.


==== tests/cases/compiler/constDeclarationShadowedByVarDeclaration.ts (3 errors) ====
Expand All @@ -12,7 +12,7 @@ tests/cases/compiler/constDeclarationShadowedByVarDeclaration.ts(22,7): error TS

var x = 0;
~
!!! error TS2451: Cannot redeclare block-scoped variable 'x'.
!!! error TS4090: Cannot initialize outer scoped variable 'x' in the same scope as block scoped declaration 'x'.
}


Expand All @@ -22,7 +22,7 @@ tests/cases/compiler/constDeclarationShadowedByVarDeclaration.ts(22,7): error TS
{
var y = 0;
~
!!! error TS2451: Cannot redeclare block-scoped variable 'y'.
!!! error TS4090: Cannot initialize outer scoped variable 'y' in the same scope as block scoped declaration 'y'.
}
}

Expand All @@ -31,5 +31,5 @@ tests/cases/compiler/constDeclarationShadowedByVarDeclaration.ts(22,7): error TS
const z = 0;
var z = 0
~
!!! error TS2451: Cannot redeclare block-scoped variable 'z'.
!!! error TS4090: Cannot initialize outer scoped variable 'z' in the same scope as block scoped declaration 'z'.
}
118 changes: 118 additions & 0 deletions tests/baselines/reference/letAndVarRedeclaration.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
tests/cases/compiler/letAndVarRedeclaration.ts(2,5): error TS2451: Cannot redeclare block-scoped variable 'e0'.
tests/cases/compiler/letAndVarRedeclaration.ts(3,5): error TS2451: Cannot redeclare block-scoped variable 'e0'.
tests/cases/compiler/letAndVarRedeclaration.ts(4,10): error TS2451: Cannot redeclare block-scoped variable 'e0'.
tests/cases/compiler/letAndVarRedeclaration.ts(7,9): error TS2451: Cannot redeclare block-scoped variable 'x1'.
tests/cases/compiler/letAndVarRedeclaration.ts(8,9): error TS2451: Cannot redeclare block-scoped variable 'x1'.
tests/cases/compiler/letAndVarRedeclaration.ts(9,14): error TS2451: Cannot redeclare block-scoped variable 'x1'.
tests/cases/compiler/letAndVarRedeclaration.ts(13,9): error TS2451: Cannot redeclare block-scoped variable 'x'.
tests/cases/compiler/letAndVarRedeclaration.ts(15,13): error TS2451: Cannot redeclare block-scoped variable 'x'.
tests/cases/compiler/letAndVarRedeclaration.ts(18,18): error TS2451: Cannot redeclare block-scoped variable 'x'.
tests/cases/compiler/letAndVarRedeclaration.ts(23,9): error TS2451: Cannot redeclare block-scoped variable 'x2'.
tests/cases/compiler/letAndVarRedeclaration.ts(24,9): error TS2451: Cannot redeclare block-scoped variable 'x2'.
tests/cases/compiler/letAndVarRedeclaration.ts(25,14): error TS2451: Cannot redeclare block-scoped variable 'x2'.
tests/cases/compiler/letAndVarRedeclaration.ts(29,9): error TS2451: Cannot redeclare block-scoped variable 'x2'.
tests/cases/compiler/letAndVarRedeclaration.ts(31,13): error TS2451: Cannot redeclare block-scoped variable 'x2'.
tests/cases/compiler/letAndVarRedeclaration.ts(34,18): error TS2451: Cannot redeclare block-scoped variable 'x2'.
tests/cases/compiler/letAndVarRedeclaration.ts(38,5): error TS2451: Cannot redeclare block-scoped variable 'x11'.
tests/cases/compiler/letAndVarRedeclaration.ts(39,10): error TS2451: Cannot redeclare block-scoped variable 'x11'.
tests/cases/compiler/letAndVarRedeclaration.ts(43,9): error TS2451: Cannot redeclare block-scoped variable 'x11'.
tests/cases/compiler/letAndVarRedeclaration.ts(44,14): error TS2451: Cannot redeclare block-scoped variable 'x11'.
tests/cases/compiler/letAndVarRedeclaration.ts(49,9): error TS2451: Cannot redeclare block-scoped variable 'x11'.
tests/cases/compiler/letAndVarRedeclaration.ts(50,14): error TS2451: Cannot redeclare block-scoped variable 'x11'.


==== tests/cases/compiler/letAndVarRedeclaration.ts (21 errors) ====

let e0
~~
!!! error TS2451: Cannot redeclare block-scoped variable 'e0'.
var e0;
~~
!!! error TS2451: Cannot redeclare block-scoped variable 'e0'.
function e0() { }
~~
!!! error TS2451: Cannot redeclare block-scoped variable 'e0'.

function f0() {
let x1;
~~
!!! error TS2451: Cannot redeclare block-scoped variable 'x1'.
var x1;
~~
!!! error TS2451: Cannot redeclare block-scoped variable 'x1'.
function x1() { }
~~
!!! error TS2451: Cannot redeclare block-scoped variable 'x1'.
}

function f1() {
let x;
~
!!! error TS2451: Cannot redeclare block-scoped variable 'x'.
{
var x;
~
!!! error TS2451: Cannot redeclare block-scoped variable 'x'.
}
{
function x() { }
~
!!! error TS2451: Cannot redeclare block-scoped variable 'x'.
}
}

module M0 {
let x2;
~~
!!! error TS2451: Cannot redeclare block-scoped variable 'x2'.
var x2;
~~
!!! error TS2451: Cannot redeclare block-scoped variable 'x2'.
function x2() { }
~~
!!! error TS2451: Cannot redeclare block-scoped variable 'x2'.
}

module M1 {
let x2;
~~
!!! error TS2451: Cannot redeclare block-scoped variable 'x2'.
{
var x2;
~~
!!! error TS2451: Cannot redeclare block-scoped variable 'x2'.
}
{
function x2() { }
~~
!!! error TS2451: Cannot redeclare block-scoped variable 'x2'.
}
}

let x11;
~~~
!!! error TS2451: Cannot redeclare block-scoped variable 'x11'.
for (var x11; ;) {
~~~
!!! error TS2451: Cannot redeclare block-scoped variable 'x11'.
}

function f2() {
let x11;
~~~
!!! error TS2451: Cannot redeclare block-scoped variable 'x11'.
for (var x11; ;) {
~~~
!!! error TS2451: Cannot redeclare block-scoped variable 'x11'.
}
}

module M2 {
let x11;
~~~
!!! error TS2451: Cannot redeclare block-scoped variable 'x11'.
for (var x11; ;) {
~~~
!!! error TS2451: Cannot redeclare block-scoped variable 'x11'.
}
}
102 changes: 102 additions & 0 deletions tests/baselines/reference/letAndVarRedeclaration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//// [letAndVarRedeclaration.ts]

let e0
var e0;
function e0() { }

function f0() {
let x1;
var x1;
function x1() { }
}

function f1() {
let x;
{
var x;
}
{
function x() { }
}
}

module M0 {
let x2;
var x2;
function x2() { }
}

module M1 {
let x2;
{
var x2;
}
{
function x2() { }
}
}

let x11;
for (var x11; ;) {
}

function f2() {
let x11;
for (var x11; ;) {
}
}

module M2 {
let x11;
for (var x11; ;) {
}
}

//// [letAndVarRedeclaration.js]
let e0;
var e0;
function e0() { }
function f0() {
let x1;
var x1;
function x1() { }
}
function f1() {
let x;
{
var x;
}
{
function x() { }
}
}
var M0;
(function (M0) {
let x2;
var x2;
function x2() { }
})(M0 || (M0 = {}));
var M1;
(function (M1) {
let x2;
{
var x2;
}
{
function x2() { }
}
})(M1 || (M1 = {}));
let x11;
for (var x11;;) {
}
function f2() {
let x11;
for (var x11;;) {
}
}
var M2;
(function (M2) {
let x11;
for (var x11;;) {
}
})(M2 || (M2 = {}));
Loading