diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php index fc050f643..b7c87df0a 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php @@ -6,6 +6,7 @@ use Psalm\Internal\Analyzer\ClassLikeAnalyzer; use Psalm\Internal\Analyzer\MethodAnalyzer; use Psalm\Internal\Analyzer\NamespaceAnalyzer; use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer; +use Psalm\Internal\Analyzer\Statements\Expression\Fetch\InstancePropertyFetchAnalyzer; use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\CodeLocation; use Psalm\Context; @@ -465,14 +466,26 @@ class StaticCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\ : null, $statements_analyzer->getFilePath() )) { - $mixin_declaring_class_storage = $codebase->classlike_storage_provider->get( - $class_storage->mixin_declaring_fqcln - ); + $mixin_candidate_type = new Type\Union([clone $class_storage->mixin]); - $lhs_type_expanded = \Psalm\Internal\Type\TypeExpander::expandUnion( + if ($class_storage->mixin instanceof Type\Atomic\TGenericObject) { + $mixin_declaring_class_storage = $codebase->classlike_storage_provider->get( + $class_storage->mixin_declaring_fqcln + ); + + $mixin_candidate_type = InstancePropertyFetchAnalyzer::localizePropertyType( + $codebase, + new Type\Union([$lhs_type_part]), + $class_storage->mixin, + $class_storage, + $mixin_declaring_class_storage + ); + } + + $new_lhs_type = \Psalm\Internal\Type\TypeExpander::expandUnion( $codebase, - new Type\Union([$lhs_type_part]), - $mixin_declaring_class_storage->name, + $mixin_candidate_type, + $fq_class_name, $fq_class_name, $class_storage->parent_class, true, @@ -480,18 +493,37 @@ class StaticCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\ $class_storage->final ); - $new_lhs_type_part = \array_values($lhs_type_expanded->getAtomicTypes())[0]; + $old_data_provider = $statements_analyzer->node_data; - if ($new_lhs_type_part instanceof Type\Atomic\TNamedObject) { - $lhs_type_part = $new_lhs_type_part; + $statements_analyzer->node_data = clone $statements_analyzer->node_data; + + $context->vars_in_scope['$tmp_mixin_var'] = $new_lhs_type; + + $fake_method_call_expr = new PhpParser\Node\Expr\MethodCall( + new PhpParser\Node\Expr\Variable( + 'tmp_mixin_var', + $stmt->class->getAttributes() + ), + $stmt->name, + $stmt->args, + $stmt->getAttributes() + ); + + if (MethodCallAnalyzer::analyze( + $statements_analyzer, + $fake_method_call_expr, + $context + ) === false) { + return false; } - $mixin_class_storage = $codebase->classlike_storage_provider->get($class_storage->mixin->value); + $fake_method_call_type = $statements_analyzer->node_data->getType($fake_method_call_expr); - $fq_class_name = $mixin_class_storage->name; - $class_storage = $mixin_class_storage; - $naive_method_exists = true; - $method_id = $new_method_id; + $statements_analyzer->node_data = $old_data_provider; + + $statements_analyzer->node_data->setType($stmt, $fake_method_call_type ?: Type::getMixed()); + + return true; } } @@ -1211,22 +1243,18 @@ class StaticCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\ ) { $code_location = new CodeLocation($statements_analyzer->getSource(), $stmt); - $method_location = $method_storage - ? ($method_storage->signature_return_type_location ?: $method_storage->location) - : null; - if ($method_storage && $method_storage->pure) { $method_source = TaintNode::getForMethodReturn( (string) $method_id, $cased_method_id, - $method_location, + $code_location, $code_location ); } else { $method_source = TaintNode::getForMethodReturn( (string) $method_id, $cased_method_id, - $method_location + $code_location ); } diff --git a/tests/MixinAnnotationTest.php b/tests/MixinAnnotationTest.php index 95fc4af14..d0c0977ad 100644 --- a/tests/MixinAnnotationTest.php +++ b/tests/MixinAnnotationTest.php @@ -267,6 +267,10 @@ class MixinAnnotationTest extends TestCase public function __call(string $name, array $args) { return $this->obj->$name(...$args); } + + public function __callStatic(string $name, array $args) { + return (new static)->obj->$name(...$args); + } } /** @@ -279,6 +283,10 @@ class MixinAnnotationTest extends TestCase */ final class FooGrandChild extends FooChild {} + function test2() : FooGrandChild { + return FooGrandChild::type(); + } + function test() : FooGrandChild { return (new FooGrandChild)->type(); }'