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]: Class constant wildcard in an @extends array shape is resolved in the parent's namespace, not the subclass's imports #116
A subclass passes an array shape to a generic parent through @extends. The shape uses a class-constant wildcard on a class the subclass imports (list<Loc::STATUS_ENUM_*>, with use App\Model\Loc;). When the parent's constructor/method checks the template, a valid value is rejected:
App\Cmds\GetLocs::__construct(): Argument $options (template TOptions = array{statuses: list<Loc::STATUS_ENUM_*>})['statuses'][0] must be a valid constant matching Loc::STATUS_ENUM_*, int (1) given
Loc::STATUS_ENUM_ONLINE is 1. The same shape written with the fully qualified name (\App\Model\Loc::STATUS_ENUM_*) is accepted, so the class name looks resolved against the parent's namespace (App\Core) instead of the subclass's imports. The short name in the message (Loc::, not App\Model\Loc::) points the same way.
Close to #93 (relative class name inside an array shape in a generic argument), which was fixed for class names; class-constant references in the same position seem not covered.
Playground
Open in playground — prints FQCN: ok, then the TypeError above on the imported version.
Reproduction
<?phpdeclare(strict_types=1);
namespaceApp\Core {
/** * @template TOptions of array<string, mixed> */abstractclass Cmd
{
/** @var TOptions */protectedarray$options;
/** @param TOptions $options */publicfunction__construct(array$options)
{
$this->options = $options;
}
}
}
namespaceApp\Model {
finalclass Loc
{
finalpublicconstSTATUS_ENUM_ONLINE = 1;
finalpublicconstSTATUS_ENUM_OFFLINE = 0;
}
}
namespaceApp\Cmds {
useApp\Core\Cmd;
useApp\Model\Loc;
/** * @extends Cmd<array{statuses: list<Loc::STATUS_ENUM_*>}> */finalclass GetLocs extends Cmd
{
}
/** * @extends Cmd<array{statuses: list<\App\Model\Loc::STATUS_ENUM_*>}> */finalclass GetLocsFqcn extends Cmd
{
}
}
namespace {
useApp\Cmds\GetLocs;
useApp\Cmds\GetLocsFqcn;
// Control: same shape with the fully qualified class name is accepted.newGetLocsFqcn(['statuses' => [1]]);
echo"FQCN: ok\n";
// Bug: Loc is imported in App\Cmds, but resolved against App\Core (the parent's namespace).newGetLocs(['statuses' => [1]]);
echo"imported: ok\n";
}
Versions
Reproduces on 0.11.4 and on main (fb08b46), PHP 8.2.
Locally with separate files per class (PSR-4) the result is identical.
Impact on our side
About 3,000 records in a 26-module run of a real code base: every option shape like @extends CommandGetLocationsBase<array{statuses: list<Location::STATUS_*>, ...}> or list<CommandGetProjectsBase::USER_*> where the constant's class is imported by the subclass.
Activity
rcalicdan commentedon Oct 13, 2026
@fdaviddpt Thank you for this bug report.. Should be fixed in v0.11.6