Scope fix - default parameters are evaluated in the mixin scope, not the

caller scope. Fixes #973.
This commit is contained in:
Luke Page
2012-11-29 19:47:40 +00:00
parent 60b2aa9a77
commit 650073e4f1
3 changed files with 41 additions and 8 deletions

View File

@@ -96,7 +96,7 @@ tree.mixin.Definition.prototype = {
find: function () { return this.parent.find.apply(this, arguments) },
rulesets: function () { return this.parent.rulesets.apply(this) },
evalParams: function (env, args, evaldArguments) {
evalParams: function (env, mixinEnv, args, evaldArguments) {
var frame = new(tree.Ruleset)(null, []), varargs, arg, params = this.params.slice(0), i, j, val, name, isNamedFound, argIndex;
if (args) {
@@ -138,12 +138,19 @@ tree.mixin.Definition.prototype = {
varargs.push(args[j].value.eval(env));
}
frame.rules.unshift(new(tree.Rule)(name, new(tree.Expression)(varargs).eval(env)));
} else if (val = (arg && arg.value) || params[i].value) {
frame.rules.unshift(new(tree.Rule)(name, val.eval(env)));
evaldArguments[i] = val.eval(env)
} else {
throw { type: 'Runtime', message: "wrong number of arguments for " + this.name +
val = arg && arg.value;
if (val) {
val = val.eval(env);
} else if (params[i].value) {
val = params[i].value.eval(mixinEnv);
} else {
throw { type: 'Runtime', message: "wrong number of arguments for " + this.name +
' (' + args.length + ' for ' + this.arity + ')' };
}
frame.rules.unshift(new(tree.Rule)(name, val));
evaldArguments[i] = val;
}
}
@@ -158,7 +165,10 @@ tree.mixin.Definition.prototype = {
return frame;
},
eval: function (env, args, important) {
var _arguments = [], frame = this.evalParams(env, args, _arguments), context, rules, start, ruleset;
var _arguments = [],
mixinFrames = this.frames.concat(env.frames),
frame = this.evalParams(env, {frames: mixinFrames}, args, _arguments),
context, rules, start, ruleset;
frame.rules.unshift(new(tree.Rule)('@arguments', new(tree.Expression)(_arguments).eval(env)));
@@ -166,14 +176,14 @@ tree.mixin.Definition.prototype = {
this.parent.makeImportant.apply(this).rules : this.rules.slice(0);
ruleset = new(tree.Ruleset)(null, rules).eval({
frames: [this, frame].concat(this.frames, env.frames)
frames: [this, frame].concat(mixinFrames)
});
ruleset.originalRuleset = this;
return ruleset;
},
matchCondition: function (args, env) {
if (this.condition && !this.condition.eval({
frames: [this.evalParams(env, args, [])].concat(env.frames)
frames: [this.evalParams(env, {frames: this.frames.concat(env.frames)}, args, [])].concat(env.frames)
})) { return false }
return true;
},

View File

@@ -28,3 +28,8 @@
.testImported {
exists: true;
}
#allAreUsedHere {
default: 'top level';
scope: 'top level';
sub-scope-only: 'inside';
}

View File

@@ -59,3 +59,21 @@
.testImported {
.imported;
}
@parameterDefault: 'top level';
@anotherVariable: 'top level';
//mixin uses top-level variables
.mixinNoParam(@parameter: @parameterDefault) when (@parameter = 'top level') {
default: @parameter;
scope: @anotherVariable;
sub-scope-only: @subScopeOnly;
}
#allAreUsedHere {
//redefine top-level variables in different scope
@parameterDefault: 'inside';
@anotherVariable: 'inside';
@subScopeOnly: 'inside';
//use the mixin
.mixinNoParam();
}