1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Allow static method mixin to invoke instance method

This commit is contained in:
Brown 2020-06-20 18:05:35 -04:00
parent 2ccec821f8
commit 80ed1daf33
2 changed files with 56 additions and 20 deletions

View File

@ -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
);
}

View File

@ -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();
}'