+ " for function names, formals, and locals"),
reason="",
matches="function @name?(@params*) { @body* }",
substitutes="function @name?(@params*) { @headDecls?; @body* }")
public ParseTreeNode fire(ParseTreeNode node, Scope scope) {
FunctionConstructor fc;
if (node instanceof FunctionConstructor) {
fc = (FunctionConstructor) node;
} else if (node instanceof FunctionDeclaration) {
fc = ((FunctionDeclaration) node).getInitializer();
} else {
return NONE;
}
Map<String, ParseTreeNode> bindings = match(fc);
if (bindings != null) {
boolean isDeclaration = fc != node;
NameContext<String, ?> context = contexts.get(scope);
NameContext<String, ?> newContext = context.makeChildContext();
Scope newScope = Scope.fromFunctionConstructor(scope, fc);
List<Declaration> headDecls = Lists.newArrayList();
if (newScope.hasFreeThis()) {
NameContext.VarInfo<String, ?> vi;
try {
vi = newContext.declare("this", FilePosition.UNKNOWN);
} catch (NameContext.RedeclarationException ex) {
throw new SomethingWidgyHappenedError(
"Local variable unexpectedly not set", ex);
}
headDecls.add((Declaration) QuasiBuilder.substV(
"var @newName = this",
"newName", new Identifier(FilePosition.UNKNOWN, vi.newName)));
}
if (newScope.hasFreeArguments()) {
NameContext.VarInfo<String, ?> vi;
try {
vi = newContext.declare("arguments", FilePosition.UNKNOWN);
} catch (NameContext.RedeclarationException ex) {
throw new SomethingWidgyHappenedError(
"Local variable unexpectedly not set", ex);
}
headDecls.add((Declaration) QuasiBuilder.substV(
"var @newName = arguments",
"newName", new Identifier(FilePosition.UNKNOWN, vi.newName)));
}
for (String local : newScope.getLocals()) {
try {
newContext.declare(
local, newScope.getLocationOfDeclaration(local));
} catch (NameContext.RedeclarationException ex) {
// Might occur if a var named arguments is defined.
}
}
contexts.put(newScope, newContext);
Identifier name = fc.getIdentifier();
Identifier rewrittenName;
if (name.getName() == null) {
rewrittenName = name;
} else if (!isSynthetic(name)) {
rewrittenName = new Identifier(
name.getFilePosition(),
(isDeclaration ? context : newContext)
.lookup(name.getName()).newName);
} else {
rewrittenName = name;
}
List<FormalParam> newFormals = Lists.newArrayList();
for (FormalParam p : fc.getParams()) {
if (!isSynthetic(p.getIdentifier())) {
NameContext.VarInfo<String, ?> v
= newContext.lookup(p.getIdentifierName());
if (v == null) {
// Occurs when an invalid parameter appears,
// e.g., function (arguments) { ... }
try {
v = newContext.declare(
p.getIdentifierName(), p.getFilePosition());
} catch (NameContext.RedeclarationException ex) {
// If it was previously declared then v wouldn't be null.
throw new SomethingWidgyHappenedError(ex);
}
}
FormalParam newP = new FormalParam(new Identifier(
p.getFilePosition(), v.newName));
newFormals.add(newP);
} else {
newFormals.add(p);
}
}
// For a declaration, a name is normally introduced in both the
// scope containing the declaration, and the function body scope.
// We produce a declaration with the outer name, but in the inner
// scope the function name should refer to the function itself.
// The only exception is that if there is a local declaration
// inside the local scope that masks the function name, then we
// should not clobber it.
// Examples:
// (function f() {
// var f = 0;
// return f;
// })() === 0
// and
// (function f() {
// function f() { return 0; }
// return f();
// })() === 0
//
// Because the var f or inner function f masks the outer function f,
// the name "f" should not be considered to refer to the function
// within its body. The condition
// newScope.isFunction(name.getName())
// && !newScope.isDeclaredFunction(name.getName())
// checks that the name still refers to the outer function, not a
// variable or a different function that is declared within the
// body.
// The second clause is required because isDeclaredFunction implies
// isDeclaredFunction but we need to distinguish the two cases.
// For a declaration, a name is normally introduced in both the
// scope containing the declaration, and the function body scope.
// We produce a declaration with the outer name, but in the inner
// scope the function name should refer to the function itself.
// The only exception is that if there is a local declaration
// inside the local scope that masks the function name, then we
// should not clobber it.
// Examples:
// (function f() {
// var f = 0;
// return f;
// })() === 0
// and
// (function f() {
// function f() { return 0; }
// return f();
// })() === 0
//
// Because the var f or inner function f masks the outer function f,
// the name "f" should not be considered to refer to the function
// within its body. The condition
// newScope.isFunction(name.getName())
// && !newScope.isDeclaredFunction(name.getName())
// checks that the name still refers to the outer function, not a
// variable or a different function that is declared within the
// body.
// The second clause is required because isDeclaredFunction implies
// isFunction but we need to distinguish the two cases.
if (isDeclaration && !isSynthetic(name)
&& newScope.isFunction(name.getName())
&& !newScope.isDeclaredFunction(name.getName())) {
headDecls.add((Declaration) QuasiBuilder.substV(
"var @innerName = @outerName;",
"outerName", new Reference(rewrittenName),
"innerName", new Identifier(
name.getFilePosition(),
newContext.lookup(name.getName()).newName)));
// TODO(mikesamuel): skip if the self name is never used.
}
FunctionConstructor out = (FunctionConstructor) substV(
"name", rewrittenName,
"headDecls", optionalDeclarations(headDecls),
"params", new ParseTreeNodeContainer(newFormals),
"body", expandAll(bindings.get("body"), newScope));
return isDeclaration ? new FunctionDeclaration(out) : out;