1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-04 02:27:59 +01:00

Add comments to AtomicMethodCallAnalyzer and suppress ComplexMethod

This commit is contained in:
Matthew Brown 2021-01-05 19:03:50 -05:00 committed by Daniil Gentili
parent 257f67d593
commit 1d6338e13b
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7

View File

@ -36,6 +36,8 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer
{ {
/** /**
* @param Type\Atomic\TNamedObject|Type\Atomic\TTemplateParam $static_type * @param Type\Atomic\TNamedObject|Type\Atomic\TTemplateParam $static_type
*
* @psalm-suppress ComplexMethod it's really complex, but unavoidably so
*/ */
public static function analyze( public static function analyze(
StatementsAnalyzer $statements_analyzer, StatementsAnalyzer $statements_analyzer,
@ -183,6 +185,7 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer
$naive_method_id = $method_id; $naive_method_id = $method_id;
// this tells us whether or not we can stay on the happy path
$naive_method_exists = $codebase->methods->methodExists( $naive_method_exists = $codebase->methods->methodExists(
$method_id, $method_id,
$context->calling_method_id, $context->calling_method_id,
@ -199,41 +202,66 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer
$fake_method_exists = false; $fake_method_exists = false;
if (!$naive_method_exists
&& $codebase->methods->existence_provider->has($fq_class_name)
) {
$method_exists = $codebase->methods->existence_provider->doesMethodExist(
$fq_class_name,
$method_id->method_name,
$source,
null
);
if ($method_exists) {
$fake_method_exists = true;
}
}
if (!$naive_method_exists) { if (!$naive_method_exists) {
[$lhs_type_part, $class_storage, $naive_method_exists, $method_id, $fq_class_name] // if the method doesn't exist we check for any method existence providers
= self::handleMixins( if ($codebase->methods->existence_provider->has($fq_class_name)) {
$class_storage, $method_exists = $codebase->methods->existence_provider->doesMethodExist(
$lhs_type_part,
$method_name_lc,
$codebase,
$context,
$method_id,
$source,
$stmt,
$statements_analyzer,
$fq_class_name, $fq_class_name,
$lhs_var_id $method_id->method_name,
$source,
null
); );
if ($method_exists) {
$fake_method_exists = true;
}
}
$naive_method_exists = false;
// @mixin attributes are an absolute pain! Lots of complexity here,
// as they can redefine the called class, method id etc.
if ($class_storage->templatedMixins
&& $lhs_type_part instanceof Type\Atomic\TGenericObject
&& $class_storage->template_types
) {
[$lhs_type_part, $class_storage, $naive_method_exists, $method_id, $fq_class_name]
= self::handleTemplatedMixins(
$class_storage,
$lhs_type_part,
$method_name_lc,
$codebase,
$context,
$method_id,
$source,
$stmt,
$statements_analyzer,
$fq_class_name
);
} elseif ($class_storage->mixin_declaring_fqcln
&& $class_storage->namedMixins
) {
[$lhs_type_part, $class_storage, $naive_method_exists, $method_id, $fq_class_name]
= self::handleRegularMixins(
$class_storage,
$lhs_type_part,
$method_name_lc,
$codebase,
$context,
$method_id,
$source,
$stmt,
$statements_analyzer,
$fq_class_name,
$lhs_var_id
);
}
} }
$all_intersection_return_type = null; $all_intersection_return_type = null;
$all_intersection_existent_method_ids = []; $all_intersection_existent_method_ids = [];
// insersection types are also fun, they also complicate matters
if ($intersection_types) { if ($intersection_types) {
[$all_intersection_return_type, $all_intersection_existent_method_ids] [$all_intersection_return_type, $all_intersection_existent_method_ids]
= self::getIntersectionReturnType( = self::getIntersectionReturnType(
@ -314,14 +342,6 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer
} }
} }
$source_source = $statements_analyzer->getSource();
/**
* @var \Psalm\Internal\Analyzer\ClassLikeAnalyzer|null
*/
$classlike_source = $source_source->getSource();
$classlike_source_fqcln = $classlike_source ? $classlike_source->getFQCLN() : null;
$intersection_method_id = $intersection_types $intersection_method_id = $intersection_types
? '(' . $lhs_type_part . ')' . '::' . $stmt->name->name ? '(' . $lhs_type_part . ')' . '::' . $stmt->name->name
: null; : null;
@ -329,7 +349,6 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer
if ($lhs_var_id === '$this' if ($lhs_var_id === '$this'
&& $context->self && $context->self
&& $classlike_source_fqcln
&& $fq_class_name !== $context->self && $fq_class_name !== $context->self
&& $codebase->methods->methodExists( && $codebase->methods->methodExists(
new MethodIdentifier($context->self, $method_name_lc) new MethodIdentifier($context->self, $method_name_lc)
@ -340,12 +359,6 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer
$fq_class_name = $context->self; $fq_class_name = $context->self;
} }
$is_interface = false;
if ($codebase->interfaceExists($fq_class_name)) {
$is_interface = true;
}
$source_method_id = $source instanceof FunctionLikeAnalyzer $source_method_id = $source instanceof FunctionLikeAnalyzer
? $source->getId() ? $source->getId()
: null; : null;
@ -369,7 +382,7 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer
$codebase, $codebase,
$stmt, $stmt,
$method_id, $method_id,
$is_interface, $codebase->interfaceExists($fq_class_name),
$context, $context,
$codebase->config, $codebase->config,
$all_intersection_return_type, $all_intersection_return_type,
@ -623,7 +636,7 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer
* @param lowercase-string $method_name_lc * @param lowercase-string $method_name_lc
* @return array{Type\Atomic\TNamedObject, \Psalm\Storage\ClassLikeStorage, bool, MethodIdentifier, string} * @return array{Type\Atomic\TNamedObject, \Psalm\Storage\ClassLikeStorage, bool, MethodIdentifier, string}
*/ */
private static function handleMixins( private static function handleTemplatedMixins(
\Psalm\Storage\ClassLikeStorage $class_storage, \Psalm\Storage\ClassLikeStorage $class_storage,
Type\Atomic\TNamedObject $lhs_type_part, Type\Atomic\TNamedObject $lhs_type_part,
string $method_name_lc, string $method_name_lc,
@ -633,8 +646,7 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer
\Psalm\StatementsSource $source, \Psalm\StatementsSource $source,
PhpParser\Node\Expr\MethodCall $stmt, PhpParser\Node\Expr\MethodCall $stmt,
StatementsAnalyzer $statements_analyzer, StatementsAnalyzer $statements_analyzer,
string $fq_class_name, string $fq_class_name
?string $lhs_var_id
) { ) {
$naive_method_exists = false; $naive_method_exists = false;
@ -695,76 +707,102 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer
} }
} }
} }
} elseif ($class_storage->mixin_declaring_fqcln }
&& $class_storage->namedMixins
) {
foreach ($class_storage->namedMixins as $mixin) {
if (!$class_storage->mixin_declaring_fqcln) {
continue;
}
$new_method_id = new MethodIdentifier( return [
$mixin->value, $lhs_type_part,
$method_name_lc $class_storage,
$naive_method_exists,
$method_id,
$fq_class_name
];
}
/**
* @param lowercase-string $method_name_lc
* @return array{Type\Atomic\TNamedObject, \Psalm\Storage\ClassLikeStorage, bool, MethodIdentifier, string}
*/
private static function handleRegularMixins(
\Psalm\Storage\ClassLikeStorage $class_storage,
Type\Atomic\TNamedObject $lhs_type_part,
string $method_name_lc,
Codebase $codebase,
Context $context,
MethodIdentifier $method_id,
\Psalm\StatementsSource $source,
PhpParser\Node\Expr\MethodCall $stmt,
StatementsAnalyzer $statements_analyzer,
string $fq_class_name,
?string $lhs_var_id
) {
$naive_method_exists = false;
foreach ($class_storage->namedMixins as $mixin) {
if (!$class_storage->mixin_declaring_fqcln) {
continue;
}
$new_method_id = new MethodIdentifier(
$mixin->value,
$method_name_lc
);
if ($codebase->methods->methodExists(
$new_method_id,
$context->calling_method_id,
$codebase->collect_locations
? new CodeLocation($source, $stmt->name)
: null,
!$context->collect_initializations
&& !$context->collect_mutations
? $statements_analyzer
: null,
$statements_analyzer->getFilePath()
)) {
$mixin_declaring_class_storage = $codebase->classlike_storage_provider->get(
$class_storage->mixin_declaring_fqcln
); );
if ($codebase->methods->methodExists( $mixin_class_template_params = ClassTemplateParamCollector::collect(
$new_method_id, $codebase,
$context->calling_method_id, $mixin_declaring_class_storage,
$codebase->collect_locations $codebase->classlike_storage_provider->get($fq_class_name),
? new CodeLocation($source, $stmt->name) null,
: null, $lhs_type_part,
!$context->collect_initializations $lhs_var_id === '$this'
&& !$context->collect_mutations );
? $statements_analyzer
: null,
$statements_analyzer->getFilePath()
)) {
$mixin_declaring_class_storage = $codebase->classlike_storage_provider->get(
$class_storage->mixin_declaring_fqcln
);
$mixin_class_template_params = ClassTemplateParamCollector::collect( $lhs_type_part = clone $mixin;
$codebase,
$mixin_declaring_class_storage,
$codebase->classlike_storage_provider->get($fq_class_name),
null,
$lhs_type_part,
$lhs_var_id === '$this'
);
$lhs_type_part = clone $mixin; $lhs_type_part->replaceTemplateTypesWithArgTypes(
new \Psalm\Internal\Type\TemplateResult([], $mixin_class_template_params ?: []),
$codebase
);
$lhs_type_part->replaceTemplateTypesWithArgTypes( $lhs_type_expanded = \Psalm\Internal\Type\TypeExpander::expandUnion(
new \Psalm\Internal\Type\TemplateResult([], $mixin_class_template_params ?: []), $codebase,
$codebase new Type\Union([$lhs_type_part]),
); $mixin_declaring_class_storage->name,
$fq_class_name,
$class_storage->parent_class,
true,
false,
$class_storage->final
);
$lhs_type_expanded = \Psalm\Internal\Type\TypeExpander::expandUnion( $new_lhs_type_part = array_values($lhs_type_expanded->getAtomicTypes())[0];
$codebase,
new Type\Union([$lhs_type_part]),
$mixin_declaring_class_storage->name,
$fq_class_name,
$class_storage->parent_class,
true,
false,
$class_storage->final
);
$new_lhs_type_part = array_values($lhs_type_expanded->getAtomicTypes())[0]; if ($new_lhs_type_part instanceof Type\Atomic\TNamedObject) {
$lhs_type_part = $new_lhs_type_part;
if ($new_lhs_type_part instanceof Type\Atomic\TNamedObject) {
$lhs_type_part = $new_lhs_type_part;
}
$mixin_class_storage = $codebase->classlike_storage_provider->get($mixin->value);
$fq_class_name = $mixin_class_storage->name;
$mixin_class_storage->mixin_declaring_fqcln = $class_storage->mixin_declaring_fqcln;
$class_storage = $mixin_class_storage;
$naive_method_exists = true;
$method_id = $new_method_id;
} }
$mixin_class_storage = $codebase->classlike_storage_provider->get($mixin->value);
$fq_class_name = $mixin_class_storage->name;
$mixin_class_storage->mixin_declaring_fqcln = $class_storage->mixin_declaring_fqcln;
$class_storage = $mixin_class_storage;
$naive_method_exists = true;
$method_id = $new_method_id;
} }
} }