Skip to content

Commit

Permalink
Allow functions to redeclare vars and functions in function scopes
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert committed Nov 13, 2023
1 parent 53d6360 commit ad51fad
Show file tree
Hide file tree
Showing 12 changed files with 139 additions and 13 deletions.
7 changes: 0 additions & 7 deletions src/ast/scopes/CatchBodyScope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,6 @@ export default class CatchBodyScope extends ChildScope {
this.addHoistedVariable(name, declaredVariable);
return declaredVariable;
}
// Functions can never re-declare catch parameters
if (kind === VariableKind.function) {
const name = identifier.name;
if (this.hoistedVariables?.get(name)) {
context.error(logRedeclarationError(name), identifier.start);
}
}
return super.addDeclaration(identifier, context, init, kind);
}
}
46 changes: 46 additions & 0 deletions src/ast/scopes/FunctionBodyScope.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { AstContext } from '../../Module';
import { logRedeclarationError } from '../../utils/logs';
import type Identifier from '../nodes/Identifier';
import type { ExpressionEntity } from '../nodes/shared/Expression';
import { VariableKind } from '../nodes/shared/VariableKinds';
import LocalVariable from '../variables/LocalVariable';
import ChildScope from './ChildScope';
import type ParameterScope from './ParameterScope';

export default class FunctionBodyScope extends ChildScope {
constructor(
readonly parent: ParameterScope,
readonly context: AstContext
) {
super(parent, context);
}

// There is stuff that is only allowed in function scopes, i.e. functions can
// be redeclared, functions and var can redeclare each other
addDeclaration(
identifier: Identifier,
context: AstContext,
init: ExpressionEntity,
kind: VariableKind
): LocalVariable {
const name = identifier.name;
const existingVariable =
this.hoistedVariables?.get(name) || (this.variables.get(name) as LocalVariable);
if (existingVariable) {
const existingKind = existingVariable.kind;
if (
(kind === VariableKind.var || kind === VariableKind.function) &&
(existingKind === VariableKind.var ||
existingKind === VariableKind.function ||
existingKind === VariableKind.parameter)
) {
existingVariable.addDeclaration(identifier, init);
return existingVariable;
}
context.error(logRedeclarationError(name), identifier.start);
}
const newVariable = new LocalVariable(identifier.name, identifier, init, context, kind);
this.variables.set(name, newVariable);
return newVariable;
}
}
3 changes: 2 additions & 1 deletion src/ast/scopes/ParameterScope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { ExpressionEntity } from '../nodes/shared/Expression';
import ParameterVariable from '../variables/ParameterVariable';
import CatchBodyScope from './CatchBodyScope';
import ChildScope from './ChildScope';
import FunctionBodyScope from './FunctionBodyScope';
import type Scope from './Scope';

export default class ParameterScope extends ChildScope {
Expand All @@ -19,7 +20,7 @@ export default class ParameterScope extends ChildScope {
super(parent, context);
this.bodyScope = isCatchScope
? new CatchBodyScope(this, context)
: new ChildScope(this, context);
: new FunctionBodyScope(this, context);
}

/**
Expand Down
7 changes: 2 additions & 5 deletions src/ast/scopes/Scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default class Scope {
/*
Redeclaration rules:
- var can redeclare var
- in function scopes, function and var can redeclare function and var
- var is hoisted across scopes, function remains in the scope it is declared
- var and function can redeclare function parameters, but parameters cannot redeclare parameters
- function cannot redeclare catch scope parameters
Expand All @@ -34,11 +35,7 @@ export default class Scope {
this.hoistedVariables?.get(name) || (this.variables.get(name) as LocalVariable);
if (existingVariable) {
const existingKind = existingVariable.kind;
if (
(kind === VariableKind.var &&
(existingKind === VariableKind.var || existingKind === VariableKind.parameter)) ||
(kind === VariableKind.function && existingKind === VariableKind.parameter)
) {
if (kind === VariableKind.var && existingKind === VariableKind.var) {
existingVariable.addDeclaration(identifier, init);
return existingVariable;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const path = require('node:path');
const ID_MAIN = path.join(__dirname, 'main.js');

module.exports = defineTest({
description: 'throws when redeclaring a function binding as a function in a block scope',
error: {
code: 'REDECLARATION_ERROR',
frame: `
1: {
2: function foo() {}
3: function foo() {}
^
4: }`,
id: ID_MAIN,
loc: {
column: 10,
file: ID_MAIN,
line: 3
},
message: 'Identifier "foo" has already been declared',
pos: 31,
watchFiles: [ID_MAIN]
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
function foo() {}
function foo() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const path = require('node:path');
const ID_MAIN = path.join(__dirname, 'main.js');

module.exports = defineTest({
description: 'throws when redeclaring a top-level function binding as a function',
error: {
code: 'REDECLARATION_ERROR',
frame: `
1: function foo() {}
2: function foo() {}
^`,
id: ID_MAIN,
loc: {
column: 9,
file: ID_MAIN,
line: 2
},
message: 'Identifier "foo" has already been declared',
pos: 27,
watchFiles: [ID_MAIN]
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
function foo() {}
function foo() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const path = require('node:path');
const ID_MAIN = path.join(__dirname, 'main.js');

module.exports = defineTest({
description: 'throws when redeclaring a top-level var binding as a function',
error: {
code: 'REDECLARATION_ERROR',
frame: `
1: var foo;
2: function foo() {}
^`,
id: ID_MAIN,
loc: {
column: 9,
file: ID_MAIN,
line: 2
},
message: 'Identifier "foo" has already been declared',
pos: 18,
watchFiles: [ID_MAIN]
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
var foo;
function foo() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = defineTest({
description: 'allows to redeclare vars and functions as vars and functions in function scopes'
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function foo() {
var fn = 1;
function fn() {}
function fn() {}
var fn = 2;

assert.equal(fn, 2);
}

foo();

0 comments on commit ad51fad

Please sign in to comment.