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
A method-level @template T is replaced by its bound when the argument's @implements binding is forwarded through an abstract class with an extra, unforwarded class template #77
/** * @template T of PBase * @template U of PBase */interface Q10BoxInterface {}
/** * @template TSelf of Q10eAbstract * @template T of PBase * @template U of PBase * @implements Q10BoxInterface<T,U> */abstractclass Q10eAbstract implements Q10BoxInterface {}
/** @extends Q10eAbstract<Q10eConcrete,PA,PB> */finalclass Q10eConcrete extends Q10eAbstract {}
finalclass Q10dGenericCreateShape
{
/** * @template T of PBase * @template U of PBase * @param Q10BoxInterface<T,U> $dto */publicfunction__construct(protectedreadonlyQ10BoxInterface$dto) {}
}
newQ10dGenericCreateShape(newQ10eConcrete());
Actual:
Q10dGenericCreateShape::__construct(): Argument $dto expects Q10BoxInterface<invariant PBase>, but Q10BoxInterface<PA> was given
The given type is resolved with a single type argument (<PA>, U dropped) and the method template falls back to its bound, checked invariantly. The trigger is the extra leading TSelf template on the abstract class: the same chain without it (variant f, @extends Q10fAbstract<PA,PB>) passes, a direct @implements Q10BoxInterface<PA,PB> on the argument's class (variant d) passes, and the chained argument against a non-templated @param Q10BoxInterface<PA,PB> (variant g) passes.
Expected: T = PA, U = PB inferred from the argument, as PHPStan resolves the @extends → @implements chain; the call is accepted.
Activity
rcalicdan commentedon Sep 18, 2026
@klunejko This issue is happening because of Positional Index Shift of Template Parameters.. I will create a dedicated PR for this..