From 470885e4f1c366d769660471a60d25f64902cd87 Mon Sep 17 00:00:00 2001 From: someniatko Date: Tue, 12 Jul 2022 13:43:53 +0300 Subject: [PATCH] #8200 - improve inferring the "final" `static` type when calling static 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. --- .../ExistingAtomicStaticCallAnalyzer.php | 34 +++++++++++ tests/Template/Issue8200Test.php | 58 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 tests/Template/Issue8200Test.php diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php index 7855200e95e..d8af9168d55 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php @@ -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; } @@ -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; + } } diff --git a/tests/Template/Issue8200Test.php b/tests/Template/Issue8200Test.php new file mode 100644 index 00000000000..a20b5fa7507 --- /dev/null +++ b/tests/Template/Issue8200Test.php @@ -0,0 +1,58 @@ +,error_levels?:string[]}> + */ + public function providerValidCodeParse(): iterable + { + return [ + 'return TemplatedClass' => [ + ' + * + * @psalm-pure + */ + public static function just($value): self + { + return new self($value); + } + } + + abstract class Test + { + final private function __construct() {} + + /** @return Maybe */ + final public static function create(): Maybe + { + return Maybe::just(new static()); + } + }', + ], + ]; + } +}