Skip to content

Ordering of Intersections matters for method calls, first matching wins #11978

@bnf

Description

@bnf

Bug report

We found two coherent issues with intersections via TYPO3 nightly tests.

We have two interfaces that are intesected, were both interfaces implement the same method but with different (but compatible) signatures:

interface View {
    public function render();
}
interface TemplateAwareViewInterface {
    public function render(string $templateName = '');
}

Important! The interfaces do not extend each other (whether that's good or not is another question, but offtopic…)

These interfaces are intersected in https://github.com/TYPO3/typo3/blob/v12.4.22/typo3/sysext/core/Classes/View/FluidViewAdapter.php#L30

        protected readonly ViewInterface&TemplateAwareViewInterface $view,

And a call to $this->view->render($templateFileName) results in:

Bug 1: The error we face is (TYPO3 nightly run: https://git.typo3.org/typo3/CI/cms/-/jobs/3954004):

 ------ --------------------------------------------------------------------- 
  Line   core/Classes/View/FluidViewAdapter.php                               
 ------ --------------------------------------------------------------------- 
  47     Method TYPO3Fluid\Fluid\View\ViewInterface::render() invoked with 1  
         parameter, 0 required.                                               
         🪪  arguments.count                                                  
 ------ --------------------------------------------------------------------- 

We noticed that the error goes away if the intersection order is swapped:

        protected readonly TemplateAwareViewInterface&ViewInterface $view,

That means phpstan seems to have a first-win approach for method calls on intersections instead of "one-has-to-match".

Bug 2: We only face that error if https://github.com/phpstan/phpstan-phpunit/blob/2.0.x/rules.neon are not included.
Including https://github.com/phpstan/phpstan-phpunit/blob/2.0.x/rules.neon makes the error go away(!) because https://github.com/phpstan/phpstan-phpunit/blob/3cc855474263ad6220dfa49167cbea34ca1dd300/src/Rules/PHPUnit/AssertRuleHelper.php#L49 calls ObjectType::isSuperTypeOf(…) which has the side effect that ordering is changed so that Bug1 kicks in, as ordering becomes relevant.

I understand that this this issue goes away if Bug 1 is resolved (as ordering shouldn't matter than), but I stil wanted to mention this, as this smells like a caching issue and a undesired side effect of a is...() method, since the ordering/interpretation of intersections is changed based on a is...() method, which should not change behavior of other code.

I could reproduce the behavior by invoking \PHPStan\dumpType within the reproduction snippet.
(In this reproduction case the error then becomes active for both since View1 is ordered before View2)

Code snippet that reproduces the problem

https://phpstan.org/r/219ecfd4-97cb-4049-a139-3ace938ddc51

Expected output

PHPStan should not detect an issue since a class that implements both interfaces can safely be called with a parameter, as the class that implements both interfaces needs to be aware to adhere to both contracts, therefore a call that matches the second contract of a intersection must be considerd valid.

I initially wasn't sure whether we should swap the ordering in our code, but from what I see that ordering is not relevant, as it is really just an assertion and the validity of the call is not dependant on the ordering of the intersection.

Also see:
https://wiki.php.net/rfc/pure-intersection-types#property_types

For example A&B and B&A represent the same type

https://wiki.php.net/rfc/pure-intersection-types#reflection

For example, the type X&Y may return types in the order ["Y", "X"] instead

Did PHPStan help you today? Did it make you happy in any way?

Thanks for this awesome tool, it is of great help!

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions