Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support of template for docblock methods #7385

Merged
merged 1 commit into from Jan 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -354,7 +354,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 @@ -417,7 +418,8 @@ public static function analyze(
$all_intersection_existent_method_ids,
$intersection_method_id,
$cased_method_id,
$result
$result,
$lhs_type_part
);

return;
Expand Down
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 @@ -36,7 +40,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 @@ -100,13 +105,23 @@ public static function handleMagicMethod(

$pseudo_method_storage = $class_storage->pseudo_methods[$method_name_lc];

$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 @@ -116,14 +131,22 @@ 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,
Expand Down Expand Up @@ -222,7 +245,8 @@ 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;
Expand All @@ -242,13 +266,23 @@ public static function handleMissingOrMagicMethod(
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 @@ -260,7 +294,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 @@ -270,6 +304,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 Down
60 changes: 60 additions & 0 deletions tests/Template/ClassTemplateTest.php
Expand Up @@ -3580,6 +3580,46 @@ function bar(A $a, B $b): void {
foo($a, $b);
}'
],
'templateOnDocblockMethod' => [
'<?php
/**
* @template T
* @method T get()
* @method void set(T $value)
*/
class Container
{
public function __call(string $name, array $args) {}
}

class A {}
function foo(A $a): void {}

/** @var Container<A> $container */
$container = new Container();
$container->set(new A());
foo($container->get());
'
],
'templateOnDocblockMethodOnInterface' => [
'<?php
/**
* @template T
* @method T get()
* @method void set(T $value)
*/
interface Container
{
}

class A {}
function foo(A $a): void {}

/** @var Container<A> $container */
$container->set(new A());
foo($container->get());
'
],
];
}

Expand Down Expand Up @@ -4340,6 +4380,26 @@ function foo(Collection $c, object $d): void {
}',
'error_message' => 'InvalidArgument',
],
'invalidTemplateArgumentOnDocblockMethod' => [
'<?php
/**
* @template T
* @method void set(T $value)
*/
class Container
{
public function __call(string $name, array $args) {}
}

class A {}
class B {}

/** @var Container<A> $container */
$container = new Container();
$container->set(new B());
',
'error_message' => 'InvalidArgument',
],
];
}
}