Hi Rowan, > If you write a function with no native type information, but an "@return int" > docblock, PHPStan will report an error for a missing return statement at > Level 0, and for an incorrect return statement on Level 3. > > There's no relationship between the syntax needed and the types of analysis > performed.
You're right that I overstated this, PHPStan verifies many docblock annotations at low levels, not just at level 9. The claim narrows correctly for *generic* annotations specifically: `@template`, `@template-extends`, `@implements`-with-generic-args, and the parametric relationships they introduce. Those tend to live at higher levels because PHPStan has no native syntax to cross-reference against, so the checks must make assumptions about user intent that warrant a higher confidence threshold. The shift after this RFC is exactly that cross-reference: once `class Box<T : object>` is in syntax, the arity, bound, and inheritance checks are language-level violations that any tool reports at any level. Ondřej confirmed that PHPStan will promote generic violations to baseline checks once they're native. Mago already has them as baseline errors (we don't have levels). I'm not certain about Psalm, but I'd be surprised if Daniil disagreed. > As Daniil pointed out, SA tools analyse code with offline parsers, not by > loading and reflecting it; so the native enforcement of arity etc will still > need to be reimplemented in each tool. Both paths exist in every SA tool. Parsers handle the project source. Reflection handles classes for which the tool doesn't have source (e.g., built-ins, extensions). Psalm has explicit Reflection-based class registration: https://github.com/vimeo/psalm/blob/6ff4aa2b472a5a4dd16a75c807da633d2c2e9368/src/Psalm/Internal/Codebase/Reflection.php#L57 Mago and PHPStan have equivalent layers. The RFC's Reflection API serves the second path; tool parsers continue to serve the first. Both paths need updating to use native generic syntax. That work is real, and the maintainers above have committed to doing it once the RFC lands. > The RFC will act as a standardisation of what tools *should* enforce around > those things; but that could equally be done by agreeing a set of conformance > tests based on the existing docblock syntax. This has been tried. JetBrains explicitly attempted to bring the SA tool maintainers together around 2020 to produce a conformance spec for docblock generics; Brent referenced it in his email on this list. The attempt failed. The reason is structural: docblock generic syntax has no governance mechanism, no shared standard to anchor to, no enforcement path (a tool that ignores the spec faces no consequences), and the tools have already diverged on edge cases that would require breaking changes to reconcile. Tag aliases differ (`@extends` vs `@template-extends`), bound resolution differs, variance support differs. The only governance body that the ecosystem actually treats as authoritative is internals. That's the structural fact behind this RFC: not just "syntax in PHP is intrinsically better than syntax in docblocks" (which is true), but "the standardisation step that would make docblock generics consistent across tools requires authority no party in the ecosystem currently has." > I think the ideal is to somehow create a standardised syntax for the > SA-checked layer, but still keep it separate from the syntax for the > runtime-checked layer. [...] Straw man example: class Foo ~~Foo<T> extends > Bar ~~Bar<int,T> The visual-distinction argument cuts both ways too. You're arguing that `~~` makes the SA-checked layer visible to the reader. But the same argument applies to `<...>`: anything in angle brackets is the generic type layer, anything outside is the runtime-checked layer. Once the convention is established (which TypeScript, Hack, Kotlin, Scala, Swift, C++, Java, and C# have already established), the distinction is just as visible as a `~~` prefix would be, without adding a new syntactic primitive PHP doesn't have anywhere else. Cheers, Seifeddine.
