feat(compiler): Support MetaProperty (#29752)
## Summary See #29737 ## How did you test this change? As the feature requires module support and the test runner does currently not support running tests as modules, I could only test it via playground.
Niklas Mollenhauer committed
Jun 9, 2024 at 01:18 UTC
f5af92d2c47d1e1f455faf912b1d3221d1038c37
17 files changed
+202
-2
compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts
+22
@@ -2427,6 +2427,28 @@ function lowerExpression(
2427
let expr = exprPath as NodePath<t.TSNonNullExpression>;
2428
return lowerExpression(builder, expr.get("expression"));
2429
}
2430
+ case "MetaProperty": {
2431
+ let expr = exprPath as NodePath<t.MetaProperty>;
2432
+ if (
2433
+ expr.node.meta.name === "import" &&
2434
+ expr.node.property.name === "meta"
2435
+ ) {
2436
+ return {
2437
+ kind: "MetaProperty",
2438
+ meta: expr.node.meta.name,
2439
+ property: expr.node.property.name,
2440
+ loc: expr.node.loc ?? GeneratedSource,
2441
+ };
2442
+ }
2443
+
2444
+ builder.errors.push({
2445
+ reason: `(BuildHIR::lowerExpression) Handle MetaProperty expressions other than import.meta`,
2446
+ severity: ErrorSeverity.Todo,
2447
+ loc: exprPath.node.loc ?? null,
2448
+ suggestions: null,
2449
+ });
2450
+ return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
2451
+ }
2452
default: {
2453
builder.errors.push({
2454
reason: `(BuildHIR::lowerExpression) Handle ${exprPath.type} expressions`,
compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts
+6
@@ -898,6 +898,12 @@ export type InstructionValue =
898
flags: string;
899
loc: SourceLocation;
900
}
901
+ | {
902
+ kind: "MetaProperty";
903
+ meta: string;
904
+ property: string;
905
+ loc: SourceLocation;
906
+ }
907
908
// store `object.property = value`
909
| {
compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts
+4
@@ -636,6 +636,10 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
636
value = `RegExp /${instrValue.pattern}/${instrValue.flags}`;
637
break;
638
}
639
+ case "MetaProperty": {
640
+ value = `MetaProperty ${instrValue.meta}.${instrValue.property}`;
641
+ break;
642
+ }
643
case "Await": {
644
value = `Await ${printPlace(instrValue.value)}`;
645
break;
compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts
+2
@@ -242,6 +242,7 @@ export function* eachInstructionValueOperand(
242
}
243
case "Debugger":
244
case "RegExpLiteral":
245
+ case "MetaProperty":
246
case "LoadGlobal":
247
case "UnsupportedNode":
248
case "Primitive":
@@ -566,6 +567,7 @@ export function mapInstructionValueOperands(
567
}
568
case "Debugger":
569
case "RegExpLiteral":
570
+ case "MetaProperty":
571
case "LoadGlobal":
572
case "UnsupportedNode":
573
case "Primitive":
compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts
+12
@@ -1216,6 +1216,18 @@ function inferBlock(
1216
};
1217
break;
1218
}
1219
+ case "MetaProperty": {
1220
+ if (instrValue.meta !== "import" || instrValue.property !== "meta") {
1221
+ continue;
1222
+ }
1223
+
1224
+ valueKind = {
1225
+ kind: ValueKind.Global,
1226
+ reason: new Set([ValueReason.Global]),
1227
+ context: new Set(),
1228
+ };
1229
+ break;
1230
+ }
1231
case "LoadGlobal":
1232
valueKind = {
1233
kind: ValueKind.Global,
compiler/packages/babel-plugin-react-compiler/src/Optimization/DeadCodeElimination.ts
+1
@@ -350,6 +350,7 @@ function pruneableValue(value: InstructionValue, state: State): boolean {
350
return false;
351
}
352
case "RegExpLiteral":
353
+ case "MetaProperty":
354
case "LoadGlobal":
355
case "ArrayExpression":
356
case "BinaryExpression":
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts
+7
@@ -2021,6 +2021,13 @@ function codegenInstructionValue(
2021
value = t.regExpLiteral(instrValue.pattern, instrValue.flags);
2022
break;
2023
}
2024
+ case "MetaProperty": {
2025
+ value = t.metaProperty(
2026
+ t.identifier(instrValue.meta),
2027
+ t.identifier(instrValue.property)
2028
+ );
2029
+ break;
2030
+ }
2031
case "Await": {
2032
value = t.awaitExpression(codegenPlaceToExpression(cx, instrValue.value));
2033
break;
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts
+1
@@ -199,6 +199,7 @@ function mayAllocate(env: Environment, instruction: Instruction): boolean {
199
case "DeclareContext":
200
case "StoreLocal":
201
case "LoadGlobal":
202
+ case "MetaProperty":
203
case "TypeCastExpression":
204
case "LoadLocal":
205
case "LoadContext":
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts
+1
@@ -459,6 +459,7 @@ function computeMemoizationInputs(
459
case "ComputedDelete":
460
case "PropertyDelete":
461
case "LoadGlobal":
462
+ case "MetaProperty":
463
case "TemplateLiteral":
464
case "Primitive":
465
case "JSXText":
compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts
+1
@@ -338,6 +338,7 @@ function* generateInstructionTypes(
338
case "DeclareLocal":
339
case "NewExpression":
340
case "RegExpLiteral":
341
+ case "MetaProperty":
342
case "PropertyStore":
343
case "ComputedStore":
344
case "ComputedLoad":
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-new-target-meta-property.expect.md
new
+27
@@ -0,0 +1,27 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import { Stringify } from "shared-runtime";
6
+
7
+function foo() {
8
+ const nt = new.target;
9
+ return <Stringify value={nt} />;
10
+}
11
+
12
+```
13
+
14
+
15
+## Error
16
+
17
+```
18
+ 2 |
19
+ 3 | function foo() {
20
+> 4 | const nt = new.target;
21
+ | ^^^^^^^^^^ Todo: (BuildHIR::lowerExpression) Handle MetaProperty expressions other than import.meta (4:4)
22
+ 5 | return <Stringify value={nt} />;
23
+ 6 | }
24
+ 7 |
25
+```
26
+
27
+
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-new-target-meta-property.js
new
+6
@@ -0,0 +1,6 @@
1
+import { Stringify } from "shared-runtime";
2
+
3
+function foo() {
4
+ const nt = new.target;
5
+ return <Stringify value={nt} />;
6
+}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-property.expect.md
new
+67
@@ -0,0 +1,67 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function a() {
6
+ return import.meta.url;
7
+}
8
+
9
+function b() {
10
+ let a = 0;
11
+ if (import.meta.url) {
12
+ a = 1;
13
+ }
14
+ return a;
15
+}
16
+
17
+function c() {
18
+ let a = 0;
19
+ if (import.meta.foo) {
20
+ a = 1;
21
+ }
22
+ return a;
23
+}
24
+
25
+function d() {
26
+ let a = 0;
27
+ if (import.meta) {
28
+ a = 1;
29
+ }
30
+ return a;
31
+}
32
+
33
+```
34
+
35
+## Code
36
+
37
+```javascript
38
+function a() {
39
+ return import.meta.url;
40
+}
41
+
42
+function b() {
43
+ let a = 0;
44
+ if (import.meta.url) {
45
+ a = 1;
46
+ }
47
+ return a;
48
+}
49
+
50
+function c() {
51
+ let a = 0;
52
+ if (import.meta.foo) {
53
+ a = 1;
54
+ }
55
+ return a;
56
+}
57
+
58
+function d() {
59
+ let a = 0;
60
+ if (import.meta) {
61
+ a = 1;
62
+ }
63
+ return a;
64
+}
65
+
66
+```
67
+
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-property.mjs
new
+27
@@ -0,0 +1,27 @@
1
+function a() {
2
+ return import.meta.url;
3
+}
4
+
5
+function b() {
6
+ let a = 0;
7
+ if (import.meta.url) {
8
+ a = 1;
9
+ }
10
+ return a;
11
+}
12
+
13
+function c() {
14
+ let a = 0;
15
+ if (import.meta.foo) {
16
+ a = 1;
17
+ }
18
+ return a;
19
+}
20
+
21
+function d() {
22
+ let a = 0;
23
+ if (import.meta) {
24
+ a = 1;
25
+ }
26
+ return a;
27
+}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/tsconfig.json
+5
-1
@@ -23,8 +23,12 @@
23
},
24
"include": [
25
"./compiler/**/*.js",
26
- "./compiler/**/*.jsx",
26
+ "./compiler/**/*.mjs",
27
+ "./compiler/**/*.cjs",
28
"./compiler/**/*.ts",
29
+ "./compiler/**/*.mts",
30
+ "./compiler/**/*.cts",
31
+ "./compiler/**/*.jsx",
32
"./compiler/**/*.tsx"
33
]
34
}
compiler/packages/snap/src/SproutTodoFilter.ts
+3
@@ -499,6 +499,9 @@ const skipFilter = new Set([
499
"useState-unpruned-dependency",
500
"useState-and-other-hook-unpruned-dependency",
501
"change-detect-reassign",
502
+
503
+ // needs to be executed as a module
504
+ "meta-property",
505
]);
506
507
export default skipFilter;
compiler/packages/snap/src/fixture-utils.ts
+10
-1
@@ -10,7 +10,16 @@ import * as glob from "glob";
10
import path from "path";
11
import { FILTER_PATH, FIXTURES_PATH, SNAPSHOT_EXTENSION } from "./constants";
12
13
-const INPUT_EXTENSIONS = [".js", ".ts", ".jsx", ".tsx"];
13
+const INPUT_EXTENSIONS = [
14
+ ".js",
15
+ ".cjs",
16
+ ".mjs",
17
+ ".ts",
18
+ ".cts",
19
+ ".mts",
20
+ ".jsx",
21
+ ".tsx",
22
+];
23
24
export type TestFilter = {
25
debug: boolean;