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
class A {}
class B {}
classA1extends A {
publicfunctionfoo() : void {}
}
classB1extends B {
publicfunctionbar() : void {}
}
/** * @template T1 of A * @template T2 of B * @param T1|T2 $type * @return T1|T2 */functionf(object$type) {
returnnew$type();
}
(f(newA1))->foo(); // should pass
(f(newB1))->bar(); // should pass
(f(newA1))->bar(); // should fail
(f(newB1))->foo(); // should fail
+---------------------------------------------------------------------+
| Line | test.php |
+---------------------------------------------------------------------+
| 21 | Call to an undefined method A1|B::foo(). |
| 21 | Unable to resolve the template type T2 in call to function f |
| 22 | Call to an undefined method A|B1::bar(). |
| 22 | Unable to resolve the template type T1 in call to function f |
| 24 | Call to an undefined method A1|B::bar(). |
| 24 | Unable to resolve the template type T2 in call to function f |
| 25 | Call to an undefined method A|B1::foo(). |
| 25 | Unable to resolve the template type T1 in call to function f |
+---------------------------------------------------------------------+
On the other hand, this is a weird example. To have a successful type resolution, we need to resolve all template types in a function call. When we're passing A1 as the argument, we don't have anything to resolve as T2. So we cannot construct the return type. On line 21, the function according to its signature always returns something as B. So it's not safe to call something that's only on A.
Unmatched template type is resolved to its bound and not to never.
I think we'd be able to construct method body with the above signature that would break in runtime without Psalm complaining about anything...
The question really is - does T1|T2 mean that the function expects a union of two types, or does it mean that I can pass in either of them. /cc @arnaud-lb
@@ @@+17: Function f() should return T1 of object but returns mixed.
23: Call to an undefined method A1::bar().
24: Call to an undefined method B1::foo().
Full report
Line
Error
17
Function f() should return T1 of object but returns mixed.
@@ @@+17: Function f() should return T1 of object but returns object.
23: Call to an undefined method A1::bar().
24: Call to an undefined method B1::foo().
Full report
Line
Error
17
Function f() should return T1 of object but returns object.
Activity
ondrejmirtes commentedon Nov 11, 2019
I confirm - this is a bug and should be easy to fix.
ondrejmirtes commentedon Nov 30, 2019
On the other hand, this is a weird example. To have a successful type resolution, we need to resolve all template types in a function call. When we're passing A1 as the argument, we don't have anything to resolve as T2. So we cannot construct the return type. On line 21, the function according to its signature always returns something as B. So it's not safe to call something that's only on A.
With a simple signature that also describes what the functon does, it behaves as expected: https://phpstan.org/r/02a16855-0d1c-42dd-aa28-8e281c48df81
muglug commentedon Nov 30, 2019
Yes, that describes a lot of the bugs people report with templating.
But here we can reasonably say that an unmatched template should be bound to the
emptytype or similar - that is, a type that disappears in any union.ondrejmirtes commentedon Dec 1, 2019
Unmatched template type is resolved to its bound and not to never.
I think we'd be able to construct method body with the above signature that would break in runtime without Psalm complaining about anything...
The question really is - does
T1|T2mean that the function expects a union of two types, or does it mean that I can pass in either of them. /cc @arnaud-lbphpstan-bot commentedon Mar 9, 2021
@ondrejmirtes PHPStan now reports different result with your code snippet:
Full report
Function f() should return T1 of object but returns mixed.Call to an undefined method A1::bar().Call to an undefined method B1::foo().phpstan-bot commentedon Sep 15, 2021
@ondrejmirtes After the latest commit in dev-master, PHPStan now reports different result with your code snippet:
Full report
Function f() should return T1 of object but returns object.Call to an undefined method A1::bar().Call to an undefined method B1::foo().ZebulanStanphill commentedon Mar 25, 2022
Looks like this is fixed now in PHPStan 1.5.0.