Skip to content

Commit

Permalink
#8200 - improve inferring the "final" static type when calling stat…
Browse files Browse the repository at this point in the history
…ic methods inside a different class

differentiate between `static` defined in a class which CALLS a given static method, and `static` defined in the method which IS CALLED.
  • Loading branch information
someniatko committed Jul 12, 2022
1 parent b156892 commit 470885e
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 0 deletions.
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;
} 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;
}
}
} 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());
}
}',
],
];
}
}

0 comments on commit 470885e

Please sign in to comment.