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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve inferring the "final" static type when calling static methods inside a different class #8249

Merged
merged 7 commits into from Jul 14, 2022
Expand Up @@ -551,6 +551,18 @@ private static function getMethodReturnType(
) {
$static_type = $context->self;
$context_final = $codebase->classlike_storage_provider->get($context->self)->final;
} elseif ($context->calling_method_id !== null) {
$self_method_return = $codebase->methods->getMethodReturnType(
MethodIdentifier::fromMethodIdReference($context->calling_method_id),
$context->self
);
// differentiate between these cases:
// 1. "static" in return type comes from return type of the
// method CALLING the currently analyzed static method - use $context->self.
// 2. "static" comes from the CALLED static method - use $fq_class_name.
$static_type = $self_method_return !== null && self::hasStaticInType($self_method_return)
? $context->self
: $fq_class_name;
AndrolGenhald marked this conversation as resolved.
Show resolved Hide resolved
} else {
$static_type = $fq_class_name;
}
Expand Down Expand Up @@ -613,4 +625,26 @@ private static function getMethodReturnType(

return $return_type_candidate;
}

/**
* Dumb way to determine whether a type contains "static" somewhere inside.
*/
private static function hasStaticInType(Union $union_type): bool
{
foreach ($union_type->getAtomicTypes() as $atomic_type) {
if ($atomic_type instanceof Atomic\TGenericObject) {
foreach ($atomic_type->type_params as $type_param) {
if (self::hasStaticInType($type_param)) {
return true;
}
}
AndrolGenhald marked this conversation as resolved.
Show resolved Hide resolved
} elseif ($atomic_type instanceof TNamedObject) {
if ($atomic_type->value === 'static') {
return true;
}
}
}

return false;
}
}
58 changes: 58 additions & 0 deletions tests/Template/Issue8200Test.php
@@ -0,0 +1,58 @@
<?php

namespace Psalm\Tests\Template;

use Psalm\Tests\TestCase;
use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait;

class Issue8200Test extends TestCase
{
use ValidCodeAnalysisTestTrait;

/**
* @return iterable<string,array{string,assertions?:array<string,string>,error_levels?:string[]}>
*/
public function providerValidCodeParse(): iterable
{
return [
'return TemplatedClass<static>' => [
'<?php

/**
* @template-covariant A
* @psalm-immutable
*/
final class Maybe
{
/**
* @param null|A $value
*/
public function __construct(private $value = null) {}

/**
* @template B
* @param B $value
* @return Maybe<B>
*
* @psalm-pure
*/
public static function just($value): self
{
return new self($value);
}
}

abstract class Test
{
final private function __construct() {}

/** @return Maybe<static> */
final public static function create(): Maybe
{
return Maybe::just(new static());
}
}',
],
];
}
}