Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More aggressively inline decorators in the static block #16294

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion babel.config.js
Expand Up @@ -543,7 +543,6 @@ function pluginToggleBooleanFlag({ types: t }, { name, value }) {
if (left.value === false) return res.replace(right.replacement);
if (right.value === false) return res.replace(left.replacement);
if (left.unrelated && right.unrelated) return res.unrelated();
console.log(left, right);
return res.replace(
t.logicalExpression("||", left.replacement, right.replacement)
);
Expand Down
Expand Up @@ -751,6 +751,32 @@ function createPrivateBrandCheckClosure(brandName: t.PrivateName) {
);
}

// Check if the expression does not reference function-specific
// context or the given identifier name.
// `true` means "maybe" and `false` means "no".
function usesFunctionContextOrRef(
expression: t.Node,
refName: string | undefined,
) {
try {
t.traverseFast(expression, node => {
if (
t.isThisExpression(node) ||
t.isSuper(node) ||
t.isIdentifier(node, { name: "arguments" }) ||
(t.isMetaProperty(node) && node.meta.name !== "import") ||
(refName && t.isIdentifier(node, { name: refName }))
) {
// TODO: Add early return support to t.traverseFast
throw null;
}
});
return false;
} catch {
return true;
}
}

function checkPrivateMethodUpdateError(
path: NodePath<t.Class>,
decoratedPrivateMethods: Set<string>,
Expand Down Expand Up @@ -810,6 +836,8 @@ function transformClass(

const classDecorators = path.node.decorators;
let hasElementDecorators = false;
let hasComputedKeysSideEffects = false;
let elemDecsUseFnContext = false;

const generateClassPrivateUid = createLazyPrivateUidGeneratorForClass(path);

Expand Down Expand Up @@ -868,6 +896,9 @@ function transformClass(
break;
}
hasElementDecorators = true;
elemDecsUseFnContext ||= element.node.decorators.some(dec =>
usesFunctionContextOrRef(dec, path.node.id?.name),
);
} else if (element.node.type === "ClassAccessorProperty") {
// @ts-expect-error todo: propertyVisitor.ClassAccessorProperty should be callable. Improve typings.
propertyVisitor.ClassAccessorProperty(
Expand Down Expand Up @@ -896,6 +927,10 @@ function transformClass(
isStatic,
);
}

if ("computed" in element.node && element.node.computed) {
hasComputedKeysSideEffects ||= !scopeParent.isStatic(element.node.key);
}
}

if (!classDecorators && !hasElementDecorators) {
Expand All @@ -922,14 +957,16 @@ function transformClass(
// Memoise the this value `a.b` of decorator member expressions `@a.b.dec`,
type HandleDecoratorExpressionsResult = {
// whether the whole decorator list requires memoisation
needMemoise: boolean;
hasSideEffects: boolean;
usesFnContext: boolean;
// the this value of each decorator if applicable
decoratorsThis: (t.Expression | undefined)[];
};
function handleDecoratorExpressions(
expressions: t.Expression[],
): HandleDecoratorExpressionsResult {
let needMemoise = false;
let hasSideEffects = false;
let usesFnContext = false;
const decoratorsThis: (t.Expression | null)[] = [];
for (const expression of expressions) {
let object;
Expand All @@ -954,11 +991,21 @@ function transformClass(
}
}
decoratorsThis.push(object);
needMemoise ||= !scopeParent.isStatic(expression);
hasSideEffects ||= !scopeParent.isStatic(expression);
usesFnContext ||= usesFunctionContextOrRef(
expression,
path.node.id?.name,
);
}
return { needMemoise, decoratorsThis };
return { hasSideEffects, usesFnContext, decoratorsThis };
}

const willExtractSomeElemDecs =
hasComputedKeysSideEffects ||
(process.env.BABEL_8_BREAKING
? elemDecsUseFnContext
: elemDecsUseFnContext || version !== "2023-11");

let needsDeclaraionForClassBinding = false;
let classDecorationsFlag = 0;
let classDecorations: t.Expression[] = [];
Expand All @@ -971,7 +1018,7 @@ function transformClass(
path.node.decorators = null;

const decoratorExpressions = classDecorators.map(el => el.expression);
const { needMemoise, decoratorsThis } =
const { hasSideEffects, decoratorsThis } =
handleDecoratorExpressions(decoratorExpressions);

const { haveThis, decs } = generateDecorationList(
Expand All @@ -982,7 +1029,7 @@ function transformClass(
classDecorationsFlag = haveThis ? 1 : 0;
classDecorations = decs;

if (needMemoise) {
if (hasSideEffects && willExtractSomeElemDecs) {
classDecorationsId = memoiseExpression(
t.arrayExpression(classDecorations),
"classDecs",
Expand Down Expand Up @@ -1042,16 +1089,22 @@ function transformClass(

if (hasDecorators) {
const decoratorExpressions = decorators.map(d => d.expression);
const { needMemoise, decoratorsThis } =
handleDecoratorExpressions(decoratorExpressions);
const {
hasSideEffects,
usesFnContext: usesFunctionContext,
decoratorsThis,
} = handleDecoratorExpressions(decoratorExpressions);
const { decs, haveThis } = generateDecorationList(
decoratorExpressions,
decoratorsThis,
version,
);
decoratorsHaveThis = haveThis;
decoratorsArray = decs.length === 1 ? decs[0] : t.arrayExpression(decs);
if (needMemoise) {
if (
usesFunctionContext ||
(hasSideEffects && willExtractSomeElemDecs)
) {
decoratorsArray = memoiseExpression(decoratorsArray, name + "Decs");
}
}
Expand Down Expand Up @@ -1311,7 +1364,9 @@ function transformClass(
toSortedDecoratorInfo(elementDecoratorInfo);

const elementDecorations = generateDecorationExprs(
sortedElementDecoratorInfo,
process.env.BABEL_8_BREAKING || version === "2023-11"
? elementDecoratorInfo
: sortedElementDecoratorInfo,
version,
);

Expand Down Expand Up @@ -1500,11 +1555,14 @@ function createLocalsAssignment(
setClassName
? createSetFunctionNameCall(state, setClassName)
: t.thisExpression(),
elementDecorations,
classDecorations,
elementDecorations,
];

if (!process.env.BABEL_8_BREAKING) {
if (version !== "2023-11") {
args.splice(1, 2, elementDecorations, classDecorations);
}
if (
version === "2021-12" ||
(version === "2022-03" && !state.availableHelper("applyDecs2203R"))
Expand Down
4 changes: 2 additions & 2 deletions packages/babel-helpers/src/helpers-generated.ts
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: 2917, gzip size: 1487
// size: 2957, gzip size: 1507
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,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)]}}}',
'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,s,f,l,p,d=Symbol.metadata||Symbol.for("Symbol.metadata"),m=Object.defineProperty,h=Object.create,y=[h(null),h(null)],v=t.length;function g(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 b(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,s,f,l,p){function d(e){if(!p(e))throw new TypeError("Attempted to access private element on non-instance")}var h=[].concat(t[0]),v=t[3],w=!u,D=1===o,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(!w){var P={},k=[],F=S?"get":j||D?"set":"value";if(f?(l||D?P={get:setFunctionName((function(){return v(this)}),r,"get"),set:function(e){t[4](this,e)}}:P[F]=v,l||setFunctionName(P[F],r,E?"":F)):l||(P=Object.getOwnPropertyDescriptor(e,r)),!l&&!f){if((c=y[+s][r])&&7!=(c^o))throw new Error("Decorating two elements with the same name ("+P[F].name+") is not supported yet");y[+s][r]=o<3?1:o}}for(var N=e,O=h.length-1;O>=0;O-=n?2:1){var z=h[O],H=n?h[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");b(t,"An initializer","be",!0),i.push(t)}.bind(null,K)};if(w)c=z.call(H,N,R),K.v=1,b(c,"class decorators","return")&&(N=c);else if(R.static=s,R.private=f,c=R.access={has:f?p.bind():function(e){return r in e}},j||(c.get=f?E?function(e){return d(e),P.value}:I("get",0,d):function(e){return e[r]}),E||S||(c.set=f?I("set",0,d):function(e,t){e[r]=t}),N=z.call(H,D?{get:P.get,set:P.set}:P[F],R),K.v=1,D){if("object"==typeof N&&N)(c=b(N.get,"accessor.get"))&&(P.get=c),(c=b(N.set,"accessor.set"))&&(P.set=c),(c=b(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 b(N,(l?"field":"method")+" decorators","return")&&(l?k.unshift(N):P[F]=N)}return o<2&&u.push(g(k,s,1),g(i,s,0)),l||w||(f?D?u.splice(-1,0,I("get",s),I("set",s)):u.push(E?P[F]:b.call.bind(P[F])):m(e,r,P)),N}function w(e){return m(e,d,{configurable:!0,enumerable:!0,value:a})}return void 0!==i&&(a=i[d]),a=h(null==a?null:a),f=[],l=function(e){e&&f.push(g(e))},p=function(t,r){for(var i=0;i<n.length;i++){var a=n[i],c=a[1],l=7&c;if((8&c)==t&&0==l==r){var p=a[2],d=!!a[3],m=16&c;applyDec(t?e:e.prototype,a,m,d?"#"+p:toPropertyKey(p),l,l<2?[]:t?s=s||[]:u=u||[],f,!!t,d,r,t&&d?function(t){return checkInRHS(t)===e}:o)}}},p(8,!1),p(0,!1),p(8,!0),p(0,!0),l(u),l(s),c=f,v||w(e),{e:c,get c(){var n=[];return v&&[w(e=applyDec(e,[t],r,e.name,5,n)),g(n,1)]}}}',
),
// size: 187, gzip size: 167
assertClassBrand: helper(
Expand Down
95 changes: 53 additions & 42 deletions packages/babel-helpers/src/helpers/applyDecs2311.ts
Expand Up @@ -12,6 +12,7 @@ const enum PROP_KIND {
GETTER = 3,
SETTER = 4,
CLASS = 5,
KIND_MASK = 7, // 0b111

STATIC = 8,

Expand Down Expand Up @@ -194,8 +195,8 @@ type DecoratorNonFieldCheckStorage = Record<

export default /* @no-mangle */ function applyDecs2311(
targetClass: any,
memberDecs: DecoratorInfo[],
classDecs: Function[],
memberDecs: DecoratorInfo[],
classDecsHaveThis: number,
instanceBrand: Function,
parentClass: any,
Expand Down Expand Up @@ -262,7 +263,6 @@ export default /* @no-mangle */ function applyDecs2311(
isStatic?: boolean,
isPrivate?: boolean,
isField?: boolean,
isAccessor?: boolean,
hasPrivateBrand?: Function,
) {
function assertInstanceIfPrivate(target: any) {
Expand All @@ -277,6 +277,7 @@ export default /* @no-mangle */ function applyDecs2311(
decVal = decInfo[3],
isClass = !ret;

var isAccessor = kind === PROP_KIND.ACCESSOR;
var isGetter = kind === PROP_KIND.GETTER;
var isSetter = kind === PROP_KIND.SETTER;
var isMethod = kind === PROP_KIND.METHOD;
Expand Down Expand Up @@ -506,50 +507,60 @@ export default /* @no-mangle */ function applyDecs2311(
var protoInitializers: Function[];
var staticInitializers: Function[];

function pushInitializers(initializers: Function[]) {
var pushInitializers = function (initializers: Function[]) {
if (initializers) {
ret.push(createRunInitializers(initializers));
}
}
};

for (var i = 0; i < memberDecs.length; i++) {
var decInfo = memberDecs[i];

var kind = decInfo[1];
var name = decInfo[2];
var isPrivate = !!decInfo[3];

var decoratorsHaveThis = kind & PROP_KIND.DECORATORS_HAVE_THIS;
var isStatic = !!(kind & PROP_KIND.STATIC);

kind &= 7; /* 0b111 */

var isField = kind === PROP_KIND.FIELD;
var isAccessor = kind === PROP_KIND.ACCESSOR;

applyDec(
isStatic ? targetClass : targetClass.prototype,
decInfo,
decoratorsHaveThis,
isPrivate ? "#" + name : (toPropertyKey(name) as string),
kind,
isField || isAccessor
? /* fieldInitializers */ []
: isStatic
? (staticInitializers = staticInitializers || [])
: (protoInitializers = protoInitializers || []),
ret,
isStatic,
isPrivate,
isField,
isAccessor,
isStatic && isPrivate
? function (_: any) {
return checkInRHS(_) === targetClass;
}
: instanceBrand,
);
}
var applyMemberDecsOfKind = function (
isStatic: PROP_KIND.STATIC | 0,
isField: boolean,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can use isField?: 1,?

) {
for (var i = 0; i < memberDecs.length; i++) {
var decInfo = memberDecs[i];

var kind = decInfo[1];
var kindOnly: PROP_KIND = kind & PROP_KIND.KIND_MASK;
if (
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
(kind & PROP_KIND.STATIC) == isStatic &&
(kindOnly == PROP_KIND.FIELD) == isField
) {
var name = decInfo[2];
var isPrivate = !!decInfo[3];

var decoratorsHaveThis = kind & PROP_KIND.DECORATORS_HAVE_THIS;

applyDec(
isStatic ? targetClass : targetClass.prototype,
decInfo,
decoratorsHaveThis,
isPrivate ? "#" + name : (toPropertyKey(name) as string),
kindOnly,
kindOnly < PROP_KIND.METHOD // isField || isAccessor
? /* fieldInitializers */ []
: isStatic
? (staticInitializers = staticInitializers || [])
: (protoInitializers = protoInitializers || []),
ret,
!!isStatic,
isPrivate,
isField,
isStatic && isPrivate
? function (_: any) {
return checkInRHS(_) === targetClass;
}
: instanceBrand,
);
}
}
};

applyMemberDecsOfKind(PROP_KIND.STATIC, false);
applyMemberDecsOfKind(0, false);
applyMemberDecsOfKind(PROP_KIND.STATIC, true);
applyMemberDecsOfKind(0, true);

pushInitializers(protoInitializers);
pushInitializers(staticInitializers);
Expand Down