[IMP] *: use lower level loops in hot code

The goal is to gain a few % of perf improvement.

closes #125
This commit is contained in:
Géry Debongnie
2019-05-28 14:04:08 +02:00
parent 94208d9cbb
commit bced9f3d04
8 changed files with 166 additions and 122 deletions
+74 -62
View File
@@ -235,24 +235,27 @@ export class Component<
}
_callMounted() {
const children = this.__owl__.children;
const __owl__ = this.__owl__;
const children = __owl__.children;
for (let id in children) {
const comp = children[id];
if (!comp.__owl__.isMounted && this.el!.contains(comp.el)) {
comp._callMounted();
}
}
this.__owl__.isMounted = true;
for (let key in this.__owl__.mountedHandlers) {
this.__owl__.mountedHandlers[key]();
__owl__.isMounted = true;
const handlers = __owl__.mountedHandlers;
for (let key in handlers) {
handlers[key]();
}
this.mounted();
}
_callWillUnmount() {
this.willUnmount();
this.__owl__.isMounted = false;
const children = this.__owl__.children;
const __owl__ = this.__owl__;
__owl__.isMounted = false;
const children = __owl__.children;
for (let id in children) {
const comp = children[id];
if (comp.__owl__.isMounted) {
@@ -269,7 +272,8 @@ export class Component<
}
async render(force: boolean = false, patchQueue?: any[]): Promise<void> {
if (!this.__owl__.isMounted) {
const __owl__ = this.__owl__;
if (!__owl__.isMounted) {
return;
}
const shouldPatch: boolean = !patchQueue;
@@ -277,26 +281,23 @@ export class Component<
patchQueue = [];
}
const renderVDom = this._render(force, patchQueue);
const renderId = this.__owl__.renderId;
const renderId = __owl__.renderId;
await renderVDom;
if (
shouldPatch &&
this.__owl__.isMounted &&
renderId === this.__owl__.renderId
) {
if (shouldPatch && __owl__.isMounted && renderId === __owl__.renderId) {
// we only update the vnode and the actual DOM if no other rendering
// occurred between now and when the render method was initially called.
for (let i = 0; i < patchQueue!.length; i++) {
const patchLen = patchQueue!.length;
for (let i = 0; i < patchLen; i++) {
const patch = patchQueue![i];
patch.push(patch[0].willPatch());
}
for (let i = 0; i < patchQueue!.length; i++) {
for (let i = 0; i < patchLen; i++) {
const patch = patchQueue![i];
patch[0]._patch(patch[1]);
}
for (let i = patchQueue!.length - 1; i >= 0; i--) {
for (let i = patchLen - 1; i >= 0; i--) {
const patch = patchQueue![i];
patch[0].patched(patch[2]);
}
@@ -304,9 +305,10 @@ export class Component<
}
destroy() {
if (!this.__owl__.isDestroyed) {
const __owl__ = this.__owl__;
if (!__owl__.isDestroyed) {
const el = this.el;
this._destroy(this.__owl__.parent);
this._destroy(__owl__.parent);
if (el) {
el.remove();
}
@@ -314,23 +316,24 @@ export class Component<
}
_destroy(parent) {
const isMounted = this.__owl__.isMounted;
const __owl__ = this.__owl__;
const isMounted = __owl__.isMounted;
if (isMounted) {
this.willUnmount();
this.__owl__.isMounted = false;
__owl__.isMounted = false;
}
const children = Object.values(this.__owl__.children);
for (let child of children) {
child._destroy(this);
const children = __owl__.children;
for (let key in children) {
children[key]._destroy(this);
}
if (parent) {
let id = this.__owl__.id;
let id = __owl__.id;
delete parent.__owl__.children[id];
this.__owl__.parent = null;
__owl__.parent = null;
}
this.clear();
this.__owl__.isDestroyed = true;
delete this.__owl__.vnode;
__owl__.isDestroyed = true;
delete __owl__.vnode;
}
shouldUpdate(nextProps: Props): boolean {
@@ -347,11 +350,12 @@ export class Component<
* mode or not.
*/
async updateEnv(nextEnv: Partial<T>): Promise<void> {
if (this.__owl__.parent && this.__owl__.parent.env === this.env) {
const __owl__ = this.__owl__;
if (__owl__.parent && __owl__.parent.env === this.env) {
this.env = Object.create(this.env);
}
Object.assign(this.env, nextEnv);
if (this.__owl__.isMounted) {
if (__owl__.isMounted) {
await this.render(true);
}
}
@@ -385,19 +389,22 @@ export class Component<
}
_patch(vnode) {
this.__owl__.renderPromise = null;
const target = this.__owl__.vnode || document.createElement(vnode.sel!);
this.__owl__.vnode = patch(target, vnode);
const __owl__ = this.__owl__;
__owl__.renderPromise = null;
const target = __owl__.vnode || document.createElement(vnode.sel!);
__owl__.vnode = patch(target, vnode);
}
_prepare(): Promise<VNode> {
this.__owl__.renderProps = this.props;
this.__owl__.renderPromise = this._prepareAndRender();
return this.__owl__.renderPromise;
const __owl__ = this.__owl__;
__owl__.renderProps = this.props;
__owl__.renderPromise = this._prepareAndRender();
return __owl__.renderPromise;
}
async _prepareAndRender(): Promise<VNode> {
await this.willStart();
if (this.__owl__.isDestroyed) {
const __owl__ = this.__owl__;
if (__owl__.isDestroyed) {
return Promise.resolve(h("div"));
}
const qweb = this.env.qweb;
@@ -428,7 +435,7 @@ export class Component<
}
}
}
this.__owl__.render = qweb.render.bind(qweb, this.template);
__owl__.render = qweb.render.bind(qweb, this.template);
this._observeState();
return this._render();
}
@@ -436,25 +443,26 @@ export class Component<
force: boolean = false,
patchQueue: any[] = []
): Promise<VNode> {
this.__owl__.renderId++;
const __owl__ = this.__owl__;
__owl__.renderId++;
const promises: Promise<void>[] = [];
const patch: any[] = [this];
if (this.__owl__.isMounted) {
if (__owl__.isMounted) {
patchQueue.push(patch);
}
if (this.__owl__.observer) {
this.__owl__.observer.allowMutations = false;
if (__owl__.observer) {
__owl__.observer.allowMutations = false;
}
let vnode = this.__owl__.render!(this, {
let vnode = __owl__.render!(this, {
promises,
handlers: this.__owl__.boundHandlers,
mountedHandlers: this.__owl__.mountedHandlers,
handlers: __owl__.boundHandlers,
mountedHandlers: __owl__.mountedHandlers,
forceUpdate: force,
patchQueue
});
patch.push(vnode);
if (this.__owl__.observer) {
this.__owl__.observer.allowMutations = true;
if (__owl__.observer) {
__owl__.observer.allowMutations = true;
}
// this part is critical for the patching process to be done correctly. The
@@ -462,38 +470,41 @@ export class Component<
// will update its own vnode representation without the knowledge of the
// parent widget. With this, we make sure that the parent widget will be
// able to patch itself properly after
vnode.key = this.__owl__.id;
this.__owl__.renderProps = this.props;
this.__owl__.renderPromise = Promise.all(promises).then(() => vnode);
return this.__owl__.renderPromise;
vnode.key = __owl__.id;
__owl__.renderProps = this.props;
__owl__.renderPromise = Promise.all(promises).then(() => vnode);
return __owl__.renderPromise;
}
/**
* Only called by qweb t-widget directive
*/
_mount(vnode: VNode, elm: HTMLElement): VNode {
this.__owl__.vnode = patch(elm, vnode);
if (this.__owl__.parent!.__owl__.isMounted && !this.__owl__.isMounted) {
const __owl__ = this.__owl__;
__owl__.vnode = patch(elm, vnode);
if (__owl__.parent!.__owl__.isMounted && !__owl__.isMounted) {
this._callMounted();
}
return this.__owl__.vnode;
return __owl__.vnode;
}
/**
* Only called by qweb t-widget directive (when t-keepalive is set)
*/
_remount() {
if (!this.__owl__.isMounted) {
this.__owl__.isMounted = true;
const __owl__ = this.__owl__;
if (!__owl__.isMounted) {
__owl__.isMounted = true;
this.mounted();
}
}
_observeState() {
if (this.state) {
this.__owl__.observer = new Observer();
this.__owl__.observer.observe(this.state);
this.__owl__.observer.notifyCB = this.render.bind(this);
const __owl__ = this.__owl__;
__owl__.observer = new Observer();
__owl__.observer.observe(this.state);
__owl__.observer.notifyCB = this.render.bind(this);
}
}
@@ -577,7 +588,7 @@ function isValidProp(prop, propDef): boolean {
// If this code is executed, this means that we want to check if a prop
// matches at least one of its descriptor.
let result = false;
for (let i = 0; i < propDef.length; i++) {
for (let i = 0, iLen = propDef.length; i < iLen; i++) {
result = result || isValidProp(prop, propDef[i]);
}
return result;
@@ -585,13 +596,14 @@ function isValidProp(prop, propDef): boolean {
// propsDef is an object
let result = isValidProp(prop, propDef.type);
if (propDef.type === Array) {
for (let i = 0; i < prop.length; i++) {
for (let i = 0, iLen = prop.length; i < iLen; i++) {
result = result && isValidProp(prop[i], propDef.element);
}
}
if (propDef.type === Object) {
for (let key in propDef.shape) {
result = result && isValidProp(prop[key], propDef.shape[key]);
const shape = propDef.shape;
for (let key in shape) {
result = result && isValidProp(prop[key], shape[key]);
}
}
return result;
+2 -1
View File
@@ -64,7 +64,8 @@ export class EventBus {
*/
trigger(eventType: string, ...args: any[]) {
const subs = this.subscriptions[eventType] || [];
for (let sub of subs) {
for (let i = 0, iLen = subs.length; i < iLen; i++) {
const sub = subs[i];
sub.callback.call(sub.owner, ...args);
}
}
+8 -6
View File
@@ -30,10 +30,13 @@ const methodsToPatch = [
"sort",
"reverse"
];
const methodLen = methodsToPatch.length;
const ArrayProto = Array.prototype;
const ModifiedArrayProto = Object.create(ArrayProto);
for (let method of methodsToPatch) {
for (let i = 0; i < methodLen; i++) {
const method = methodsToPatch[i];
const initialMethod = ArrayProto[method];
ModifiedArrayProto[method] = function(...args) {
if (!this.__observer__.allowMutations) {
@@ -57,8 +60,8 @@ for (let method of methodsToPatch) {
break;
}
if (inserted) {
for (let elem of inserted) {
this.__observer__.observe(elem, this);
for (let i = 0, iLen = inserted.length; i < iLen; i++) {
this.__observer__.observe(inserted[i], this);
}
}
return initialMethod.call(this, ...args);
@@ -112,10 +115,9 @@ export class Observer {
}
_observeObj<T extends { __owl__?: any }>(obj: T, parent?: any) {
const keys = Object.keys(obj);
obj.__owl__ = { rev: this.rev, deepRev: this.rev, parent };
Object.defineProperty(obj, "__owl__", { enumerable: false });
for (let key of keys) {
for (let key in obj) {
this._addProp(obj, key, obj[key]);
}
}
@@ -125,7 +127,7 @@ export class Observer {
Object.defineProperty(arr, "__owl__", { enumerable: false });
(<any>arr).__proto__ = Object.create(ModifiedArrayProto);
(<any>arr).__proto__.__observer__ = this;
for (let i = 0; i < arr.length; i++) {
for (let i = 0, iLen = arr.length; i < iLen; i++) {
this.observe(arr[i], arr);
}
}
+11 -8
View File
@@ -100,9 +100,9 @@ interface Utils {
h: typeof h;
objectToAttrString(obj: Object): string;
[key: string]: any;
};
}
export const UTILS:Utils = {
export const UTILS: Utils = {
h: h,
objectToAttrString(obj: Object): string {
let classes: string[] = [];
@@ -361,17 +361,20 @@ export class QWeb {
}
}
for (let directive of DIRECTIVES) {
const DIR_N = DIRECTIVES.length;
const ATTR_N = attributes.length;
for (let i = 0; i < DIR_N; i++) {
let directive = DIRECTIVES[i];
let fullName;
let value;
for (let i = 0; i < attributes.length; i++) {
const name = attributes[i].name;
for (let j = 0; j < ATTR_N; j++) {
const name = attributes[j].name;
if (
name === "t-" + directive.name ||
name.startsWith("t-" + directive.name + "-")
) {
fullName = name;
value = attributes[i].textContent;
value = attributes[j].textContent;
validDirectives.push({ directive, value, fullName });
if (directive.name === "on") {
withHandlers = true;
@@ -721,8 +724,8 @@ export class Context {
let invarPos = 0;
let r = "";
chars.push(" ");
for (var i = 0, ilen = chars.length; i < ilen; i++) {
var c = chars[i];
for (let i = 0, ilen = chars.length; i < ilen; i++) {
let c = chars[i];
if (instring.length) {
if (c === instring && chars[i - 1] !== "\\") {
instring = "";
+3 -2
View File
@@ -250,14 +250,15 @@ QWeb.addDirective({
ctx.addLine(
`var _${keysID} = _${arrayID} instanceof Array ? _${arrayID} : Object.keys(_${arrayID});`
);
ctx.addLine(`var _length${keysID} = _${keysID}.length;`);
let valuesID = ctx.generateID();
ctx.addLine(
`var _${valuesID} = _${arrayID} instanceof Array ? _${arrayID} : Object.values(_${arrayID});`
);
ctx.addLine(`for (let i = 0; i < _${keysID}.length; i++) {`);
ctx.addLine(`for (let i = 0; i < _length${keysID}; i++) {`);
ctx.indent();
ctx.addLine(`context.${name}_first = i === 0;`);
ctx.addLine(`context.${name}_last = i === _${keysID}.length - 1;`);
ctx.addLine(`context.${name}_last = i === _length${keysID} - 1;`);
ctx.addLine(`context.${name}_parity = i % 2 === 0 ? 'even' : 'odd';`);
ctx.addLine(`context.${name}_index = i;`);
ctx.addLine(`context.${name} = _${keysID}[i];`);
+29 -17
View File
@@ -15,12 +15,15 @@
* - patch function (to apply a vnode to an actual DOM node)
*/
// because those in TypeScript are too restrictive: https://github.com/Microsoft/TSJS-lib-generator/pull/237
declare global {
interface Element {
setAttribute(name: string, value: string | number | boolean): void;
setAttributeNS(namespaceURI: string, qualifiedName: string, value: string | number | boolean): void;
setAttributeNS(
namespaceURI: string,
qualifiedName: string,
value: string | number | boolean
): void;
}
}
@@ -162,6 +165,7 @@ export function init(modules: Array<Partial<Module>>, domApi?: DOMAPI) {
function createElm(vnode: VNode, insertedVnodeQueue: VNodeQueue): Node {
let i: any,
iLen: number,
data = vnode.data;
if (data !== undefined) {
if (isDef((i = data.hook)) && isDef((i = i.init))) {
@@ -193,9 +197,10 @@ export function init(modules: Array<Partial<Module>>, domApi?: DOMAPI) {
if (hash < dot) elm.setAttribute("id", sel.slice(hash + 1, dot));
if (dotIdx > 0)
elm.setAttribute("class", sel.slice(dot + 1).replace(/\./g, " "));
for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode);
for (i = 0, iLen = cbs.create.length; i < iLen; ++i)
cbs.create[i](emptyNode, vnode);
if (array(children)) {
for (i = 0; i < children.length; ++i) {
for (i = 0, iLen = children.length; i < iLen; ++i) {
const ch = children[i];
if (ch != null) {
api.appendChild(elm, createElm(ch as VNode, insertedVnodeQueue));
@@ -233,13 +238,16 @@ export function init(modules: Array<Partial<Module>>, domApi?: DOMAPI) {
function invokeDestroyHook(vnode: VNode) {
let i: any,
iLen: number,
j: number,
jLen: number,
data = vnode.data;
if (data !== undefined) {
if (isDef((i = data.hook)) && isDef((i = i.destroy))) i(vnode);
for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode);
for (i = 0, iLen = cbs.destroy.length; i < iLen; ++i)
cbs.destroy[i](vnode);
if (vnode.children !== undefined) {
for (j = 0; j < vnode.children.length; ++j) {
for (j = 0, jLen = vnode.children.length; j < jLen; ++j) {
i = vnode.children[j];
if (i != null && typeof i !== "string") {
invokeDestroyHook(i);
@@ -257,6 +265,7 @@ export function init(modules: Array<Partial<Module>>, domApi?: DOMAPI) {
): void {
for (; startIdx <= endIdx; ++startIdx) {
let i: any,
iLen: number,
listeners: number,
rm: () => void,
ch = vnodes[startIdx];
@@ -265,7 +274,8 @@ export function init(modules: Array<Partial<Module>>, domApi?: DOMAPI) {
invokeDestroyHook(ch);
listeners = cbs.remove.length + 1;
rm = createRmCb(ch.elm as Node, listeners);
for (i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm);
for (i = 0, iLen = cbs.remove.length; i < iLen; ++i)
cbs.remove[i](ch, rm);
if (
isDef((i = ch.data)) &&
isDef((i = i.hook)) &&
@@ -395,7 +405,7 @@ export function init(modules: Array<Partial<Module>>, domApi?: DOMAPI) {
vnode: VNode,
insertedVnodeQueue: VNodeQueue
) {
let i: any, hook: any;
let i: any, iLen: number, hook: any;
if (
isDef((i = vnode.data)) &&
isDef((hook = i.hook)) &&
@@ -408,7 +418,8 @@ export function init(modules: Array<Partial<Module>>, domApi?: DOMAPI) {
let ch = vnode.children;
if (oldVnode === vnode) return;
if (vnode.data !== undefined) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode);
for (i = 0, iLen = cbs.update.length; i < iLen; ++i)
cbs.update[i](oldVnode, vnode);
i = vnode.data.hook;
if (isDef(i) && isDef((i = i.update))) i(oldVnode, vnode);
}
@@ -458,9 +469,9 @@ export function init(modules: Array<Partial<Module>>, domApi?: DOMAPI) {
}
return function patch(oldVnode: VNode | Element, vnode: VNode): VNode {
let i: number, elm: Node, parent: Node;
let i: number, iLen: number, elm: Node, parent: Node;
const insertedVnodeQueue: VNodeQueue = [];
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]();
for (i = 0, iLen = cbs.pre.length; i < iLen; ++i) cbs.pre[i]();
if (!isVnode(oldVnode)) {
oldVnode = emptyNodeAt(oldVnode);
@@ -480,12 +491,12 @@ export function init(modules: Array<Partial<Module>>, domApi?: DOMAPI) {
}
}
for (i = 0; i < insertedVnodeQueue.length; ++i) {
for (i = 0, iLen = insertedVnodeQueue.length; i < iLen; ++i) {
(((insertedVnodeQueue[i].data as VNodeData).hook as Hooks).insert as any)(
insertedVnodeQueue[i]
);
}
for (i = 0; i < cbs.post.length; ++i) cbs.post[i]();
for (i = 0, iLen = cbs.post.length; i < iLen; ++i) cbs.post[i]();
return vnode;
};
}
@@ -647,7 +658,7 @@ function addNS(
): void {
data.ns = "http://www.w3.org/2000/svg";
if (sel !== "foreignObject" && children !== undefined) {
for (let i = 0; i < children.length; ++i) {
for (let i = 0, iLen = children.length; i < iLen; ++i) {
let childData = children[i].data;
if (childData !== undefined) {
addNS(
@@ -668,7 +679,8 @@ export function h(sel: any, b?: any, c?: any): VNode {
var data: VNodeData = {},
children: any,
text: any,
i: number;
i: number,
iLen: number;
if (c !== undefined) {
data = b;
if (array(c)) {
@@ -690,7 +702,7 @@ export function h(sel: any, b?: any, c?: any): VNode {
}
}
if (children !== undefined) {
for (i = 0; i < children.length; ++i) {
for (i = 0, iLen = children.length; i < iLen; ++i) {
if (primitive(children[i]))
children[i] = vnode(
undefined,
@@ -788,7 +800,7 @@ function invokeHandler(handler: any, vnode?: VNode, event?: Event): void {
}
} else {
// call multiple handlers
for (var i = 0; i < handler.length; i++) {
for (let i = 0, iLen = handler.length; i < iLen; i++) {
invokeHandler(handler[i], vnode, event);
}
}
+3 -2
View File
@@ -90,10 +90,11 @@ exports[`composition sub widgets with some state rendered in a loop 1`] = `
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
var _length3 = _3.length;
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
for (let i = 0; i < _3.length; i++) {
for (let i = 0; i < _length3; i++) {
context.number_first = i === 0;
context.number_last = i === _3.length - 1;
context.number_last = i === _length3 - 1;
context.number_parity = i % 2 === 0 ? 'even' : 'odd';
context.number_index = i;
context.number = _3[i];
+36 -24
View File
@@ -308,10 +308,11 @@ exports[`foreach does not pollute the rendering context 1`] = `
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
var _length3 = _3.length;
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
for (let i = 0; i < _3.length; i++) {
for (let i = 0; i < _length3; i++) {
context.item_first = i === 0;
context.item_last = i === _3.length - 1;
context.item_last = i === _length3 - 1;
context.item_parity = i % 2 === 0 ? 'even' : 'odd';
context.item_index = i;
context.item = _3[i];
@@ -336,10 +337,11 @@ exports[`foreach iterate on items (on a element node) 1`] = `
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
var _length3 = _3.length;
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
for (let i = 0; i < _3.length; i++) {
for (let i = 0; i < _length3; i++) {
context.item_first = i === 0;
context.item_last = i === _3.length - 1;
context.item_last = i === _length3 - 1;
context.item_parity = i % 2 === 0 ? 'even' : 'odd';
context.item_index = i;
context.item = _3[i];
@@ -367,10 +369,11 @@ exports[`foreach iterate on items 1`] = `
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
var _length3 = _3.length;
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
for (let i = 0; i < _3.length; i++) {
for (let i = 0; i < _length3; i++) {
context.item_first = i === 0;
context.item_last = i === _3.length - 1;
context.item_last = i === _length3 - 1;
context.item_parity = i % 2 === 0 ? 'even' : 'odd';
context.item_index = i;
context.item = _3[i];
@@ -407,10 +410,11 @@ exports[`foreach iterate, dict param 1`] = `
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
var _length3 = _3.length;
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
for (let i = 0; i < _3.length; i++) {
for (let i = 0; i < _length3; i++) {
context.item_first = i === 0;
context.item_last = i === _3.length - 1;
context.item_last = i === _length3 - 1;
context.item_parity = i % 2 === 0 ? 'even' : 'odd';
context.item_index = i;
context.item = _3[i];
@@ -452,10 +456,11 @@ exports[`foreach iterate, integer param 1`] = `
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
var _length3 = _3.length;
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
for (let i = 0; i < _3.length; i++) {
for (let i = 0; i < _length3; i++) {
context.item_first = i === 0;
context.item_last = i === _3.length - 1;
context.item_last = i === _length3 - 1;
context.item_parity = i % 2 === 0 ? 'even' : 'odd';
context.item_index = i;
context.item = _3[i];
@@ -492,10 +497,11 @@ exports[`foreach iterate, position 1`] = `
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
var _length3 = _3.length;
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
for (let i = 0; i < _3.length; i++) {
for (let i = 0; i < _length3; i++) {
context.elem_first = i === 0;
context.elem_last = i === _3.length - 1;
context.elem_last = i === _length3 - 1;
context.elem_parity = i % 2 === 0 ? 'even' : 'odd';
context.elem_index = i;
context.elem = _3[i];
@@ -529,10 +535,11 @@ exports[`foreach warn if no key in some case 1`] = `
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
var _length3 = _3.length;
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
for (let i = 0; i < _3.length; i++) {
for (let i = 0; i < _length3; i++) {
context.item_first = i === 0;
context.item_last = i === _3.length - 1;
context.item_last = i === _length3 - 1;
context.item_parity = i % 2 === 0 ? 'even' : 'odd';
context.item_index = i;
context.item = _3[i];
@@ -589,10 +596,11 @@ exports[`misc global 1`] = `
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
var _length3 = _3.length;
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
for (let i = 0; i < _3.length; i++) {
for (let i = 0; i < _length3; i++) {
context.value_first = i === 0;
context.value_last = i === _3.length - 1;
context.value_last = i === _length3 - 1;
context.value_parity = i % 2 === 0 ? 'even' : 'odd';
context.value_index = i;
context.value = _3[i];
@@ -1098,10 +1106,11 @@ exports[`t-key t-key directive in a list 1`] = `
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
var _length3 = _3.length;
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
for (let i = 0; i < _3.length; i++) {
for (let i = 0; i < _length3; i++) {
context.beer_first = i === 0;
context.beer_last = i === _3.length - 1;
context.beer_last = i === _length3 - 1;
context.beer_parity = i % 2 === 0 ? 'even' : 'odd';
context.beer_index = i;
context.beer = _3[i];
@@ -1197,10 +1206,11 @@ exports[`t-on can bind handlers with loop variable as argument 1`] = `
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
var _length3 = _3.length;
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
for (let i = 0; i < _3.length; i++) {
for (let i = 0; i < _length3; i++) {
context.action_first = i === 0;
context.action_last = i === _3.length - 1;
context.action_last = i === _length3 - 1;
context.action_parity = i % 2 === 0 ? 'even' : 'odd';
context.action_index = i;
context.action = _3[i];
@@ -1524,10 +1534,11 @@ exports[`t-ref refs in a loop 1`] = `
if (!_2) { throw new Error('QWeb error: Invalid loop expression')}
if (typeof _2 === 'number') { _2 = Array.from(Array(_2).keys())}
var _3 = _2 instanceof Array ? _2 : Object.keys(_2);
var _length3 = _3.length;
var _4 = _2 instanceof Array ? _2 : Object.values(_2);
for (let i = 0; i < _3.length; i++) {
for (let i = 0; i < _length3; i++) {
context.item_first = i === 0;
context.item_last = i === _3.length - 1;
context.item_last = i === _length3 - 1;
context.item_parity = i % 2 === 0 ? 'even' : 'odd';
context.item_index = i;
context.item = _3[i];
@@ -1682,10 +1693,11 @@ exports[`t-set t-set should reuse variable if possible 1`] = `
if (!_3) { throw new Error('QWeb error: Invalid loop expression')}
if (typeof _3 === 'number') { _3 = Array.from(Array(_3).keys())}
var _4 = _3 instanceof Array ? _3 : Object.keys(_3);
var _length4 = _4.length;
var _5 = _3 instanceof Array ? _3 : Object.values(_3);
for (let i = 0; i < _4.length; i++) {
for (let i = 0; i < _length4; i++) {
context.elem_first = i === 0;
context.elem_last = i === _4.length - 1;
context.elem_last = i === _length4 - 1;
context.elem_parity = i % 2 === 0 ? 'even' : 'odd';
context.elem_index = i;
context.elem = _4[i];