You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: Inherited property default is checked against the subclass's template binding, so constructing the subclass throws (PHPStan accepts it) #110
A subclass binds the template to a shape with a required key (@extends Command<array{name: string}>). TypePHP then rejects constructing the subclass, before any code has assigned the property:
Property App\GetAgency::$options is missing required key 'name'
PHPStan (level 9) accepts the same code. This is a divergence from PHPStan, in the spirit of #67 and #74.
Playground
Open in playground — output: construct: Property App\GetAgency::$options is missing required key 'name'
construct: Property App\GetAgency::$options is missing required key 'name'
Expected
construct: OK
The default [] is written by the base class, where TOptions is only known by its bound (array<string, mixed>), and [] satisfies that bound. Assignments made after construction (e.g. init(['name' => 'x'])) should of course still be checked against the subclass's binding.
What PHPStan does
Same two classes, PHPStan 2.3.0, level 9: no error on the default value. To show the rule set was active, a deliberate $this->count = -1 on an int<0, max> property in the same base class is reported (assign.propertyType), and nothing else. PHPStan checks a property's default value once, in the declaring class, against the declared type there (a template resolves to its bound). It does not re-check inherited defaults against each subclass's template binding.
Why it matters
This is the standard "command / options bag" pattern: the base class owns $options, each concrete command declares its option shape through @extends, and init() fills the property. With property checks on, every command in our codebase throws at new, so nothing in a module that includes the base class can run. Changing the docblock to TOptions|array{} would be honest about the pre-init() state, but it forces narrowing on every read in every command, which is exactly what PHPStan's model avoids.
Versions
Identical on v0.11.0, v0.11.1, v0.11.2, v0.11.3 and dev-main @ 4e4244e (local project, PHP 8.3, array_validation => 'full'), and in the playground.
Suggested behaviour
Check a property's declared default against the type as seen by the declaring class (template replaced by its bound, mixed when unbound), not against a subclass's binding. Equivalently: skip the default-value check when the declared type is a class template, like PHPStan.
Activity