Skip to content

feat(closure): closure parameter type narrowing from call-site literals - #103

Open
yuan-dian wants to merge 3 commits into
swoole:masterfrom
yuan-dian:closure-type-narrowing-v2
Open

feat(closure): closure parameter type narrowing from call-site literals#103
yuan-dian wants to merge 3 commits into
swoole:masterfrom
yuan-dian:closure-type-narrowing-v2

Conversation

@yuan-dian

Copy link
Copy Markdown
Contributor

Summary

Infer closure parameter types from call-site literal arguments to generate native C++ types instead of php::Var. When all call sites pass the same literal type, the lambda signature uses the native type directly, eliminating runtime type checks.

Performance

Scenario Before After Speedup
fn($x)(3.14) float 28ms 4ms 7x
fn($x)("hello") string 140ms 22ms 6x
fn($x)(true) bool 10ms 3ms 3x
fn($x)(42) int 6ms 3ms 2x
fn($x)($i) variable 35ms 35ms no change
fn($x)([1,2,3]) array 360ms 360ms no change

Changes

Core Logic:

  • LocalClosureAnalyzer.php: Track call sites per candidate, inferParamTypes() detects int/float/string/bool/array literals, unary ops, boolean expressions, string concatenation, cast expressions, and ConstFetch
  • ClosureGenerator.php: Use inferred types in lambda signatures, skip type checks when effectiveType is native, add newClosureWithParameters for closures with type checks
  • Translator.php: Wire inferParamTypes() into candidate processing
  • FunctionContext.php: Document callSites and inferredParamTypes keys

Tests:

  • ClosureParamTypeTest.php: 22 unit tests covering type declarations, call-site literals, multi-call fallback, unary ops, boolean expressions, string concatenation, cast expressions, goto, nested functions, and class method guard
  • closure-param-type.php: Test fixture with 20+ scenarios
  • closure-param-type-class.php: Class method guard test
  • closure-param-type-inference.phpt: End-to-end integration test

How It Works

  1. Analysis phase: LocalClosureAnalyzer tracks all call sites for each closure candidate and infers parameter types from literal arguments
  2. Code generation: If all call sites pass the same type (or type declaration exists), the lambda uses native C++ type (e.g., php::Int, php::Float) instead of php::Var
  3. Fallback: Multiple call sites with different types → php::Var + runtime type check (unchanged behavior)

Scope

  • ✅ Scalar type declarations (int, float, string, bool)
  • ✅ Array type declarations
  • ✅ Call-site literal inference (all scalar types + array)
  • ✅ Unary expressions (-42, +42)
  • ✅ Boolean expressions (===, ||, instanceof)
  • ✅ String concatenation ("hello" . "world")
  • ✅ Cast expressions ((int)"42")
  • ✅ ConstFetch (true, false)
  • ✅ FuncCall (count, strlen, sizeof)
  • ❌ Class method closures (guard: stays as Zend closure)
  • ❌ Closures with goto (invalidated)
  • ❌ Closures with nested functions (invalidated)

Safety

  • No behavior change for existing code
  • Multi-call closures with different types fall back to php::Var
  • Class method closures remain as Zend closures
  • Closures with goto/labels are invalidated
  • 60 closure tests pass, full test suite 1987 tests (8 pre-existing failures unchanged)

…terals

Infer closure parameter types from call-site literal arguments to generate
native C++ types instead of php::Var. When all call sites pass the same
literal type, the lambda signature uses the native type directly.

Changes:
- LocalClosureAnalyzer: track call sites per candidate, inferParamTypes()
  detects int/float/string/bool/array literals, unary ops, boolean
  expressions, string concatenation, cast expressions, and ConstFetch
- ClosureGenerator: use inferred types in lambda signatures, skip type
  checks when effectiveType is native, add newClosureWithParameters for
  closures with type checks
- Translator: wire inferParamTypes() into candidate processing
- FunctionContext: document callSites and inferredParamTypes keys

Performance (5M iterations):
- fn()(42): 10ms -> 5ms (2x faster)
- fn()(3.14): 85ms -> 8ms (10x faster)
- fn()(true): 14ms -> 4ms (3.5x faster)
- fn(int $x)(42): 10ms (no regression, approach A: no declaration narrowing)

Tests: 22 new unit tests, 60 total closure tests pass
- Remove stray main() call outside function body (TypePHP prohibits loose code)
- Change fn($x) => $x to fn($x) => count($x) to match expected int(2) output
run-tests.php requires the closing PHP tag to properly extract
the --FILE-- section. All other PHPT tests in the project have it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant