Skip to content

steins-infer: arity is judged for closures, IIFEs, first-class callables and string callees when the callee is proven (#938, #1051 S4) - #1070

Draft
zonuexe wants to merge 2 commits into
masterfrom
claude/ta-s4-dynamic-callee-arity
Draft

zonuexe wants to merge 2 commits into
masterfrom
claude/ta-s4-dynamic-callee-arity

Conversation

@zonuexe

@zonuexe zonuexe commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Slice S4 of the type/arity reproduction run (#1051). Part of #938: arity is now judged for closures, arrow functions, first-class callables and string callees, wherever the callee is proven.

What changed

  • steins-syntax: CallExpr::dynamic_callee: Option<ArgValue> holds an in-place closure, arrow fn, f(...) or string literal when the call is Callee::Dynamic. The receiver stays Dynamic, so every opaque-callee reader still treats the call as opaque. This is the only IR change.
  • arity.rs: a rule table with one row per callee shape, plus check_closure_scope_arity and string_callee_ref. A string callee resolves as fully qualified, never through the calling namespace, as PHP does.
  • descent.rs / stmt_calls.rs: handle_var_call runs arity in the plain pass for a closure scope, an FCC target, and a string whose Known stratum is Verified. check_dynamic_callee_arity covers the Callee::Dynamic arm, which is IIFE and f(...)(1).

Rule table (all rows run on PHP 8.5.11)

callee verdict
a variable holding a closure / arrow fn, an IIFE, a variable holding an FCC, an immediately called FCC, a string literal (in a variable or direct 'add'(1)) call.too-few-arguments, as for a direct call
array callable, __invoke object, OneOf of closures, a by-ref captured closure that is rewritten, Closure::fromCallable, Closure::bind, builtin and 'K::m' strings, a reassigned variable declined
a bare 'add' inside namespace App silent: PHP raises Error (undefined function), not ArgumentCountError
FCC creation itself never judged

Too-many is never judged: PHP runs a user callable with extra arguments cleanly, so D4 is unchanged.

Tests

  • dynamic_callee_arity.rs has 16 tests, one per table row.
  • arity::silent_closure_var_call, which pinned $fn = 'f'; $fn(1) as silent, now expects the finding. It is a true positive.

Measurement (ten public packages)

  • Strict with vendor diagnostics, default, and --no-php: all 30 outputs are byte-identical.
  • composer perf is unchanged.

Not measured

  • The private half of the local fp-gate. The orchestrator runs it before merge.

Review outcome

Adversarial review: approve. No false positive, no lost check, no perf change.

  • About 110 PHP snippets, each run on head, base and PHP 8.5. Every head finding matches an ArgumentCountError in PHP.
  • Silent, as PHP requires:
    • rebinding through loops, goto, match, by-ref, extract, $$n, global, static, use (&$f), list, $GLOBALS, include and by-ref arguments;
    • defaults, variadics, named arguments and spread;
    • FCCs of overridden, static::, parent:: or trait methods;
    • namespace fallback, use function, and the dam;
    • bind / bindTo / call.
  • Strict with vendor diagnostics, with and without the sidecar: all 40 head/base pairs are byte-identical.
  • The string-callee and FCC rows fire only with the sidecar (Verified stratum).

…d a proven string callee get the direct-call too-few-arguments check, through a CallExpr dynamic_callee for in-place callees (#1051)
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