[Change]: Destructuring with non-final spread should still use rest syntax (#4517) (#4825)

* destructuring optimization

* refactor

* minor improvement, fix errors

* minor refactoring

* improvements

* Update output

* Update output
This commit is contained in:
zdenko
2018-01-16 04:24:21 +01:00
committed by Geoffrey Booth
parent 3ade25a32c
commit c1283ead45
4 changed files with 325 additions and 248 deletions

View File

@@ -11,7 +11,8 @@
// format that can be fed directly into [Jison](https://github.com/zaach/jison). These
// are read by jison in the `parser.lexer` function defined in coffeescript.coffee.
var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARABLE_LEFT_SIDE, COMPARE, COMPOUND_ASSIGN, CSX_ATTRIBUTE, CSX_FRAGMENT_IDENTIFIER, CSX_IDENTIFIER, CSX_INTERPOLATION, HERECOMMENT_ILLEGAL, HEREDOC_DOUBLE, HEREDOC_INDENT, HEREDOC_SINGLE, HEREGEX, HEREGEX_OMIT, HERE_JSTOKEN, IDENTIFIER, INDENTABLE_CLOSERS, INDEXABLE, INSIDE_CSX, INVERSES, JSTOKEN, JS_KEYWORDS, LEADING_BLANK_LINE, LINE_BREAK, LINE_CONTINUER, Lexer, MATH, MULTI_DENT, NOT_REGEX, NUMBER, OPERATOR, POSSIBLY_DIVISION, REGEX, REGEX_FLAGS, REGEX_ILLEGAL, REGEX_INVALID_ESCAPE, RELATION, RESERVED, Rewriter, SHIFT, SIMPLE_STRING_OMIT, STRICT_PROSCRIBED, STRING_DOUBLE, STRING_INVALID_ESCAPE, STRING_OMIT, STRING_SINGLE, STRING_START, TRAILING_BLANK_LINE, TRAILING_SPACES, UNARY, UNARY_MATH, UNFINISHED, UNICODE_CODE_POINT_ESCAPE, VALID_FLAGS, WHITESPACE, attachCommentsToNode, compact, count, invertLiterate, isForFrom, isUnassignable, key, locationDataToString, merge, repeat, starts, throwSyntaxError,
indexOf = [].indexOf;
indexOf = [].indexOf,
slice = [].slice;
({Rewriter, INVERSES} = require('./rewriter'));
@@ -1050,7 +1051,7 @@
if (braceInterpolator) {
// Turn the leading and trailing `{` and `}` into parentheses. Unnecessary
// parentheses will be removed later.
open = nested[0], close = nested[nested.length - 1];
[open] = nested, [close] = slice.call(nested, -1);
open[0] = open[1] = '(';
close[0] = close[1] = ')';
close.origin = ['', 'end of interpolation', close[2]];
@@ -1075,7 +1076,7 @@
length: delimiter.length
});
}
firstToken = tokens[0], lastToken = tokens[tokens.length - 1];
[firstToken] = tokens, [lastToken] = slice.call(tokens, -1);
firstToken[2].first_column -= delimiter.length;
if (lastToken[1].substr(-1) === '\n') {
lastToken[2].last_line += 1;
@@ -1176,7 +1177,7 @@
this.tokens.push(...tokensToPush);
}
if (lparen) {
lastToken = tokens[tokens.length - 1];
[lastToken] = slice.call(tokens, -1);
lparen.origin = [
'STRING',
null,
@@ -1202,7 +1203,7 @@
// correctly balanced throughout the course of the token stream.
pair(tag) {
var lastIndent, prev, ref, ref1, wanted;
ref = this.ends, prev = ref[ref.length - 1];
ref = this.ends, [prev] = slice.call(ref, -1);
if (tag !== (wanted = prev != null ? prev.tag : void 0)) {
if ('OUTDENT' !== wanted) {
this.error(`unmatched ${tag}`);
@@ -1212,7 +1213,7 @@
// el.click((event) ->
// el.hide())
ref1 = this.indents, lastIndent = ref1[ref1.length - 1];
ref1 = this.indents, [lastIndent] = slice.call(ref1, -1);
this.outdentToken(lastIndent, true);
return this.pair(tag);
}
@@ -1238,7 +1239,7 @@
lineCount = count(string, '\n');
column = this.chunkColumn;
if (lineCount > 0) {
ref = string.split('\n'), lastLine = ref[ref.length - 1];
ref = string.split('\n'), [lastLine] = slice.call(ref, -1);
column = lastLine.length;
} else {
column += string.length;
@@ -1279,14 +1280,14 @@
// Peek at the last tag in the token stream.
tag() {
var ref, token;
ref = this.tokens, token = ref[ref.length - 1];
ref = this.tokens, [token] = slice.call(ref, -1);
return token != null ? token[0] : void 0;
}
// Peek at the last value in the token stream.
value(useOrigin = false) {
var ref, ref1, token;
ref = this.tokens, token = ref[ref.length - 1];
ref = this.tokens, [token] = slice.call(ref, -1);
if (useOrigin && ((token != null ? token.origin : void 0) != null)) {
return (ref1 = token.origin) != null ? ref1[1] : void 0;
} else {

View File

@@ -7,7 +7,7 @@
var Access, Arr, Assign, AwaitReturn, Base, Block, BooleanLiteral, CSXTag, Call, Class, Code, CodeFragment, ComputedPropertyName, Elision, ExecutableClassBody, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, FuncGlyph, HereComment, HoistTarget, IdentifierLiteral, If, ImportClause, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, ImportSpecifierList, In, Index, InfinityLiteral, JS_FORBIDDEN, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, LineComment, Literal, ModuleDeclaration, ModuleSpecifier, ModuleSpecifierList, NEGATE, NO, NaNLiteral, NullLiteral, NumberLiteral, Obj, Op, Param, Parens, PassthroughLiteral, PropertyName, Range, RegexLiteral, RegexWithInterpolations, Return, SIMPLENUM, Scope, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, Super, SuperCall, Switch, TAB, THIS, TaggedTemplateCall, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addDataToNode, attachCommentsToNode, compact, del, ends, extend, flatten, fragmentsToText, hasLineComments, indentInitial, isLiteralArguments, isLiteralThis, isUnassignable, locationDataToString, merge, moveComments, multident, shouldCacheOrIsAssignable, some, starts, throwSyntaxError, unfoldSoak, unshiftAfterComments, utility,
indexOf = [].indexOf,
splice = [].splice,
slice = [].slice;
slice1 = [].slice;
Error.stackTraceLimit = 2e308;
@@ -721,7 +721,7 @@
fragments = node.compileToFragments(o);
if (!node.isStatement(o)) {
fragments = indentInitial(fragments, this);
lastFragment = fragments[fragments.length - 1];
[lastFragment] = slice1.call(fragments, -1);
if (!(lastFragment.code === '' || lastFragment.isComment)) {
fragments.push(this.makeCode(';'));
}
@@ -1380,7 +1380,7 @@
isSplice() {
var lastProp, ref1;
ref1 = this.properties, lastProp = ref1[ref1.length - 1];
ref1 = this.properties, [lastProp] = slice1.call(ref1, -1);
return lastProp instanceof Slice;
}
@@ -1404,7 +1404,7 @@
// `a()[b()] ?= c` -> `(_base = a())[_name = b()] ? _base[_name] = c`
cacheReference(o) {
var base, bref, name, nref, ref1;
ref1 = this.properties, name = ref1[ref1.length - 1];
ref1 = this.properties, [name] = slice1.call(ref1, -1);
if (this.properties.length < 2 && !this.base.shouldCache() && !(name != null ? name.shouldCache() : void 0)) {
return [this, this]; // `a` `a.b`
}
@@ -2615,7 +2615,7 @@
if (!this.variable) {
return null;
}
ref1 = this.variable.properties, tail = ref1[ref1.length - 1];
ref1 = this.variable.properties, [tail] = slice1.call(ref1, -1);
node = tail ? tail instanceof Access && tail.name : this.variable.base;
if (!(node instanceof IdentifierLiteral || node instanceof PropertyName)) {
return null;
@@ -3234,7 +3234,7 @@
// we've been assigned to, for correct internal references. If the variable
// has not been seen yet within the current scope, declare it.
compileNode(o) {
var answer, compiledName, hasSplat, isValue, j, name, objDestructAnswer, properties, prototype, ref1, ref2, ref3, ref4, ref5, val, varBase;
var answer, compiledName, hasSplat, isValue, name, objDestructAnswer, properties, prototype, ref1, ref2, ref3, ref4, ref5, val, varBase;
isValue = this.variable instanceof Value;
if (isValue) {
// When compiling `@variable`, remember if it is part of a function parameter.
@@ -3317,7 +3317,7 @@
if (this.value.isStatic) {
this.value.name = this.variable.properties[0];
} else if (((ref3 = this.variable.properties) != null ? ref3.length : void 0) >= 2) {
ref4 = this.variable.properties, properties = 3 <= ref4.length ? slice.call(ref4, 0, j = ref4.length - 2) : (j = 0, []), prototype = ref4[j++], name = ref4[j++];
ref4 = this.variable.properties, [...properties] = ref4, [prototype, name] = splice.call(properties, -2);
if (((ref5 = prototype.name) != null ? ref5.value : void 0) === 'prototype') {
this.value.name = name;
}
@@ -3464,7 +3464,7 @@
// Brief implementation of recursive pattern matching, when assigning array or
// object literals to a value. Peeks at their properties to assign inner names.
compileDestructuring(o) {
var acc, assigns, code, defaultValue, expandedIdx, fragments, i, idx, isObject, ivar, j, len1, message, name, obj, objects, olen, ref, rest, top, val, value, vvar, vvarText;
var assignObjects, assigns, code, compSlice, compSplice, complexObjects, expIdx, expans, fragments, hasObjAssigns, hasObjSpreads, i, isArray, isExpans, isObject, isSplat, leftObjs, loopObjects, obj, objIsUnassignable, objects, olen, processObjects, ref, refExp, restVar, rightObjs, slicer, splats, splatsAndExpans, top, value, vvar, vvarText;
top = o.level === LEVEL_TOP;
({value} = this);
({objects} = this.variable.base);
@@ -3484,53 +3484,45 @@
if (olen === 1 && obj instanceof Expansion) {
obj.error('Destructuring assignment has no target');
}
isObject = this.variable.isObject();
// Special case for when there's only one thing destructured off of
// something. `{a} = b`, `[a] = b`, `{a: b} = c`
if (top && olen === 1 && !(obj instanceof Splat)) {
// Pick the property straight off the value when theres just one to pick
// (no need to cache the value into a variable).
defaultValue = void 0;
if (obj instanceof Assign && obj.context === 'object') {
({
// A regular object pattern-match.
variable: {
base: idx
},
value: obj
} = obj);
if (obj instanceof Assign) {
defaultValue = obj.value;
obj = obj.variable;
// Count all `Splats`: [a, b, c..., d, e]
splats = (function() {
var j, len1, results;
results = [];
for (i = j = 0, len1 = objects.length; j < len1; i = ++j) {
obj = objects[i];
if (obj instanceof Splat) {
results.push(i);
}
} else {
if (obj instanceof Assign) {
defaultValue = obj.value;
obj = obj.variable;
}
return results;
})();
// Count all `Expansions`: [a, b, ..., c, d]
expans = (function() {
var j, len1, results;
results = [];
for (i = j = 0, len1 = objects.length; j < len1; i = ++j) {
obj = objects[i];
if (obj instanceof Expansion) {
results.push(i);
}
// A shorthand `{a, b, @c} = val` pattern-match.
// A regular array pattern-match.
idx = isObject ? obj.this ? obj.properties[0].name : new PropertyName(obj.unwrap().value) : new NumberLiteral(0);
}
acc = idx.unwrap() instanceof PropertyName;
value = new Value(value);
value.properties.push(new (acc ? Access : Index)(idx));
message = isUnassignable(obj.unwrap().value);
if (message) {
obj.error(message);
}
if (defaultValue) {
defaultValue.isDefaultValue = true;
value = new Op('?', value, defaultValue);
}
return new Assign(obj, value, null, {
param: this.param
}).compileToFragments(o, LEVEL_TOP);
return results;
})();
// Combine splats and expansions.
splatsAndExpans = [...splats, ...expans];
// Show error if there is more than one `Splat`, or `Expansion`.
// Examples: [a, b, c..., d, e, f...], [a, b, ..., c, d, ...], [a, b, ..., c, d, e...]
if (splatsAndExpans.length > 1) {
// Sort 'splatsAndExpans' so we can show error at first disallowed token.
objects[splatsAndExpans.sort()[1]].error("multiple splats/expansions are disallowed in an assignment");
}
isSplat = splats.length;
isExpans = expans.length;
isObject = this.variable.isObject();
isArray = this.variable.isArray();
vvar = value.compileToFragments(o, LEVEL_LIST);
vvarText = fragmentsToText(vvar);
assigns = [];
expandedIdx = false;
// At this point, there are several things to destructure. So the `fn()` in
// `{a, b} = fn()` must be cached, for example. Make vvar into a simple
// variable if it isnt already.
@@ -3540,100 +3532,176 @@
vvar = [this.makeCode(ref)];
vvarText = ref;
}
// And here comes the big loop that handles all of these cases:
// `[a, b] = c`
// `[a..., b] = c`
// `[..., a, b] = c`
// `[@a, b] = c`
// `[a = 1, b] = c`
// `{a, b} = c`
// `{@a, b} = c`
// `{a = 1, b} = c`
// etc.
for (i = j = 0, len1 = objects.length; j < len1; i = ++j) {
obj = objects[i];
idx = i;
if (!expandedIdx && obj instanceof Splat) {
name = obj.name.unwrap().value;
obj = obj.unwrap();
val = `${olen} <= ${vvarText}.length ? ${utility('slice', o)}.call(${vvarText}, ${i}`;
rest = olen - i - 1;
if (rest !== 0) {
ivar = o.scope.freeVariable('i', {
single: true
});
val += `, ${ivar} = ${vvarText}.length - ${rest}) : (${ivar} = ${i}, [])`;
} else {
val += ") : []";
slicer = function(type) {
return function(vvar, start, end = false) {
var args, slice;
args = [new IdentifierLiteral(vvar), new NumberLiteral(start)];
if (end) {
args.push(new NumberLiteral(end));
}
val = new Literal(val);
expandedIdx = `${ivar}++`;
} else if (!expandedIdx && obj instanceof Expansion) {
rest = olen - i - 1;
if (rest !== 0) {
if (rest === 1) {
expandedIdx = `${vvarText}.length - 1`;
} else {
ivar = o.scope.freeVariable('i', {
single: true
});
val = new Literal(`${ivar} = ${vvarText}.length - ${rest}`);
expandedIdx = `${ivar}++`;
assigns.push(val.compileToFragments(o, LEVEL_LIST));
}
slice = new Value(new IdentifierLiteral(utility(type, o)), [new Access(new PropertyName('call'))]);
return new Value(new Call(slice, args));
};
};
// Helper which outputs `[].slice` code.
compSlice = slicer("slice");
// Helper which outputs `[].splice` code.
compSplice = slicer("splice");
// Check if `objects` array contains object spread (`{a, r...}`), e.g. `[a, b, {c, r...}]`.
hasObjSpreads = function(objs) {
var j, len1, results;
results = [];
for (i = j = 0, len1 = objs.length; j < len1; i = ++j) {
obj = objs[i];
if (obj.base instanceof Obj && obj.base.hasSplat()) {
results.push(i);
}
continue;
} else {
if (obj instanceof Splat || obj instanceof Expansion) {
obj.error("multiple splats/expansions are disallowed in an assignment");
}
return results;
};
// Check if `objects` array contains any instance of `Assign`, e.g. {a:1}.
hasObjAssigns = function(objs) {
var j, len1, results;
results = [];
for (i = j = 0, len1 = objs.length; j < len1; i = ++j) {
obj = objs[i];
if (obj instanceof Assign && obj.context === 'object') {
results.push(i);
}
defaultValue = void 0;
}
return results;
};
// Check if `objects` array contains any unassignable object.
objIsUnassignable = function(objs) {
var j, len1;
for (j = 0, len1 = objs.length; j < len1; j++) {
obj = objs[j];
if (!obj.isAssignable()) {
return true;
}
}
return false;
};
// `objects` are complex when there is object spread ({a...}), object assign ({a:1}),
// unassignable object, or just a single node.
complexObjects = function(objs) {
return hasObjSpreads(objs).length || hasObjAssigns(objs).length || objIsUnassignable(objs) || olen === 1;
};
// "Complex" `objects` are processed in a loop.
// Examples: [a, b, {c, r...}, d], [a, ..., {b, r...}, c, d]
loopObjects = (objs, vvarTxt) => {
var acc, idx, j, len1, message, objSpreads, results, vval;
objSpreads = hasObjSpreads(objs);
results = [];
for (i = j = 0, len1 = objs.length; j < len1; i = ++j) {
obj = objs[i];
if (obj instanceof Elision) {
// `Elision` can be skipped.
continue;
}
// If `obj` is {a: 1}
if (obj instanceof Assign && obj.context === 'object') {
({
// A regular object pattern-match.
variable: {
base: idx
},
value: obj
value: vvar
} = obj);
if (obj instanceof Assign) {
defaultValue = obj.value;
obj = obj.variable;
if (vvar instanceof Assign) {
({
variable: vvar
} = vvar);
}
idx = vvar.this ? vvar.properties[0].name : new PropertyName(vvar.unwrap().value);
acc = idx.unwrap() instanceof PropertyName;
vval = new Value(value, [new (acc ? Access : Index)(idx)]);
} else {
if (obj instanceof Assign) {
defaultValue = obj.value;
obj = obj.variable;
}
// A shorthand `{a, b, @c} = val` pattern-match.
// A regular array pattern-match.
idx = isObject ? obj.this ? obj.properties[0].name : new PropertyName(obj.unwrap().value) : new Literal(expandedIdx || idx);
// `obj` is [a...], {a...} or a
vvar = (function() {
switch (false) {
case !(obj instanceof Splat):
return new Value(obj.name);
case indexOf.call(objSpreads, i) < 0:
return new Value(obj.base);
default:
return obj;
}
})();
vval = (function() {
switch (false) {
case !(obj instanceof Splat):
return compSlice(vvarTxt, i);
default:
return new Value(new Literal(vvarTxt), [new Index(new NumberLiteral(i))]);
}
})();
}
name = obj.unwrap().value;
acc = idx.unwrap() instanceof PropertyName;
val = new Value(new Literal(vvarText), [new (acc ? Access : Index)(idx)]);
if (defaultValue) {
defaultValue.isDefaultValue = true;
val = new Op('?', val, defaultValue);
}
}
if (name != null) {
message = isUnassignable(name);
message = isUnassignable(vvar.unwrap().value);
if (message) {
obj.error(message);
vvar.error(message);
}
}
if (!(obj instanceof Elision)) {
assigns.push(new Assign(obj, val, null, {
results.push(assigns.push(new Assign(vvar, vval, null, {
param: this.param,
subpattern: true
}).compileToFragments(o, LEVEL_LIST));
} else {
if (expandedIdx) {
// Output `Elision` only if `idx` is `i++`, e.g. expandedIdx.
assigns.push(idx.compileToFragments(o, LEVEL_LIST));
}
}).compileToFragments(o, LEVEL_LIST)));
}
return results;
};
// "Simple" `objects` can be split and compiled to arrays, [a, b, c] = arr, [a, b, c...] = arr
assignObjects = (objs, vvarTxt) => {
var vval;
vvar = new Value(new Arr(objs, true));
vval = vvarTxt instanceof Value ? vvarTxt : new Value(new Literal(vvarTxt));
return assigns.push(new Assign(vvar, vval, null, {
param: this.param,
subpattern: true
}).compileToFragments(o, LEVEL_LIST));
};
processObjects = function(objs, vvarTxt) {
if (complexObjects(objs)) {
return loopObjects(objs, vvarTxt);
} else {
return assignObjects(objs, vvarTxt);
}
};
// In case there is `Splat` or `Expansion` in `objects`,
// we can split array in two simple subarrays.
// `Splat` [a, b, c..., d, e] can be split into [a, b, c...] and [d, e].
// `Expansion` [a, b, ..., c, d] can be split into [a, b] and [c, d].
// Examples:
// a) `Splat`
// CS: [a, b, c..., d, e] = arr
// JS: [a, b, ...c] = arr, [d, e] = splice.call(c, -2)
// b) `Expansion`
// CS: [a, b, ..., d, e] = arr
// JS: [a, b] = arr, [d, e] = slice.call(arr, -2)
if (splatsAndExpans.length) {
expIdx = splatsAndExpans[0];
leftObjs = objects.slice(0, expIdx + (isSplat ? 1 : 0));
rightObjs = objects.slice(expIdx + 1);
if (leftObjs.length !== 0) {
processObjects(leftObjs, vvarText);
}
if (rightObjs.length !== 0) {
// Slice or splice `objects`.
refExp = (function() {
switch (false) {
case !isSplat:
return compSplice(objects[expIdx].unwrapAll().value, rightObjs.length * -1);
case !isExpans:
return compSlice(vvarText, rightObjs.length * -1);
}
})();
if (complexObjects(rightObjs)) {
restVar = refExp;
refExp = o.scope.freeVariable('ref');
assigns.push([this.makeCode(refExp + ' = '), ...restVar.compileToFragments(o, LEVEL_LIST)]);
}
processObjects(rightObjs, refExp);
}
} else {
// There is no `Splat` or `Expansion` in `objects`.
processObjects(objects, vvarText);
}
if (!(top || this.subpattern)) {
assigns.push(vvar);
@@ -5293,7 +5361,7 @@
compileNode(o) {
var body, bodyFragments, compare, compareDown, declare, declareDown, defPart, down, forPartFragments, fragments, guardPart, idt1, increment, index, ivar, kvar, kvarAssign, last, lvar, name, namePart, ref, ref1, resultPart, returnResult, rvar, scope, source, step, stepNum, stepVar, svar, varPart;
body = Block.wrap([this.body]);
ref1 = body.expressions, last = ref1[ref1.length - 1];
ref1 = body.expressions, [last] = slice1.call(ref1, -1);
if ((last != null ? last.jumps() : void 0) instanceof Return) {
this.returns = false;
}

View File

@@ -1,7 +1,7 @@
// Generated by CoffeeScript 2.1.1
(function() {
var LONG_FLAG, MULTI_FLAG, OPTIONAL, OptionParser, SHORT_FLAG, buildRule, buildRules, normalizeArguments, repeat,
slice = [].slice;
splice = [].splice;
({repeat} = require('./helpers'));
@@ -157,7 +157,7 @@
};
normalizeArguments = function(args, flagDict) {
var arg, argIndex, flag, i, innerOpts, j, k, lastOpt, len, len1, multiFlags, multiOpts, needsArgOpt, positional, ref, rule, rules, singleRule, withArg;
var arg, argIndex, flag, i, innerOpts, j, lastOpt, len, len1, multiFlags, multiOpts, needsArgOpt, positional, ref, rule, rules, singleRule, withArg;
rules = [];
positional = [];
needsArgOpt = null;
@@ -187,9 +187,9 @@
return {rule, flag};
});
// Only the last flag in a multi-flag may have an argument.
innerOpts = 2 <= multiOpts.length ? slice.call(multiOpts, 0, j = multiOpts.length - 1) : (j = 0, []), lastOpt = multiOpts[j++];
for (k = 0, len1 = innerOpts.length; k < len1; k++) {
({rule, flag} = innerOpts[k]);
[...innerOpts] = multiOpts, [lastOpt] = splice.call(innerOpts, -1);
for (j = 0, len1 = innerOpts.length; j < len1; j++) {
({rule, flag} = innerOpts[j]);
if (rule.hasArgument) {
throw new Error(`cannot use option ${flag} in multi-flag ${arg} except as the last option, because it needs an argument`);
}

View File

@@ -2351,47 +2351,26 @@ exports.Assign = class Assign extends Base
if olen is 1 and obj instanceof Expansion
obj.error 'Destructuring assignment has no target'
isObject = @variable.isObject()
# Count all `Splats`: [a, b, c..., d, e]
splats = (i for obj, i in objects when obj instanceof Splat)
# Count all `Expansions`: [a, b, ..., c, d]
expans = (i for obj, i in objects when obj instanceof Expansion)
# Combine splats and expansions.
splatsAndExpans = [splats..., expans...]
# Show error if there is more than one `Splat`, or `Expansion`.
# Examples: [a, b, c..., d, e, f...], [a, b, ..., c, d, ...], [a, b, ..., c, d, e...]
if splatsAndExpans.length > 1
# Sort 'splatsAndExpans' so we can show error at first disallowed token.
objects[splatsAndExpans.sort()[1]].error "multiple splats/expansions are disallowed in an assignment"
# Special case for when there's only one thing destructured off of
# something. `{a} = b`, `[a] = b`, `{a: b} = c`
if top and olen is 1 and obj not instanceof Splat
# Pick the property straight off the value when theres just one to pick
# (no need to cache the value into a variable).
defaultValue = undefined
if obj instanceof Assign and obj.context is 'object'
# A regular object pattern-match.
{variable: {base: idx}, value: obj} = obj
if obj instanceof Assign
defaultValue = obj.value
obj = obj.variable
else
if obj instanceof Assign
defaultValue = obj.value
obj = obj.variable
idx = if isObject
# A shorthand `{a, b, @c} = val` pattern-match.
if obj.this
obj.properties[0].name
else
new PropertyName obj.unwrap().value
else
# A regular array pattern-match.
new NumberLiteral 0
acc = idx.unwrap() instanceof PropertyName
value = new Value value
value.properties.push new (if acc then Access else Index) idx
message = isUnassignable obj.unwrap().value
obj.error message if message
if defaultValue
defaultValue.isDefaultValue = yes
value = new Op '?', value, defaultValue
return new Assign(obj, value, null, param: @param).compileToFragments o, LEVEL_TOP
isSplat = splats.length
isExpans = expans.length
isObject = @variable.isObject()
isArray = @variable.isArray()
vvar = value.compileToFragments o, LEVEL_LIST
vvarText = fragmentsToText vvar
assigns = []
expandedIdx = false
# At this point, there are several things to destructure. So the `fn()` in
# `{a, b} = fn()` must be cached, for example. Make vvar into a simple
@@ -2402,79 +2381,108 @@ exports.Assign = class Assign extends Base
vvar = [@makeCode ref]
vvarText = ref
# And here comes the big loop that handles all of these cases:
# `[a, b] = c`
# `[a..., b] = c`
# `[..., a, b] = c`
# `[@a, b] = c`
# `[a = 1, b] = c`
# `{a, b} = c`
# `{@a, b} = c`
# `{a = 1, b} = c`
# etc.
for obj, i in objects
idx = i
if not expandedIdx and obj instanceof Splat
name = obj.name.unwrap().value
obj = obj.unwrap()
val = "#{olen} <= #{vvarText}.length ? #{utility 'slice', o}.call(#{vvarText}, #{i}"
rest = olen - i - 1
if rest isnt 0
ivar = o.scope.freeVariable 'i', single: true
val += ", #{ivar} = #{vvarText}.length - #{rest}) : (#{ivar} = #{i}, [])"
else
val += ") : []"
val = new Literal val
expandedIdx = "#{ivar}++"
else if not expandedIdx and obj instanceof Expansion
rest = olen - i - 1
if rest isnt 0
if rest is 1
expandedIdx = "#{vvarText}.length - 1"
else
ivar = o.scope.freeVariable 'i', single: true
val = new Literal "#{ivar} = #{vvarText}.length - #{rest}"
expandedIdx = "#{ivar}++"
assigns.push val.compileToFragments o, LEVEL_LIST
continue
else
if obj instanceof Splat or obj instanceof Expansion
obj.error "multiple splats/expansions are disallowed in an assignment"
defaultValue = undefined
if obj instanceof Assign and obj.context is 'object'
# A regular object pattern-match.
{variable: {base: idx}, value: obj} = obj
if obj instanceof Assign
defaultValue = obj.value
obj = obj.variable
else
if obj instanceof Assign
defaultValue = obj.value
obj = obj.variable
idx = if isObject
# A shorthand `{a, b, @c} = val` pattern-match.
if obj.this
obj.properties[0].name
else
new PropertyName obj.unwrap().value
else
# A regular array pattern-match.
new Literal expandedIdx or idx
name = obj.unwrap().value
acc = idx.unwrap() instanceof PropertyName
val = new Value new Literal(vvarText), [new (if acc then Access else Index) idx]
if defaultValue
defaultValue.isDefaultValue = yes
val = new Op '?', val, defaultValue
if name?
message = isUnassignable name
obj.error message if message
unless obj instanceof Elision
assigns.push new Assign(obj, val, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST
else
# Output `Elision` only if `idx` is `i++`, e.g. expandedIdx.
assigns.push idx.compileToFragments o, LEVEL_LIST if expandedIdx
slicer = (type) -> (vvar, start, end = no) ->
args = [new IdentifierLiteral(vvar), new NumberLiteral(start)]
args.push new NumberLiteral end if end
slice = new Value (new IdentifierLiteral utility type, o), [new Access new PropertyName 'call']
new Value new Call slice, args
# Helper which outputs `[].slice` code.
compSlice = slicer "slice"
# Helper which outputs `[].splice` code.
compSplice = slicer "splice"
# Check if `objects` array contains object spread (`{a, r...}`), e.g. `[a, b, {c, r...}]`.
hasObjSpreads = (objs) ->
(i for obj, i in objs when obj.base instanceof Obj and obj.base.hasSplat())
# Check if `objects` array contains any instance of `Assign`, e.g. {a:1}.
hasObjAssigns = (objs) ->
(i for obj, i in objs when obj instanceof Assign and obj.context is 'object')
# Check if `objects` array contains any unassignable object.
objIsUnassignable = (objs) ->
return yes for obj in objs when not obj.isAssignable()
no
# `objects` are complex when there is object spread ({a...}), object assign ({a:1}),
# unassignable object, or just a single node.
complexObjects = (objs) ->
hasObjSpreads(objs).length or hasObjAssigns(objs).length or objIsUnassignable(objs) or olen is 1
# "Complex" `objects` are processed in a loop.
# Examples: [a, b, {c, r...}, d], [a, ..., {b, r...}, c, d]
loopObjects = (objs, vvarTxt) =>
objSpreads = hasObjSpreads objs
for obj, i in objs
# `Elision` can be skipped.
continue if obj instanceof Elision
# If `obj` is {a: 1}
if obj instanceof Assign and obj.context is 'object'
{variable: {base: idx}, value: vvar} = obj
{variable: vvar} = vvar if vvar instanceof Assign
idx =
if vvar.this
vvar.properties[0].name
else
new PropertyName vvar.unwrap().value
acc = idx.unwrap() instanceof PropertyName
vval = new Value value, [new (if acc then Access else Index) idx]
else
# `obj` is [a...], {a...} or a
vvar = switch
when obj instanceof Splat then new Value obj.name
when i in objSpreads then new Value obj.base
else obj
vval = switch
when obj instanceof Splat then compSlice(vvarTxt, i)
else new Value new Literal(vvarTxt), [new Index new NumberLiteral i]
message = isUnassignable vvar.unwrap().value
vvar.error message if message
assigns.push new Assign(vvar, vval, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST
# "Simple" `objects` can be split and compiled to arrays, [a, b, c] = arr, [a, b, c...] = arr
assignObjects = (objs, vvarTxt) =>
vvar = new Value new Arr(objs, yes)
vval = if vvarTxt instanceof Value then vvarTxt else new Value new Literal(vvarTxt)
assigns.push new Assign(vvar, vval, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST
processObjects = (objs, vvarTxt) ->
if complexObjects objs
loopObjects objs, vvarTxt
else
assignObjects objs, vvarTxt
# In case there is `Splat` or `Expansion` in `objects`,
# we can split array in two simple subarrays.
# `Splat` [a, b, c..., d, e] can be split into [a, b, c...] and [d, e].
# `Expansion` [a, b, ..., c, d] can be split into [a, b] and [c, d].
# Examples:
# a) `Splat`
# CS: [a, b, c..., d, e] = arr
# JS: [a, b, ...c] = arr, [d, e] = splice.call(c, -2)
# b) `Expansion`
# CS: [a, b, ..., d, e] = arr
# JS: [a, b] = arr, [d, e] = slice.call(arr, -2)
if splatsAndExpans.length
expIdx = splatsAndExpans[0]
leftObjs = objects.slice 0, expIdx + (if isSplat then 1 else 0)
rightObjs = objects.slice expIdx + 1
processObjects leftObjs, vvarText if leftObjs.length isnt 0
if rightObjs.length isnt 0
# Slice or splice `objects`.
refExp = switch
when isSplat then compSplice objects[expIdx].unwrapAll().value, rightObjs.length * -1
when isExpans then compSlice vvarText, rightObjs.length * -1
if complexObjects rightObjs
restVar = refExp
refExp = o.scope.freeVariable 'ref'
assigns.push [@makeCode(refExp + ' = '), restVar.compileToFragments(o, LEVEL_LIST)...]
processObjects rightObjs, refExp
else
# There is no `Splat` or `Expansion` in `objects`.
processObjects objects, vvarText
assigns.push vvar unless top or @subpattern
fragments = @joinFragmentArrays assigns, ', '
if o.level < LEVEL_LIST then fragments else @wrapInParentheses fragments