Type Inference
debt(d5/e3/b3/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list phpstan and psalm as the tools — these are specialist static analysers, not default linters. The code pattern flagged is redundant or incorrect @var annotations that conflict with what the analyser would infer, and the automated field is yes but only via these specialist tools.
Closest to 'simple parameterised fix' (e3). The quick_fix is to annotate function/method signatures and let the analyser infer inside the body — this is a small, localised change per function boundary rather than a one-liner swap, but also doesn't span multiple files. Common mistakes like over-annotating or missing boundary annotations are corrected function by function.
Closest to 'localised tax' (b3). The applies_to covers web and cli PHP contexts broadly, but the burden is mainly felt at function/method boundaries where explicit annotations are needed. The rest of the codebase benefits from inference inside bodies without ongoing cost; the tax is paid at the interface layer rather than pervasively.
Closest to 'serious trap' (t7). The misconception field explicitly states that developers confuse type inference with dynamic typing — a fundamental misunderstanding that contradicts how static typing works in other contexts. Additionally, common mistakes show developers assume wide types when inference is narrow, and assume inference works across function boundaries in PHP when it does not. These contradictions with intuitions from dynamically typed PHP or other languages make this a serious cognitive trap.
Also Known As
TL;DR
Explanation
Type inference lets developers write less verbose code while retaining type safety. The compiler analyses assignments, function return values, and usage context to infer types. Hindley-Milner is the classic algorithm (used in Haskell, ML, Rust) and can infer types globally across an entire program. PHP's type system requires explicit declarations for parameters and return types but infers types in several contexts: `$x = 5` gives `$x` an inferred int type in static analysers (PHPStan, Psalm), and `match` expressions can have their return type inferred. TypeScript uses local type inference heavily — `const x = 42` infers `number`, array literals infer their element type, and generics are inferred from call-site arguments. Understanding how inference works helps you write cleaner code and understand why a type error fires even without explicit annotations.
Common Misconception
Why It Matters
Common Mistakes
- Assuming a variable has a wide type when the inferred type is narrow — assigning `$x = []` then later using it as `array<int>` may fail static analysis if elements of wrong type are pushed.
- Over-annotating in TypeScript where inference is already precise — `const x: number = 42` is redundant noise.
- Relying on inference across function boundaries in PHP — PHPStan infers local variables but cannot infer parameter types of public methods without annotations.
- Assuming inference works across all PHP versions — static analysers vary in inference depth; PHPStan level 9 infers more aggressively than level 0.
Code Examples
// TypeScript: fighting inference with redundant annotations
const items: Array<string> = ['a', 'b', 'c']; // redundant — inferred already
const doubled: Array<number> = items.map((s: string): number => s.length); // over-annotated
// PHP: not trusting analyser inference, adding wrong doc comment
/** @var string $result */ // WRONG — actual type is int|false
$result = strpos($haystack, $needle);
// TypeScript: let inference do the work
const items = ['a', 'b', 'c']; // inferred: string[]
const lengths = items.map(s => s.length); // inferred: number[]
// PHP: annotate function signatures, trust inference inside
function getPosition(string $haystack, string $needle): int|false {
$pos = strpos($haystack, $needle); // inferred int|false by PHPStan
if ($pos === false) return false;
return $pos; // narrowed to int here
}