Skip to content

Commit 3ff20b9

Browse files
Statically generate boilerplate for bitfield accessors (#16482)
* Statically generate boilerplate for parser state accessors * Also use in `@babel/traverse` * Node 6 compat in the plugin * Use public field
1 parent 4bd1b2c commit 3ff20b9

5 files changed

Lines changed: 190 additions & 174 deletions

File tree

‎babel.config.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ module.exports = function (api) {
187187

188188
convertESM ? "@babel/transform-export-namespace-from" : null,
189189
env !== "standalone" ? "@babel/plugin-proposal-json-modules" : null,
190+
191+
require("./scripts/babel-plugin-bit-decorator/plugin.cjs"),
190192
].filter(Boolean),
191193
overrides: [
192194
{

‎packages/babel-parser/src/tokenizer/state.ts‎

Lines changed: 17 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -23,40 +23,18 @@ type TopicContextState = {
2323
maxTopicIndex: null | 0;
2424
};
2525

26-
const enum StateFlags {
27-
None = 0,
28-
Strict = 1 << 0,
29-
maybeInArrowParameters = 1 << 1,
30-
inType = 1 << 2,
31-
noAnonFunctionType = 1 << 3,
32-
hasFlowComment = 1 << 4,
33-
isAmbientContext = 1 << 5,
34-
inAbstractClass = 1 << 6,
35-
inDisallowConditionalTypesContext = 1 << 7,
36-
soloAwait = 1 << 8,
37-
inFSharpPipelineDirectBody = 1 << 9,
38-
canStartJSXElement = 1 << 10,
39-
containsEsc = 1 << 11,
40-
}
41-
4226
export const enum LoopLabelKind {
4327
Loop = 1,
4428
Switch = 2,
4529
}
4630

31+
declare const bit: import("../../../../scripts/babel-plugin-bit-decorator/types.d.ts").BitDecorator<State>;
32+
4733
export default class State {
48-
flags: number = StateFlags.canStartJSXElement;
34+
@bit.storage flags: number;
35+
36+
@bit accessor strict = false;
4937

50-
get strict(): boolean {
51-
return (this.flags & StateFlags.Strict) > 0;
52-
}
53-
set strict(value: boolean) {
54-
if (value) {
55-
this.flags |= StateFlags.Strict;
56-
} else {
57-
this.flags &= ~StateFlags.Strict;
58-
}
59-
}
6038
curLine: number;
6139
lineStart: number;
6240

@@ -98,76 +76,13 @@ export default class State {
9876
noArrowParamsConversionAt: number[] = [];
9977

10078
// Flags to track
101-
get maybeInArrowParameters(): boolean {
102-
return (this.flags & StateFlags.maybeInArrowParameters) > 0;
103-
}
104-
set maybeInArrowParameters(value: boolean) {
105-
if (value) {
106-
this.flags |= StateFlags.maybeInArrowParameters;
107-
} else {
108-
this.flags &= ~StateFlags.maybeInArrowParameters;
109-
}
110-
}
111-
get inType(): boolean {
112-
return (this.flags & StateFlags.inType) > 0;
113-
}
114-
set inType(value: boolean) {
115-
if (value) {
116-
this.flags |= StateFlags.inType;
117-
} else {
118-
this.flags &= ~StateFlags.inType;
119-
}
120-
}
121-
get noAnonFunctionType(): boolean {
122-
return (this.flags & StateFlags.noAnonFunctionType) > 0;
123-
}
124-
set noAnonFunctionType(value: boolean) {
125-
if (value) {
126-
this.flags |= StateFlags.noAnonFunctionType;
127-
} else {
128-
this.flags &= ~StateFlags.noAnonFunctionType;
129-
}
130-
}
131-
get hasFlowComment(): boolean {
132-
return (this.flags & StateFlags.hasFlowComment) > 0;
133-
}
134-
set hasFlowComment(value: boolean) {
135-
if (value) {
136-
this.flags |= StateFlags.hasFlowComment;
137-
} else {
138-
this.flags &= ~StateFlags.hasFlowComment;
139-
}
140-
}
141-
get isAmbientContext(): boolean {
142-
return (this.flags & StateFlags.isAmbientContext) > 0;
143-
}
144-
set isAmbientContext(value: boolean) {
145-
if (value) {
146-
this.flags |= StateFlags.isAmbientContext;
147-
} else {
148-
this.flags &= ~StateFlags.isAmbientContext;
149-
}
150-
}
151-
get inAbstractClass(): boolean {
152-
return (this.flags & StateFlags.inAbstractClass) > 0;
153-
}
154-
set inAbstractClass(value: boolean) {
155-
if (value) {
156-
this.flags |= StateFlags.inAbstractClass;
157-
} else {
158-
this.flags &= ~StateFlags.inAbstractClass;
159-
}
160-
}
161-
get inDisallowConditionalTypesContext(): boolean {
162-
return (this.flags & StateFlags.inDisallowConditionalTypesContext) > 0;
163-
}
164-
set inDisallowConditionalTypesContext(value: boolean) {
165-
if (value) {
166-
this.flags |= StateFlags.inDisallowConditionalTypesContext;
167-
} else {
168-
this.flags &= ~StateFlags.inDisallowConditionalTypesContext;
169-
}
170-
}
79+
@bit accessor maybeInArrowParameters = false;
80+
@bit accessor inType = false;
81+
@bit accessor noAnonFunctionType = false;
82+
@bit accessor hasFlowComment = false;
83+
@bit accessor isAmbientContext = false;
84+
@bit accessor inAbstractClass = false;
85+
@bit accessor inDisallowConditionalTypesContext = false;
17186

17287
// For the Hack-style pipelines plugin
17388
topicContext: TopicContextState = {
@@ -176,26 +91,8 @@ export default class State {
17691
};
17792

17893
// For the F#-style pipelines plugin
179-
get soloAwait(): boolean {
180-
return (this.flags & StateFlags.soloAwait) > 0;
181-
}
182-
set soloAwait(value: boolean) {
183-
if (value) {
184-
this.flags |= StateFlags.soloAwait;
185-
} else {
186-
this.flags &= ~StateFlags.soloAwait;
187-
}
188-
}
189-
get inFSharpPipelineDirectBody(): boolean {
190-
return (this.flags & StateFlags.inFSharpPipelineDirectBody) > 0;
191-
}
192-
set inFSharpPipelineDirectBody(value: boolean) {
193-
if (value) {
194-
this.flags |= StateFlags.inFSharpPipelineDirectBody;
195-
} else {
196-
this.flags &= ~StateFlags.inFSharpPipelineDirectBody;
197-
}
198-
}
94+
@bit accessor soloAwait = false;
95+
@bit accessor inFSharpPipelineDirectBody = false;
19996

20097
// Labels in scope.
20198
labels: Array<{
@@ -231,31 +128,14 @@ export default class State {
231128
// The context stack is used to track whether the apostrophe "`" starts
232129
// or ends a string template
233130
context: Array<TokContext> = [ct.brace];
131+
234132
// Used to track whether a JSX element is allowed to form
235-
get canStartJSXElement(): boolean {
236-
return (this.flags & StateFlags.canStartJSXElement) > 0;
237-
}
238-
set canStartJSXElement(value: boolean) {
239-
if (value) {
240-
this.flags |= StateFlags.canStartJSXElement;
241-
} else {
242-
this.flags &= ~StateFlags.canStartJSXElement;
243-
}
244-
}
133+
@bit accessor canStartJSXElement = true;
245134

246135
// Used to signal to callers of `readWord1` whether the word
247136
// contained any escape sequences. This is needed because words with
248137
// escape sequences must not be interpreted as keywords.
249-
get containsEsc(): boolean {
250-
return (this.flags & StateFlags.containsEsc) > 0;
251-
}
252-
set containsEsc(value: boolean) {
253-
if (value) {
254-
this.flags |= StateFlags.containsEsc;
255-
} else {
256-
this.flags &= ~StateFlags.containsEsc;
257-
}
258-
}
138+
@bit accessor containsEsc = false;
259139

260140
// Used to track invalid escape sequences in template literals,
261141
// that must be reported if the template is not tagged.

‎packages/babel-traverse/src/path/index.ts‎

Lines changed: 8 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export const REMOVED = 1 << 0;
3333
export const SHOULD_STOP = 1 << 1;
3434
export const SHOULD_SKIP = 1 << 2;
3535

36+
declare const bit: import("../../../../scripts/babel-plugin-bit-decorator/types.d.ts").BitDecorator<any>;
37+
3638
const NodePath_Final = class NodePath {
3739
constructor(hub: HubInterface, parent: t.Node | null) {
3840
this.parent = parent;
@@ -53,8 +55,12 @@ const NodePath_Final = class NodePath {
5355
contexts: Array<TraversalContext> = [];
5456
state: any = null;
5557
opts: ExplodedTraverseOptions | null = null;
56-
// this.shouldSkip = false; this.shouldStop = false; this.removed = false;
57-
_traverseFlags: number = 0;
58+
59+
@bit.storage _traverseFlags: number;
60+
@bit(REMOVED) accessor removed = false;
61+
@bit(SHOULD_STOP) accessor shouldStop = false;
62+
@bit(SHOULD_SKIP) accessor shouldSkip = false;
63+
5864
skipKeys: Record<string, boolean> | null = null;
5965
parentPath: NodePath_Final | null = null;
6066
container: t.Node | Array<t.Node> | null = null;
@@ -180,41 +186,6 @@ const NodePath_Final = class NodePath {
180186
get parentKey(): string {
181187
return (this.listKey || this.key) as string;
182188
}
183-
184-
get shouldSkip() {
185-
return !!(this._traverseFlags & SHOULD_SKIP);
186-
}
187-
188-
set shouldSkip(v) {
189-
if (v) {
190-
this._traverseFlags |= SHOULD_SKIP;
191-
} else {
192-
this._traverseFlags &= ~SHOULD_SKIP;
193-
}
194-
}
195-
196-
get shouldStop() {
197-
return !!(this._traverseFlags & SHOULD_STOP);
198-
}
199-
200-
set shouldStop(v) {
201-
if (v) {
202-
this._traverseFlags |= SHOULD_STOP;
203-
} else {
204-
this._traverseFlags &= ~SHOULD_STOP;
205-
}
206-
}
207-
208-
get removed() {
209-
return !!(this._traverseFlags & REMOVED);
210-
}
211-
set removed(v) {
212-
if (v) {
213-
this._traverseFlags |= REMOVED;
214-
} else {
215-
this._traverseFlags &= ~REMOVED;
216-
}
217-
}
218189
};
219190

220191
Object.assign(
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
module.exports = pluginBabelBitDecorator;
2+
3+
/** @param {{ types: import("@babel/types") }} api */
4+
function pluginBabelBitDecorator({ types: t, template }) {
5+
const bodyTemplate = template.statement({ allowReturnOutsideFunction: true });
6+
7+
return {
8+
manipulateOptions({ parserOpts }) {
9+
parserOpts.plugins.push("decorators", "decoratorAutoAccessors");
10+
},
11+
visitor: {
12+
Class(path) {
13+
let storageField;
14+
let storageName;
15+
16+
for (const element of path.get("body.body")) {
17+
if (
18+
(element.isClassProperty() || element.isClassPrivateProperty()) &&
19+
element.node.decorators &&
20+
element.node.decorators.some(dec =>
21+
t.matchesPattern(dec.expression, "bit.storage")
22+
)
23+
) {
24+
element.node.decorators = element.node.decorators.filter(
25+
dec => !t.matchesPattern(dec.expression, "bit.storage")
26+
);
27+
storageField = element;
28+
storageName = element.node.key;
29+
break;
30+
}
31+
}
32+
33+
let initial = 0;
34+
let nextMask = 1;
35+
36+
for (const element of path.get("body.body")) {
37+
let dec;
38+
if (
39+
element.isClassAccessorProperty() &&
40+
(dec =
41+
element.node.decorators &&
42+
element
43+
.get("decorators")
44+
.find(
45+
({ node: dec }) =>
46+
t.isIdentifier(dec.expression, { name: "bit" }) ||
47+
(t.isCallExpression(dec.expression) &&
48+
t.isIdentifier(dec.expression.callee, { name: "bit" }))
49+
))
50+
) {
51+
if (element.node.static || t.isPrivateName(element.node.key)) {
52+
throw element.buildCodeFrameError(
53+
"@bit cannot be used on static or private fields"
54+
);
55+
}
56+
if (element.node.decorators.length > 1) {
57+
throw element.buildCodeFrameError(
58+
"@bit cannot be used with other decorators"
59+
);
60+
}
61+
if (!t.isBooleanLiteral(element.node.value)) {
62+
throw element.buildCodeFrameError(
63+
"@bit fields must be initialized to a boolean literal"
64+
);
65+
}
66+
if (!storageName) {
67+
throw path.buildCodeFrameError(
68+
"Cannot use @bit withuot also declaring a @bit.storage field"
69+
);
70+
}
71+
if (nextMask === 0) {
72+
// overflow
73+
throw path.buildCodeFrameError(
74+
"A class can contain at most 32 @bit decorators"
75+
);
76+
}
77+
78+
let val;
79+
if (
80+
t.isCallExpression(dec.node.expression) &&
81+
dec.node.expression.arguments.length > 0 &&
82+
(val = dec.get("expression.arguments.0").evaluate().value) !==
83+
nextMask
84+
) {
85+
throw dec.buildCodeFrameError(
86+
`Bit mask is ${nextMask.toString(2)}, but found ${val.toString(2)} (or couldn't evaluate)`
87+
);
88+
}
89+
90+
if (element.node.value.value) {
91+
initial |= nextMask;
92+
}
93+
94+
element.replaceWithMultiple([
95+
t.classMethod(
96+
"get",
97+
element.node.key,
98+
[],
99+
bodyTemplate.ast`{
100+
return (
101+
this.${t.cloneNode(storageName)} & ${t.numericLiteral(nextMask)}
102+
) > 0;
103+
}`
104+
),
105+
t.classMethod(
106+
"set",
107+
element.node.key,
108+
[t.identifier("v")],
109+
bodyTemplate.ast`{
110+
if (v) this.${t.cloneNode(storageName)} |= ${t.numericLiteral(nextMask)};
111+
else this.${t.cloneNode(storageName)} &= ${t.valueToNode(~nextMask)};
112+
}`
113+
),
114+
]);
115+
116+
nextMask <<= 1;
117+
}
118+
}
119+
120+
if (storageField) {
121+
storageField.node.value = t.numericLiteral(initial);
122+
}
123+
},
124+
},
125+
};
126+
}

0 commit comments

Comments
 (0)