1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-27 04:45:20 +01:00

Add support of docblock method using parent keyword (fix #7411)

This commit is contained in:
Vincent 2022-01-17 19:26:42 +01:00
parent c3745cd342
commit 09fc43a4d8
2 changed files with 111 additions and 57 deletions

View File

@ -445,37 +445,16 @@ class AtomicStaticCallAnalyzer
$class_storage->final
);
$old_data_provider = $statements_analyzer->node_data;
$statements_analyzer->node_data = clone $statements_analyzer->node_data;
$context->vars_in_scope['$tmp_mixin_var'] = $new_lhs_type;
$fake_method_call_expr = new VirtualMethodCall(
new VirtualVariable(
'tmp_mixin_var',
$stmt->class->getAttributes()
),
$stmt_name,
$stmt->getArgs(),
$stmt->getAttributes()
);
if (MethodCallAnalyzer::analyze(
return self::forwardCallToInstanceMethod(
$statements_analyzer,
$fake_method_call_expr,
$context
) === false) {
return false;
}
$fake_method_call_type = $statements_analyzer->node_data->getType($fake_method_call_expr);
$statements_analyzer->node_data = $old_data_provider;
$statements_analyzer->node_data->setType($stmt, $fake_method_call_type ?? Type::getMixed());
return true;
$stmt,
$stmt_name,
$context,
'tmp_mixin_var',
true
);
}
}
}
@ -671,6 +650,18 @@ class AtomicStaticCallAnalyzer
if ($pseudo_method_storage->return_type) {
return true;
}
} elseif ($stmt->class instanceof PhpParser\Node\Name && $stmt->class->parts[0] === 'parent'
&& !$codebase->methodExists($method_id)
&& !$statements_analyzer->isStatic()
) {
// In case of parent::xxx() call on instance method context (i.e. not static context)
// with nonexistent method, we try to forward to instance method call for resolve pseudo method.
return self::forwardCallToInstanceMethod(
$statements_analyzer,
$stmt,
$stmt_name,
$context
);
}
if (!$context->check_methods) {
@ -781,37 +772,12 @@ class AtomicStaticCallAnalyzer
}
if ($is_dynamic_this_method) {
$old_data_provider = $statements_analyzer->node_data;
$statements_analyzer->node_data = clone $statements_analyzer->node_data;
$fake_method_call_expr = new VirtualMethodCall(
new VirtualVariable(
'this',
$stmt->class->getAttributes()
),
$stmt_name,
$stmt->getArgs(),
$stmt->getAttributes()
);
if (MethodCallAnalyzer::analyze(
return self::forwardCallToInstanceMethod(
$statements_analyzer,
$fake_method_call_expr,
$stmt,
$stmt_name,
$context
) === false) {
return false;
}
$fake_method_call_type = $statements_analyzer->node_data->getType($fake_method_call_expr);
$statements_analyzer->node_data = $old_data_provider;
if ($fake_method_call_type) {
$statements_analyzer->node_data->setType($stmt, $fake_method_call_type);
}
return true;
);
}
}
@ -1046,4 +1012,59 @@ class AtomicStaticCallAnalyzer
return null;
}
/**
* Forward static call to instance call, using `VirtualMethodCall` and `MethodCallAnalyzer::analyze()`
* The resolved method return type will be set as type of the $stmt node.
*
* @param StatementsAnalyzer $statements_analyzer
* @param PhpParser\Node\Expr\StaticCall $stmt
* @param PhpParser\Node\Identifier $stmt_name
* @param Context $context
* @param string $virtual_var_name Temporary var name to use for create the fake MethodCall statement.
* @param bool $always_set_node_type If true, when the method has no declared typed, mixed will be set on node.
*
* @return bool Result of analysis. False if the call is invalid.
*
* @see MethodCallAnalyzer::analyze()
*/
private static function forwardCallToInstanceMethod(
StatementsAnalyzer $statements_analyzer,
PhpParser\Node\Expr\StaticCall $stmt,
PhpParser\Node\Identifier $stmt_name,
Context $context,
string $virtual_var_name = 'this',
bool $always_set_node_type = false
): bool {
$old_data_provider = $statements_analyzer->node_data;
$statements_analyzer->node_data = clone $statements_analyzer->node_data;
$fake_method_call_expr = new VirtualMethodCall(
new VirtualVariable($virtual_var_name, $stmt->class->getAttributes()),
$stmt_name,
$stmt->getArgs(),
$stmt->getAttributes()
);
if (MethodCallAnalyzer::analyze(
$statements_analyzer,
$fake_method_call_expr,
$context
) === false) {
return false;
}
$fake_method_call_type = $statements_analyzer->node_data->getType($fake_method_call_expr);
$statements_analyzer->node_data = $old_data_provider;
if ($fake_method_call_type) {
$statements_analyzer->node_data->setType($stmt, $fake_method_call_type);
} elseif ($always_set_node_type) {
$statements_analyzer->node_data->setType($stmt, Type::getMixed());
}
return true;
}
}

View File

@ -819,6 +819,39 @@ class MagicMethodAnnotationTest extends TestCase
consumeInt(B::bar());'
],
'callUsingParent' => [
'<?php
/**
* @method static create(array $data)
*/
class Model {
public function __call() {
/** @psalm-suppress UnsafeInstantiation */
return new static;
}
}
class BlahModel extends Model {
/**
* @param mixed $input
*/
public function create($input): BlahModel
{
return parent::create([]);
}
}
class FooModel extends Model {}
function consumeFoo(FooModel $a): void {}
function consumeBlah(BlahModel $a): void {}
$b = new FooModel();
consumeFoo($b->create([]));
$d = new BlahModel();
consumeBlah($d->create([]));'
],
];
}