Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/4.x' into upstream-master2
Browse files Browse the repository at this point in the history
  • Loading branch information
orklah committed Jan 15, 2022
2 parents dd8df37 + 4e27889 commit 5b82082
Show file tree
Hide file tree
Showing 11 changed files with 337 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
use Psalm\Type\Atomic\TNull;
use Psalm\Type\Atomic\TTemplateParamClass;
use Psalm\Type\Atomic\TTrue;
use Psalm\Type\Reconciler;
use Psalm\Type\Union;
use UnexpectedValueException;

Expand Down Expand Up @@ -1909,7 +1910,7 @@ protected static function hasCallableCheck(PhpParser\Node\Expr\FuncCall $stmt):
}

/**
* @return 0|1|2
* @return Reconciler::RECONCILIATION_*
*/
protected static function hasClassExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): int
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,8 @@ public static function analyze(
$context,
$codebase->config,
$all_intersection_return_type,
$result
$result,
$lhs_type_part
);

if ($new_call_context) {
Expand Down Expand Up @@ -419,7 +420,8 @@ public static function analyze(
$all_intersection_existent_method_ids,
$intersection_method_id,
$cased_method_id,
$result
$result,
$lhs_type_part
);

return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
use Psalm\Config;
use Psalm\Context;
use Psalm\Internal\Analyzer\Statements\Expression\Call\ArgumentsAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Call\ClassTemplateParamCollector;
use Psalm\Internal\Analyzer\Statements\Expression\CallAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\MethodIdentifier;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\TypeExpander;
use Psalm\Node\Expr\VirtualArray;
use Psalm\Node\Expr\VirtualArrayItem;
Expand All @@ -19,6 +22,7 @@
use Psalm\Storage\ClassLikeStorage;
use Psalm\Storage\MethodStorage;
use Psalm\Type;
use Psalm\Type\Atomic;
use Psalm\Type\Atomic\TClosure;
use Psalm\Type\Union;

Expand All @@ -39,7 +43,8 @@ public static function handleMagicMethod(
Context $context,
Config $config,
?Union $all_intersection_return_type,
AtomicMethodCallAnalysisResult $result
AtomicMethodCallAnalysisResult $result,
?Atomic $lhs_type_part
): ?AtomicCallContext {
$fq_class_name = $method_id->fq_class_name;
$method_name_lc = $method_id->method_name;
Expand Down Expand Up @@ -97,19 +102,35 @@ public static function handleMagicMethod(
}
}

if (isset($class_storage->pseudo_methods[$method_name_lc])) {
$found_method_and_class_storage = self::findPseudoMethodAndClassStorages(
$codebase,
$class_storage,
$method_name_lc
);

if ($found_method_and_class_storage) {
$result->has_valid_method_call_type = true;
$result->existent_method_ids[] = $method_id->__toString();

$pseudo_method_storage = $class_storage->pseudo_methods[$method_name_lc];
[$pseudo_method_storage, $defining_class_storage] = $found_method_and_class_storage;

$found_generic_params = ClassTemplateParamCollector::collect(
$codebase,
$class_storage,
$class_storage,
$method_name_lc,
$lhs_type_part,
!$statements_analyzer->isStatic() && $method_id->fq_class_name === $context->self
);

ArgumentsAnalyzer::analyze(
$statements_analyzer,
$stmt->getArgs(),
$pseudo_method_storage->params,
(string) $method_id,
true,
$context
$context,
$found_generic_params ? new TemplateResult([], $found_generic_params) : null
);

ArgumentsAnalyzer::checkArgumentsMatch(
Expand All @@ -119,20 +140,28 @@ public static function handleMagicMethod(
$pseudo_method_storage->params,
$pseudo_method_storage,
null,
null,
$found_generic_params ? new TemplateResult([], $found_generic_params) : null,
new CodeLocation($statements_analyzer, $stmt),
$context
);

if ($pseudo_method_storage->return_type) {
$return_type_candidate = clone $pseudo_method_storage->return_type;

if ($found_generic_params) {
TemplateInferredTypeReplacer::replace(
$return_type_candidate,
new TemplateResult([], $found_generic_params),
$codebase
);
}

$return_type_candidate = TypeExpander::expandUnion(
$codebase,
$return_type_candidate,
$defining_class_storage->name,
$fq_class_name,
$fq_class_name,
$class_storage->parent_class
$defining_class_storage->parent_class
);

if ($all_intersection_return_type) {
Expand Down Expand Up @@ -223,33 +252,50 @@ public static function handleMissingOrMagicMethod(
array $all_intersection_existent_method_ids,
?string $intersection_method_id,
string $cased_method_id,
AtomicMethodCallAnalysisResult $result
AtomicMethodCallAnalysisResult $result,
?Atomic $lhs_type_part
): void {
$fq_class_name = $method_id->fq_class_name;
$method_name_lc = $method_id->method_name;

$class_storage = $codebase->classlike_storage_provider->get($fq_class_name);

$found_method_and_class_storage = self::findPseudoMethodAndClassStorages(
$codebase,
$class_storage,
$method_name_lc
);

if (($is_interface || $config->use_phpdoc_method_without_magic_or_parent)
&& isset($class_storage->pseudo_methods[$method_name_lc])
&& $found_method_and_class_storage
) {
$result->has_valid_method_call_type = true;
$result->existent_method_ids[] = $method_id->__toString();

$pseudo_method_storage = $class_storage->pseudo_methods[$method_name_lc];
[$pseudo_method_storage, $defining_class_storage] = $found_method_and_class_storage;

if ($stmt->isFirstClassCallable()) {
$result->return_type = self::createFirstClassCallableReturnType($pseudo_method_storage);
return;
}

$found_generic_params = ClassTemplateParamCollector::collect(
$codebase,
$class_storage,
$class_storage,
$method_name_lc,
$lhs_type_part,
!$statements_analyzer->isStatic() && $method_id->fq_class_name === $context->self
);

if (ArgumentsAnalyzer::analyze(
$statements_analyzer,
$stmt->getArgs(),
$pseudo_method_storage->params,
(string) $method_id,
true,
$context
$context,
$found_generic_params ? new TemplateResult([], $found_generic_params) : null
) === false) {
return;
}
Expand All @@ -261,7 +307,7 @@ public static function handleMissingOrMagicMethod(
$pseudo_method_storage->params,
$pseudo_method_storage,
null,
null,
$found_generic_params ? new TemplateResult([], $found_generic_params) : null,
new CodeLocation($statements_analyzer, $stmt->name),
$context
) === false) {
Expand All @@ -271,6 +317,14 @@ public static function handleMissingOrMagicMethod(
if ($pseudo_method_storage->return_type) {
$return_type_candidate = clone $pseudo_method_storage->return_type;

if ($found_generic_params) {
TemplateInferredTypeReplacer::replace(
$return_type_candidate,
new TemplateResult([], $found_generic_params),
$codebase
);
}

if ($all_intersection_return_type) {
$return_type_candidate = Type::intersectUnionTypes(
$all_intersection_return_type,
Expand All @@ -282,9 +336,9 @@ public static function handleMissingOrMagicMethod(
$return_type_candidate = TypeExpander::expandUnion(
$codebase,
$return_type_candidate,
$defining_class_storage->name,
$fq_class_name,
$fq_class_name,
$class_storage->parent_class,
$defining_class_storage->parent_class,
true,
false,
$class_storage->final
Expand Down Expand Up @@ -352,4 +406,41 @@ private static function createFirstClassCallableReturnType(?MethodStorage $metho

return Type::getClosure();
}

/**
* Try to find matching pseudo method over ancestors (including interfaces).
*
* Returns the pseudo method if exists, with its defining class storage.
* If the method is not declared, null is returned.
*
* @param Codebase $codebase
* @param ClassLikeStorage $static_class_storage The called class
* @param lowercase-string $method_name_lc
*
* @return array{MethodStorage, ClassLikeStorage}
*/
private static function findPseudoMethodAndClassStorages(
Codebase $codebase,
ClassLikeStorage $static_class_storage,
string $method_name_lc
): ?array {
if ($pseudo_method_storage = $static_class_storage->pseudo_methods[$method_name_lc] ?? null) {
return [$pseudo_method_storage, $static_class_storage];
}

$ancestors = $static_class_storage->class_implements + $static_class_storage->parent_classes;

foreach ($ancestors as $fq_class_name => $_) {
$class_storage = $codebase->classlikes->getStorageFor($fq_class_name);

if ($class_storage && isset($class_storage->pseudo_methods[$method_name_lc])) {
return [
$class_storage->pseudo_methods[$method_name_lc],
$class_storage
];
}
}

return null;
}
}

0 comments on commit 5b82082

Please sign in to comment.