Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix inferring template types in ClosureType #921

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Type/CallableType.php
Expand Up @@ -218,7 +218,7 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
return $receivedType->inferTemplateTypesOn($this);
}

if ($receivedType->isCallable()->no()) {
if (! $receivedType->isCallable()->yes()) {
return TemplateTypeMap::createEmpty();
}

Expand Down
2 changes: 1 addition & 1 deletion src/Type/ClosureType.php
Expand Up @@ -334,7 +334,7 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
return $receivedType->inferTemplateTypesOn($this);
}

if ($receivedType->isCallable()->no()) {
if ($receivedType->isCallable()->no() || ! $receivedType instanceof self) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, what about the same change and test in CallableType.php? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I thought that can be a valid use case? new Foo which is ObjectType, can be callable, and we should try to infer template types. I did this for ClosureType because nothing else can be Closure other than Closure 馃槃

Or did I understand this wrong?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want you to write a test and implementation for this case :)

/**
 *
 * @template TGetDefault
 * @template TKey
 *
 * @param  TKey  $key
 * @param  TGetDefault|(callable(): TGetDefault)  $default
 * @return TKey|TGetDefault
 */

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the test cases. But the same change did not work well in CallableType. Handled a bit differently and it works now.

Also added a test case with an invokable class, seems to work as expected 馃憤馃徑

return TemplateTypeMap::createEmpty();
}

Expand Down
72 changes: 72 additions & 0 deletions tests/PHPStan/Analyser/data/generic-unions.php
Expand Up @@ -61,3 +61,75 @@ public function foo(
}

}

class InvokableClass
{
public function __invoke(): string
{
return 'foo';
}
}

/**
*
* @template TGetDefault
* @template TKey
*
* @param TKey $key
* @param TGetDefault|(\Closure(): TGetDefault) $default
* @return TKey|TGetDefault
*/
function getWithDefault($key, $default = null)
{
if(rand(0,10) > 5) {
return $key;
}

if (is_callable($default)) {
return $default();
}

return $default;
}

/**
*
* @template TGetDefault
* @template TKey
*
* @param TKey $key
* @param TGetDefault|(callable(): TGetDefault) $default
* @return TKey|TGetDefault
*/
function getWithDefaultCallable($key, $default = null)
{
if(rand(0,10) > 5) {
return $key;
}

if (is_callable($default)) {
return $default();
}

return $default;
}

assertType('int|null', getWithDefault(3));
assertType('int|null', getWithDefaultCallable(3));
assertType('int|string', getWithDefault(3, 'foo'));
assertType('int|string', getWithDefaultCallable(3, 'foo'));
assertType('int|string', getWithDefault(3, function () {
return 'foo';
}));
assertType('int|string', getWithDefaultCallable(3, function () {
return 'foo';
}));
assertType('GenericUnions\Foo|int', getWithDefault(3, function () {
return new Foo;
}));
assertType('GenericUnions\Foo|int', getWithDefaultCallable(3, function () {
return new Foo;
}));
assertType('GenericUnions\Foo|int', getWithDefault(3, new Foo));
assertType('GenericUnions\Foo|int', getWithDefaultCallable(3, new Foo));
assertType('int|string', getWithDefaultCallable(3, new InvokableClass));