Skip to content

Commit

Permalink
Reduce decorator static property size (#16287)
Browse files Browse the repository at this point in the history
* improve

* fix new test

* fix test

* add test

* Apply suggestions from code review

Co-authored-by: Nicol貌 Ribaudo <nicolo.ribaudo@gmail.com>

* lint

---------

Co-authored-by: Nicol貌 Ribaudo <nicolo.ribaudo@gmail.com>
  • Loading branch information
liuxingbaoyu and nicolo-ribaudo committed Feb 19, 2024
1 parent 9971e5a commit 6e79c05
Show file tree
Hide file tree
Showing 21 changed files with 209 additions and 147 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,7 @@ function extractElementLocalAssignments(decorationInfo: DecoratorInfo[]) {
}

function addCallAccessorsFor(
version: DecoratorVersionKind,
element: NodePath,
key: t.PrivateName,
getId: t.Identifier,
Expand All @@ -616,7 +617,12 @@ function addCallAccessorsFor(
[],
t.blockStatement([
t.returnStatement(
t.callExpression(t.cloneNode(getId), [t.thisExpression()]),
t.callExpression(
t.cloneNode(getId),
(process.env.BABEL_8_BREAKING || version === "2023-11") && isStatic
? []
: [t.thisExpression()],
),
),
]),
isStatic,
Expand All @@ -630,10 +636,12 @@ function addCallAccessorsFor(
[t.identifier("v")],
t.blockStatement([
t.expressionStatement(
t.callExpression(t.cloneNode(setId), [
t.thisExpression(),
t.identifier("v"),
]),
t.callExpression(
t.cloneNode(setId),
(process.env.BABEL_8_BREAKING || version === "2023-11") && isStatic
? [t.identifier("v")]
: [t.thisExpression(), t.identifier("v")],
),
),
]),
isStatic,
Expand Down Expand Up @@ -1085,7 +1093,10 @@ function transformClass(
if (kind === ACCESSOR) {
const { value } = element.node as t.ClassAccessorProperty;

const params: t.Expression[] = [t.thisExpression()];
const params: t.Expression[] =
(process.env.BABEL_8_BREAKING || version === "2023-11") && isStatic
? []
: [t.thisExpression()];

if (value) {
params.push(t.cloneNode(value));
Expand All @@ -1112,7 +1123,7 @@ function transformClass(
`set_${name}`,
);

addCallAccessorsFor(newPath, key, getId, setId, isStatic);
addCallAccessorsFor(version, newPath, key, getId, setId, isStatic);

locals = [newFieldInitId, getId, setId];
} else {
Expand All @@ -1135,12 +1146,13 @@ function transformClass(
element as NodePath<t.ClassProperty | t.ClassPrivateProperty>
).get("value");

valuePath.replaceWith(
t.callExpression(
t.cloneNode(initId),
[t.thisExpression(), valuePath.node].filter(v => v),
),
);
const args: t.Expression[] =
(process.env.BABEL_8_BREAKING || version === "2023-11") && isStatic
? []
: [t.thisExpression()];
if (valuePath.node) args.push(valuePath.node);

valuePath.replaceWith(t.callExpression(t.cloneNode(initId), args));

locals = [initId];

Expand Down Expand Up @@ -1248,9 +1260,10 @@ function transformClass(
`init_extra_${name}`,
);
locals.push(initExtraId);
const initExtraCall = t.callExpression(t.cloneNode(initExtraId), [
t.thisExpression(),
]);
const initExtraCall = t.callExpression(
t.cloneNode(initExtraId),
isStatic ? [] : [t.thisExpression()],
);
if (!isStatic) {
fieldInitializerExpressions.push(initExtraCall);
} else {
Expand Down
4 changes: 2 additions & 2 deletions packages/babel-helpers/src/helpers-generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ export default Object.freeze({
"7.21.0",
'import checkInRHS from"checkInRHS";import setFunctionName from"setFunctionName";import toPropertyKey from"toPropertyKey";export default function applyDecs2305(e,t,r,n,o,a){function i(e,t,r){return function(n,o){return r&&r(n),e[t].call(n,o)}}function c(e,t){for(var r=0;r<e.length;r++)e[r].call(t);return t}function s(e,t,r,n){if("function"!=typeof e&&(n||void 0!==e))throw new TypeError(t+" must "+(r||"be")+" a function"+(n?"":" or undefined"));return e}function applyDec(e,t,r,n,o,a,c,u,l,f,p,d,h){function m(e){if(!h(e))throw new TypeError("Attempted to access private element on non-instance")}var y,v=t[0],g=t[3],b=!u;if(!b){r||Array.isArray(v)||(v=[v]);var w={},S=[],A=3===o?"get":4===o||d?"set":"value";f?(p||d?w={get:setFunctionName((function(){return g(this)}),n,"get"),set:function(e){t[4](this,e)}}:w[A]=g,p||setFunctionName(w[A],n,2===o?"":A)):p||(w=Object.getOwnPropertyDescriptor(e,n))}for(var P=e,j=v.length-1;j>=0;j-=r?2:1){var D=v[j],E=r?v[j-1]:void 0,I={},O={kind:["field","accessor","method","getter","setter","class"][o],name:n,metadata:a,addInitializer:function(e,t){if(e.v)throw new Error("attempted to call addInitializer after decoration was finished");s(t,"An initializer","be",!0),c.push(t)}.bind(null,I)};try{if(b)(y=s(D.call(E,P,O),"class decorators","return"))&&(P=y);else{var k,F;O.static=l,O.private=f,f?2===o?k=function(e){return m(e),w.value}:(o<4&&(k=i(w,"get",m)),3!==o&&(F=i(w,"set",m))):(k=function(e){return e[n]},(o<2||4===o)&&(F=function(e,t){e[n]=t}));var N=O.access={has:f?h.bind():function(e){return n in e}};if(k&&(N.get=k),F&&(N.set=F),P=D.call(E,d?{get:w.get,set:w.set}:w[A],O),d){if("object"==typeof P&&P)(y=s(P.get,"accessor.get"))&&(w.get=y),(y=s(P.set,"accessor.set"))&&(w.set=y),(y=s(P.init,"accessor.init"))&&S.push(y);else if(void 0!==P)throw new TypeError("accessor decorators must return an object with get, set, or init properties or void 0")}else s(P,(p?"field":"method")+" decorators","return")&&(p?S.push(P):w[A]=P)}}finally{I.v=!0}}return(p||d)&&u.push((function(e,t){for(var r=S.length-1;r>=0;r--)t=S[r].call(e,t);return t})),p||b||(f?d?u.push(i(w,"get"),i(w,"set")):u.push(2===o?w[A]:i.call.bind(w[A])):Object.defineProperty(e,n,w)),P}function u(e,t){return Object.defineProperty(e,Symbol.metadata||Symbol.for("Symbol.metadata"),{configurable:!0,enumerable:!0,value:t})}if(arguments.length>=6)var l=a[Symbol.metadata||Symbol.for("Symbol.metadata")];var f=Object.create(null==l?null:l),p=function(e,t,r,n){var o,a,i=[],s=function(t){return checkInRHS(t)===e},u=new Map;function l(e){e&&i.push(c.bind(null,e))}for(var f=0;f<t.length;f++){var p=t[f];if(Array.isArray(p)){var d=p[1],h=p[2],m=p.length>3,y=16&d,v=!!(8&d),g=0==(d&=7),b=h+"/"+v;if(!g&&!m){var w=u.get(b);if(!0===w||3===w&&4!==d||4===w&&3!==d)throw new Error("Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: "+h);u.set(b,!(d>2)||d)}applyDec(v?e:e.prototype,p,y,m?"#"+h:toPropertyKey(h),d,n,v?a=a||[]:o=o||[],i,v,m,g,1===d,v&&m?s:r)}}return l(o),l(a),i}(e,t,o,f);return r.length||u(e,f),{e:p,get c(){var t=[];return r.length&&[u(applyDec(e,[r],n,e.name,5,f,t),f),c.bind(null,t,e)]}}}',
),
// size: 2899, gzip size: 1475
// size: 2917, gzip size: 1487
applyDecs2311: helper(
"7.23.0",
'import checkInRHS from"checkInRHS";import setFunctionName from"setFunctionName";import toPropertyKey from"toPropertyKey";export default function applyDecs2311(e,t,n,r,o,i){var a,c,u=Symbol.metadata||Symbol.for("Symbol.metadata"),s=Object.defineProperty,l=Object.create,f=[l(null),l(null)],p=n.length;function d(e,t,n,r){for(var o=0;o<e.length;o++)r=e[o].apply(n,t?[r]:[]);return t?r:n}function m(e,t,n,r){if("function"!=typeof e&&(r||void 0!==e))throw new TypeError(t+" must "+(n||"be")+" a function"+(r?"":" or undefined"));return e}function applyDec(e,t,n,r,o,i,u,l,p,h,y,v){function g(e){if(!v(e))throw new TypeError("Attempted to access private element on non-instance")}var b=[].concat(t[0]),w=t[3],D=!u,S=3===o,j=4===o,E=2===o;function I(e,t){return function(n,r){return t&&t(n),P[e].call(n,r)}}if(!D){var P={},k=[],F=S?"get":j||y?"set":"value";if(p?(h||y?P={get:setFunctionName((function(){return w(this)}),r,"get"),set:function(e){t[4](this,e)}}:P[F]=w,h||setFunctionName(P[F],r,E?"":F)):h||(P=Object.getOwnPropertyDescriptor(e,r)),!h&&!p){if((c=f[+l][r])&&7!=(c^o))throw new Error("Decorating two elements with the same name ("+P[F].name+") is not supported yet");f[+l][r]=o<3?1:o}}for(var N=e,O=b.length-1;O>=0;O-=n?2:1){var z=b[O],H=n?b[O-1]:void 0,K={},R={kind:["field","accessor","method","getter","setter","class"][o],name:r,metadata:a,addInitializer:function(e,t){if(e.v)throw new Error("attempted to call addInitializer after decoration was finished");m(t,"An initializer","be",!0),i.push(t)}.bind(null,K)};if(D)c=z.call(H,N,R),K.v=1,m(c,"class decorators","return")&&(N=c);else if(R.static=l,R.private=p,c=R.access={has:p?v.bind():function(e){return r in e}},j||(c.get=p?E?function(e){return g(e),P.value}:I("get",g):function(e){return e[r]}),E||S||(c.set=p?I("set",g):function(e,t){e[r]=t}),N=z.call(H,y?{get:P.get,set:P.set}:P[F],R),K.v=1,y){if("object"==typeof N&&N)(c=m(N.get,"accessor.get"))&&(P.get=c),(c=m(N.set,"accessor.set"))&&(P.set=c),(c=m(N.init,"accessor.init"))&&k.unshift(c);else if(void 0!==N)throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined")}else m(N,(h?"field":"method")+" decorators","return")&&(h?k.unshift(N):P[F]=N)}return o<2&&u.push(d.bind(null,k,1),d.bind(null,i,0)),h||D||(p?y?u.splice(-1,0,I("get"),I("set")):u.push(E?P[F]:d.call.bind(P[F])):s(e,r,P)),N}function h(e){return s(e,u,{configurable:!0,enumerable:!0,value:a})}return void 0!==i&&(a=i[u]),a=l(null==a?null:a),c=function(){var n,r,i=[];function a(e){e&&i.push(d.bind(null,e,0))}for(var c=0;c<t.length;c++){var u=t[c],s=u[1],l=u[2],f=!!u[3],p=16&s,m=!!(8&s),h=0==(s&=7),y=1===s;applyDec(m?e:e.prototype,u,p,f?"#"+l:toPropertyKey(l),s,h||y?[]:m?r=r||[]:n=n||[],i,m,f,h,y,m&&f?function(t){return checkInRHS(t)===e}:o)}return a(n),a(r),i}(),p||h(e),{e:c,get c(){var t=[];return p&&[h(applyDec(e,[n],r,e.name,5,t)),d.bind(null,t,0,e)]}}}',
'import checkInRHS from"checkInRHS";import setFunctionName from"setFunctionName";import toPropertyKey from"toPropertyKey";export default function applyDecs2311(e,t,n,r,o,i){var a,c,u=Symbol.metadata||Symbol.for("Symbol.metadata"),s=Object.defineProperty,f=Object.create,l=[f(null),f(null)],p=n.length;function d(t,n,r){return function(o,i){n&&(i=o,o=e);for(var a=0;a<t.length;a++)i=t[a].apply(o,r?[i]:[]);return r?i:o}}function m(e,t,n,r){if("function"!=typeof e&&(r||void 0!==e))throw new TypeError(t+" must "+(n||"be")+" a function"+(r?"":" or undefined"));return e}function applyDec(e,t,n,r,o,i,u,f,p,h,y,v){function g(e){if(!v(e))throw new TypeError("Attempted to access private element on non-instance")}var b=[].concat(t[0]),w=t[3],D=!u,S=3===o,j=4===o,E=2===o;function I(t,n,r){return function(o,i){return n&&(i=o,o=e),r&&r(o),P[t].call(o,i)}}if(!D){var P={},k=[],F=S?"get":j||y?"set":"value";if(p?(h||y?P={get:setFunctionName((function(){return w(this)}),r,"get"),set:function(e){t[4](this,e)}}:P[F]=w,h||setFunctionName(P[F],r,E?"":F)):h||(P=Object.getOwnPropertyDescriptor(e,r)),!h&&!p){if((c=l[+f][r])&&7!=(c^o))throw new Error("Decorating two elements with the same name ("+P[F].name+") is not supported yet");l[+f][r]=o<3?1:o}}for(var N=e,O=b.length-1;O>=0;O-=n?2:1){var z=b[O],H=n?b[O-1]:void 0,K={},R={kind:["field","accessor","method","getter","setter","class"][o],name:r,metadata:a,addInitializer:function(e,t){if(e.v)throw new Error("attempted to call addInitializer after decoration was finished");m(t,"An initializer","be",!0),i.push(t)}.bind(null,K)};if(D)c=z.call(H,N,R),K.v=1,m(c,"class decorators","return")&&(N=c);else if(R.static=f,R.private=p,c=R.access={has:p?v.bind():function(e){return r in e}},j||(c.get=p?E?function(e){return g(e),P.value}:I("get",0,g):function(e){return e[r]}),E||S||(c.set=p?I("set",0,g):function(e,t){e[r]=t}),N=z.call(H,y?{get:P.get,set:P.set}:P[F],R),K.v=1,y){if("object"==typeof N&&N)(c=m(N.get,"accessor.get"))&&(P.get=c),(c=m(N.set,"accessor.set"))&&(P.set=c),(c=m(N.init,"accessor.init"))&&k.unshift(c);else if(void 0!==N)throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined")}else m(N,(h?"field":"method")+" decorators","return")&&(h?k.unshift(N):P[F]=N)}return o<2&&u.push(d(k,f,1),d(i,f,0)),h||D||(p?y?u.splice(-1,0,I("get",f),I("set",f)):u.push(E?P[F]:m.call.bind(P[F])):s(e,r,P)),N}function h(e){return s(e,u,{configurable:!0,enumerable:!0,value:a})}return void 0!==i&&(a=i[u]),a=f(null==a?null:a),c=function(){var n,r,i=[];function a(e){e&&i.push(d(e))}for(var c=0;c<t.length;c++){var u=t[c],s=u[1],f=u[2],l=!!u[3],p=16&s,m=!!(8&s),h=0==(s&=7),y=1===s;applyDec(m?e:e.prototype,u,p,l?"#"+f:toPropertyKey(f),s,h||y?[]:m?r=r||[]:n=n||[],i,m,l,h,y,m&&l?function(t){return checkInRHS(t)===e}:o)}return a(n),a(r),i}(),p||h(e),{e:c,get c(){var t=[];return p&&[h(e=applyDec(e,[n],r,e.name,5,t)),d(t,1)]}}}',
),
// size: 187, gzip size: 167
assertClassBrand: helper(
Expand Down
56 changes: 37 additions & 19 deletions packages/babel-helpers/src/helpers/applyDecs2311.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,16 +213,21 @@ export default /* @no-mangle */ function applyDecs2311(
// This is a temporary variable for smaller helper size
var _: any;

function runInitializers(
function createRunInitializers(
initializers: Function[],
hasValue: 0 | 1,
thisArg: any,
value?: any,
useStaticThis?: 0 | 1 | boolean,
hasValue?: 0 | 1,
) {
for (var i = 0; i < initializers.length; i++) {
value = initializers[i].apply(thisArg, hasValue ? [value] : []);
}
return hasValue ? value : thisArg;
return function (thisArg: any, value?: any) {
if (useStaticThis) {
value = thisArg;
thisArg = targetClass;
}
for (var i = 0; i < initializers.length; i++) {
value = initializers[i].apply(thisArg, hasValue ? [value] : []);
}
return hasValue ? value : thisArg;
};
}

function assertCallable(
Expand Down Expand Up @@ -276,8 +281,16 @@ export default /* @no-mangle */ function applyDecs2311(
var isSetter = kind === PROP_KIND.SETTER;
var isMethod = kind === PROP_KIND.METHOD;

function _bindPropCall(name: keyof PropertyDescriptor, before?: Function) {
function _bindPropCall(
name: keyof PropertyDescriptor,
useStaticThis: 0 | 1 | boolean,
before?: Function,
) {
return function (_this: any, value?: any) {
if (useStaticThis) {
value = _this;
_this = Class;
}
if (before) {
before(_this);
}
Expand Down Expand Up @@ -395,14 +408,14 @@ export default /* @no-mangle */ function applyDecs2311(
assertInstanceIfPrivate(_this);
return desc.value;
}
: _bindPropCall("get", assertInstanceIfPrivate)
: _bindPropCall("get", 0, assertInstanceIfPrivate)
: function (target: any) {
return target[name];
};
}
if (!isMethod && !isGetter) {
_.set = isPrivate
? _bindPropCall("set", assertInstanceIfPrivate)
? _bindPropCall("set", 0, assertInstanceIfPrivate)
: function (target: any, v: any) {
target[name] = v;
};
Expand Down Expand Up @@ -456,23 +469,28 @@ export default /* @no-mangle */ function applyDecs2311(
if (kind < PROP_KIND.METHOD) {
ret.push(
// init
runInitializers.bind(null, init, 1),
createRunInitializers(init, isStatic, 1),
// init_extra
runInitializers.bind(null, initializers, 0),
createRunInitializers(initializers, isStatic, 0),
);
}

if (!isField && !isClass) {
if (isPrivate) {
if (isAccessor) {
// get and set should be returned before init_extra
ret.splice(-1, 0, _bindPropCall("get"), _bindPropCall("set"));
ret.splice(
-1,
0,
_bindPropCall("get", isStatic),
_bindPropCall("set", isStatic),
);
} else {
ret.push(
isMethod
? desc[key]
: // Equivalent to `Function.call`, just to reduce code size
runInitializers.call.bind(desc[key]),
assertCallable.call.bind(desc[key]),
);
}
} else {
Expand All @@ -490,7 +508,7 @@ export default /* @no-mangle */ function applyDecs2311(

function pushInitializers(initializers: Function[]) {
if (initializers) {
ret.push(runInitializers.bind(null, initializers, 0));
ret.push(createRunInitializers(initializers));
}
}

Expand Down Expand Up @@ -562,16 +580,16 @@ export default /* @no-mangle */ function applyDecs2311(
return (
hasClassDecs && [
defineMetadata(
applyDec(
(targetClass = applyDec(
targetClass,
[classDecs],
classDecsHaveThis,
targetClass.name,
PROP_KIND.CLASS,
initializers,
),
)),
),
runInitializers.bind(null, initializers, 0, targetClass),
createRunInitializers(initializers, 1),
]
);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,38 +62,38 @@ class Foo {
}
_Foo = Foo;
function _set_a2(v) {
_set_a(this, v);
_set_a(v);
}
function _get_a2() {
return _get_a(this);
return _get_a();
}
[_init_a, _init_extra_a, _init_a2, _get_a, _set_a, _init_extra_a2, _init_computedKey, _init_extra_computedKey, _init_computedKey2, _init_extra_computedKey2, _init_computedKey3, _init_extra_computedKey3, _init_computedKey4, _init_extra_computedKey4, _init_computedKey5, _init_extra_computedKey5, _init_computedKey6, _init_extra_computedKey6, _init_computedKey7, _init_extra_computedKey7] = babelHelpers.applyDecs2311(_Foo, [[dec, 9, "a"], [dec, 9, "a", o => babelHelpers.assertClassBrand(o, _Foo, _B)._, (o, v) => _B._ = babelHelpers.assertClassBrand(o, _Foo, v)], [dec, 9, "b"], [dec, 9, "c"], [dec, 9, 0], [dec, 9, 1], [dec, 9, 2n], [dec, 9, 3n], [dec, 9, _computedKey]], []).e;
var _A = {
_: _init_a(_Foo)
_: _init_a()
};
var _B = {
_: (_init_extra_a(_Foo), _init_a2(_Foo))
_: (_init_extra_a(), _init_a2())
};
var _C = {
_: (_init_extra_a2(_Foo), _init_computedKey(_Foo))
_: (_init_extra_a2(), _init_computedKey())
};
var _D = {
_: (_init_extra_computedKey(_Foo), _init_computedKey2(_Foo))
_: (_init_extra_computedKey(), _init_computedKey2())
};
var _E = {
_: (_init_extra_computedKey2(_Foo), _init_computedKey3(_Foo))
_: (_init_extra_computedKey2(), _init_computedKey3())
};
var _F = {
_: (_init_extra_computedKey3(_Foo), _init_computedKey4(_Foo))
_: (_init_extra_computedKey3(), _init_computedKey4())
};
var _G = {
_: (_init_extra_computedKey4(_Foo), _init_computedKey5(_Foo))
_: (_init_extra_computedKey4(), _init_computedKey5())
};
var _H = {
_: (_init_extra_computedKey5(_Foo), _init_computedKey6(_Foo))
_: (_init_extra_computedKey5(), _init_computedKey6())
};
var _I = {
_: (_init_extra_computedKey6(_Foo), _init_computedKey7(_Foo))
_: (_init_extra_computedKey6(), _init_computedKey7())
};
_init_extra_computedKey7(_Foo);
_init_extra_computedKey7();
expect(logs).toStrictEqual(["computing f", "calling toPrimitive", "a", "#a", "b", "c", "0", "1", "2", "3", "f()"]);
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ const dec = () => {};
class Foo {}
_Foo = Foo;
function _set_a2(v) {
_set_a(this, v);
_set_a(v);
}
function _get_a2() {
return _get_a(this);
return _get_a();
}
function _set_b2(v) {
_set_b(this, v);
_set_b(v);
}
function _get_b2() {
return _get_b(this);
return _get_b();
}
[_init_a, _get_a, _set_a, _init_extra_a, _init_b, _get_b, _set_b, _init_extra_b] = babelHelpers.applyDecs2311(_Foo, [[dec, 9, "a", o => babelHelpers.assertClassBrand(o, _Foo, _A)._, (o, v) => _A._ = babelHelpers.assertClassBrand(o, _Foo, v)], [dec, 9, "b", o => babelHelpers.assertClassBrand(o, _Foo, _B)._, (o, v) => _B._ = babelHelpers.assertClassBrand(o, _Foo, v)]], []).e;
var _A = {
_: _init_a(_Foo)
_: _init_a()
};
var _B = {
_: (_init_extra_a(_Foo), _init_b(_Foo, 123))
_: (_init_extra_a(), _init_b(123))
};
_init_extra_b(_Foo);
_init_extra_b();
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ class Foo {
_Foo = Foo;
[_init_a, _init_extra_a, _init_b, _init_extra_b, _init_computedKey, _init_extra_computedKey] = babelHelpers.applyDecs2311(_Foo, [[dec, 9, "a"], [dec, 9, "b"], [dec, 9, 'c']], []).e;
var _A = {
_: _init_a(_Foo)
_: _init_a()
};
var _B = {
_: (_init_extra_a(_Foo), _init_b(_Foo, 123))
_: (_init_extra_a(), _init_b(123))
};
var _C = {
_: (_init_extra_b(_Foo), _init_computedKey(_Foo, 456))
_: (_init_extra_b(), _init_computedKey(456))
};
_init_extra_computedKey(_Foo);
_init_extra_computedKey();

0 comments on commit 6e79c05

Please sign in to comment.