optimizePropsMethodCalls

File

src/Optimization/OptimizePropsMethodCalls.ts

Purpose

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.

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.

Input Invariants

Output Guarantees

Algorithm

export function optimizePropsMethodCalls(fn: HIRFunction): void {
  for (const [, block] of fn.body.blocks) {
    for (let i = 0; i < block.instructions.length; i++) {
      const instr = block.instructions[i]!;

      if (
        instr.value.kind === 'MethodCall' &&
        isPropsType(instr.value.receiver.identifier)
      ) {
        // Transform: props.onClick(arg)
        // To: const t0 = props.onClick; t0(arg)
        instr.value = {
          kind: 'CallExpression',
          callee: instr.value.property,  // The method becomes the callee
          args: instr.value.args,
          loc: instr.value.loc,
        };
      }
    }
  }
}

function isPropsType(identifier: Identifier): boolean {
  return (
    identifier.type.kind === 'Object' &&
    identifier.type.shapeId === BuiltInPropsId
  );
}

Edge Cases

Non-Props Method Calls

Method calls on non-props objects are left unchanged:

// Unchanged - array.map is not on props
array.map(x => x * 2)

// Unchanged - obj is not props
obj.method()

Props Type Detection

The pass uses type information to identify props:

function Component(props) {
  // props has type TObject<BuiltInProps>
  props.onClick();  // Transformed
}

function Regular(obj) {
  // obj has unknown type
  obj.onClick();  // Not transformed
}

Nested Props Access

Only direct method calls on props are transformed:

props.onClick();       // Transformed
props.nested.onClick(); // Not transformed (receiver is props.nested, not props)

Arrow Function Callbacks

Works with any method on props:

props.onChange(value);   // Transformed
props.onSubmit(data);    // Transformed
props.validate(input);   // Transformed

TODOs

None in the source file.

Example

Fixture: Using props method

Input:

function Component(props) {
  return <button onClick={() => props.onClick()} />;
}

Before OptimizePropsMethodCalls:

[1] $5 = Function @context[props$1] ...
    <<anonymous>>():
      [1] $2 = LoadLocal props$1
      [2] $3 = PropertyLoad $2.onClick
      [3] $4 = MethodCall $2.$3()  // Method call on props
      [4] Return Void

After OptimizePropsMethodCalls:

[1] $5 = Function @context[props$1] ...
    <<anonymous>>():
      [1] $2 = LoadLocal props$1
      [2] $3 = PropertyLoad $2.onClick
      [3] $4 = Call $3()  // Now a regular call
      [4] Return Void

Key observations: