main
md 132 lines 3.66 KB
Rendered Raw
1 # optimizePropsMethodCalls
2
3 ## File
4 `src/Optimization/OptimizePropsMethodCalls.ts`
5
6 ## Purpose
7 This pass converts method calls on the props object to regular function calls. Method calls like `props.onClick()` are transformed to `const t0 = props.onClick; t0()`. This normalization enables better analysis and optimization by the compiler.
8
9 The transformation is important because method calls have different semantics than regular calls - the receiver (`props`) would normally be passed as `this` to the method. For React props, methods are typically just callback functions where `this` binding doesn't matter, so converting them to regular calls is safe and enables better memoization.
10
11 ## Input Invariants
12 - The function has been through type inference
13 - Props parameters are typed as `TObject<BuiltInProps>`
14
15 ## Output Guarantees
16 - All `MethodCall` instructions where the receiver has props type are converted to `CallExpression`
17 - The method property becomes the callee of the call
18 - Arguments are preserved exactly
19
20 ## Algorithm
21
22 ```typescript
23 export function optimizePropsMethodCalls(fn: HIRFunction): void {
24 for (const [, block] of fn.body.blocks) {
25 for (let i = 0; i < block.instructions.length; i++) {
26 const instr = block.instructions[i]!;
27
28 if (
29 instr.value.kind === 'MethodCall' &&
30 isPropsType(instr.value.receiver.identifier)
31 ) {
32 // Transform: props.onClick(arg)
33 // To: const t0 = props.onClick; t0(arg)
34 instr.value = {
35 kind: 'CallExpression',
36 callee: instr.value.property, // The method becomes the callee
37 args: instr.value.args,
38 loc: instr.value.loc,
39 };
40 }
41 }
42 }
43 }
44
45 function isPropsType(identifier: Identifier): boolean {
46 return (
47 identifier.type.kind === 'Object' &&
48 identifier.type.shapeId === BuiltInPropsId
49 );
50 }
51 ```
52
53 ## Edge Cases
54
55 ### Non-Props Method Calls
56 Method calls on non-props objects are left unchanged:
57 ```javascript
58 // Unchanged - array.map is not on props
59 array.map(x => x * 2)
60
61 // Unchanged - obj is not props
62 obj.method()
63 ```
64
65 ### Props Type Detection
66 The pass uses type information to identify props:
67 ```javascript
68 function Component(props) {
69 // props has type TObject<BuiltInProps>
70 props.onClick(); // Transformed
71 }
72
73 function Regular(obj) {
74 // obj has unknown type
75 obj.onClick(); // Not transformed
76 }
77 ```
78
79 ### Nested Props Access
80 Only direct method calls on props are transformed:
81 ```javascript
82 props.onClick(); // Transformed
83 props.nested.onClick(); // Not transformed (receiver is props.nested, not props)
84 ```
85
86 ### Arrow Function Callbacks
87 Works with any method on props:
88 ```javascript
89 props.onChange(value); // Transformed
90 props.onSubmit(data); // Transformed
91 props.validate(input); // Transformed
92 ```
93
94 ## TODOs
95 None in the source file.
96
97 ## Example
98
99 ### Fixture: Using props method
100
101 **Input:**
102 ```javascript
103 function Component(props) {
104 return <button onClick={() => props.onClick()} />;
105 }
106 ```
107
108 **Before OptimizePropsMethodCalls:**
109 ```
110 [1] $5 = Function @context[props$1] ...
111 <<anonymous>>():
112 [1] $2 = LoadLocal props$1
113 [2] $3 = PropertyLoad $2.onClick
114 [3] $4 = MethodCall $2.$3() // Method call on props
115 [4] Return Void
116 ```
117
118 **After OptimizePropsMethodCalls:**
119 ```
120 [1] $5 = Function @context[props$1] ...
121 <<anonymous>>():
122 [1] $2 = LoadLocal props$1
123 [2] $3 = PropertyLoad $2.onClick
124 [3] $4 = Call $3() // Now a regular call
125 [4] Return Void
126 ```
127
128 Key observations:
129 - `MethodCall $2.$3()` becomes `Call $3()`
130 - The property load (`$3 = PropertyLoad $2.onClick`) is preserved
131 - The receiver (`$2`) is no longer part of the call
132 - This enables the compiler to analyze `onClick` as a regular function