
You've seen this a thousand times:
Parameter #1 $amount of method format() expects float, float|null given.
You know the final type. You don't know how it got there — which param, which assignment, which missing narrow let the null slip in. So you scroll up, squint, guess, repeat.
phpstan-type-trace prints the whole journey instead of just the destination:
$amount · App\PriceCalculator::format (up to L25)
L16 param float|null
L20 narrow Assert::notNull($amount) => float via AssertTypeSpecifyingExtension
L25 read float
Now the bug is obvious. Param came in as float|null, a narrow tightened it on L20. If your error fires before L20, you know exactly what's missing — no guessing.
composer require --dev kayw-geek/phpstan-type-trace
./vendor/bin/phpstan-trace inspect src/Foo.php:42 myVar
The via tag is the kicker: when a third-party extension (Larastan, webmozart/assert, your own) reshaped the type, it tells you which one to blame — no more grepping the vendor tree.
It stops your AI from hallucinating types
This is the part that matters for legacy codebases. When an LLM agent chases PHPStan errors, it guesses types from pattern-matching — and in a 10-year-old codebase, those guesses are confidently wrong.
Ship it as a Claude Code skill and the agent calls the trace automatically. Fixes get grounded in real upstream type evidence, not vibes:
/plugin marketplace add kayw-geek/phpstan-type-trace
/plugin install phpstan-type-trace@kayw-geek
A versioned --json contract makes it safe for agents and CI to build on.
Now in your editor
The same engine powers a PhpStorm plugin: caret on a variable → Trace Type at Caret → the chain renders in a tool window with clickable lines, copyable types, and via attribution pills. (JetBrains' official account even liked the launch — hi 👋.)
Try it
Live examples — five real chains, 10 seconds, no install. If you've ever lost ten minutes hunting where a null came from, this gives them back. Star the repo if it helps.
Top comments (0)