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,14 @@ private static function getMethodReturnType(
) {
$static_type = $context->self;
$context_final = $codebase->classlike_storage_provider->get($context->self)->final;
} elseif ($context->calling_method_id !== null) {
// differentiate between these cases:
// 1. "static" comes from the CALLED static method - use $fq_class_name.
// 2. "static" in return type comes from return type of the
// method CALLING the currently analyzed static method - use $context->self.
$static_type = self::hasStaticInType($return_type_candidate)
? $fq_class_name
: $context->self;
} else {
$static_type = $fq_class_name;
}
Expand Down Expand Up @@ -613,4 +621,22 @@ private static function getMethodReturnType(

return $return_type_candidate;
}

/**
* Dumb way to determine whether a type contains "static" somewhere inside.
*/
private static function hasStaticInType(Type\TypeNode $type): bool
{
if ($type instanceof TNamedObject && $type->value === 'static') {
return true;
}

foreach ($type->getChildNodes() as $child_type) {
if (self::hasStaticInType($child_type)) {
return true;
}
}

return false;
}
}
105 changes: 105 additions & 0 deletions tests/Template/ClassTemplateTest.php
Expand Up @@ -3694,6 +3694,111 @@ protected function setUp(): void
}
}',
],
'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());
}
}',
],
'return list<static> created in a static method of another class' => [
'<?php

final class Lister
{
/**
* @template B
* @param B $value
* @return list<B>
*
* @psalm-pure
*/
public static function mklist($value): array
{
return [ $value ];
}
}

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

/** @return list<static> */
final public static function create(): array
{
return Lister::mklist(new static());
}
}',
],
'use TemplatedClass<static> as an intermediate variable inside a method' => [
'<?php

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

/**
* @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() {}

final public static function create(): static
{
$maybe = Maybe::just(new static());
return $maybe->value;
}
}',
],
];
}

Expand Down