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

Merge remote-tracking branch 'upstream/4.x' into upstream-master2

This commit is contained in:
orklah 2022-01-15 20:21:00 +01:00
commit 5b82082bbd
11 changed files with 337 additions and 69 deletions

View File

@ -52,6 +52,7 @@ use Psalm\Type\Atomic\TNamedObject;
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;
@ -1909,7 +1910,7 @@ class AssertionFinder
}
/**
* @return 0|1|2
* @return Reconciler::RECONCILIATION_*
*/
protected static function hasClassExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): int
{

View File

@ -356,7 +356,8 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer
$context,
$codebase->config,
$all_intersection_return_type,
$result
$result,
$lhs_type_part
);
if ($new_call_context) {
@ -419,7 +420,8 @@ class AtomicMethodCallAnalyzer extends CallAnalyzer
$all_intersection_existent_method_ids,
$intersection_method_id,
$cased_method_id,
$result
$result,
$lhs_type_part
);
return;

View File

@ -8,9 +8,12 @@ use Psalm\Codebase;
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;
@ -19,6 +22,7 @@ use Psalm\Node\VirtualArg;
use Psalm\Storage\ClassLikeStorage;
use Psalm\Storage\MethodStorage;
use Psalm\Type;
use Psalm\Type\Atomic;
use Psalm\Type\Atomic\TClosure;
use Psalm\Type\Union;
@ -39,7 +43,8 @@ class MissingMethodCallHandler
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;
@ -97,11 +102,26 @@ class MissingMethodCallHandler
}
}
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,
@ -109,7 +129,8 @@ class MissingMethodCallHandler
$pseudo_method_storage->params,
(string) $method_id,
true,
$context
$context,
$found_generic_params ? new TemplateResult([], $found_generic_params) : null
);
ArgumentsAnalyzer::checkArgumentsMatch(
@ -119,7 +140,7 @@ class MissingMethodCallHandler
$pseudo_method_storage->params,
$pseudo_method_storage,
null,
null,
$found_generic_params ? new TemplateResult([], $found_generic_params) : null,
new CodeLocation($statements_analyzer, $stmt),
$context
);
@ -127,12 +148,20 @@ class MissingMethodCallHandler
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) {
@ -223,33 +252,50 @@ class MissingMethodCallHandler
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;
}
@ -261,7 +307,7 @@ class MissingMethodCallHandler
$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) {
@ -271,6 +317,14 @@ class MissingMethodCallHandler
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,
@ -282,9 +336,9 @@ class MissingMethodCallHandler
$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
@ -352,4 +406,41 @@ class MissingMethodCallHandler
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;
}
}

View File

@ -5,6 +5,7 @@ namespace Psalm\Internal\Analyzer\Statements\Expression\Call\StaticMethod;
use Exception;
use PhpParser;
use Psalm\CodeLocation;
use Psalm\Codebase;
use Psalm\Context;
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
use Psalm\Internal\Analyzer\ClassLikeNameOptions;
@ -485,6 +486,12 @@ class AtomicStaticCallAnalyzer
$config = $codebase->config;
$found_method_and_class_storage = self::findPseudoMethodAndClassStorages(
$codebase,
$class_storage,
$method_name_lc
);
if (!$naive_method_exists
|| !MethodAnalyzer::isMethodVisible(
$method_id,
@ -492,7 +499,7 @@ class AtomicStaticCallAnalyzer
$statements_analyzer->getSource()
)
|| $fake_method_exists
|| (isset($class_storage->pseudo_static_methods[$method_name_lc])
|| ($found_method_and_class_storage
&& ($config->use_phpdoc_method_without_magic_or_parent || $class_storage->parent_class))
) {
$callstatic_id = new MethodIdentifier(
@ -552,8 +559,8 @@ class AtomicStaticCallAnalyzer
}
}
if (isset($class_storage->pseudo_static_methods[$method_name_lc])) {
$pseudo_method_storage = $class_storage->pseudo_static_methods[$method_name_lc];
if ($found_method_and_class_storage) {
[$pseudo_method_storage, $defining_class_storage] = $found_method_and_class_storage;
if (self::checkPseudoMethod(
$statements_analyzer,
@ -561,7 +568,7 @@ class AtomicStaticCallAnalyzer
$method_id,
$fq_class_name,
$args,
$class_storage,
$defining_class_storage,
$pseudo_method_storage,
$context
) === false
@ -644,10 +651,10 @@ class AtomicStaticCallAnalyzer
$fq_class_name,
'__callstatic'
);
} elseif (isset($class_storage->pseudo_static_methods[$method_name_lc])
} elseif ($found_method_and_class_storage
&& ($config->use_phpdoc_method_without_magic_or_parent || $class_storage->parent_class)
) {
$pseudo_method_storage = $class_storage->pseudo_static_methods[$method_name_lc];
[$pseudo_method_storage, $defining_class_storage] = $found_method_and_class_storage;
if (self::checkPseudoMethod(
$statements_analyzer,
@ -655,7 +662,7 @@ class AtomicStaticCallAnalyzer
$method_id,
$fq_class_name,
$args,
$class_storage,
$defining_class_storage,
$pseudo_method_storage,
$context
) === false
@ -836,7 +843,7 @@ class AtomicStaticCallAnalyzer
StatementsAnalyzer $statements_analyzer,
PhpParser\Node\Expr\StaticCall $stmt,
MethodIdentifier $method_id,
string $fq_class_name,
string $static_fq_class_name,
array $args,
ClassLikeStorage $class_storage,
MethodStorage $pseudo_method_storage,
@ -906,8 +913,8 @@ class AtomicStaticCallAnalyzer
$return_type_candidate = TypeExpander::expandUnion(
$statements_analyzer->getCodebase(),
$return_type_candidate,
$fq_class_name,
$fq_class_name,
$class_storage->name,
$static_fq_class_name,
$class_storage->parent_class
);
@ -1004,4 +1011,41 @@ class AtomicStaticCallAnalyzer
$statements_analyzer->getSuppressedIssues()
);
}
/**
* 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}|null
*/
private static function findPseudoMethodAndClassStorages(
Codebase $codebase,
ClassLikeStorage $static_class_storage,
string $method_name_lc
): ?array {
if ($pseudo_method_storage = $static_class_storage->pseudo_static_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_static_methods[$method_name_lc])) {
return [
$class_storage->pseudo_static_methods[$method_name_lc],
$class_storage
];
}
}
return null;
}
}

View File

@ -6,6 +6,7 @@ namespace Psalm\Internal\LanguageServer\Server;
use Amp\Promise;
use Amp\Success;
use InvalidArgumentException;
use LanguageServerProtocol\CompletionList;
use LanguageServerProtocol\Hover;
use LanguageServerProtocol\Location;
@ -369,7 +370,7 @@ class TextDocument
try {
$this->codebase->analyzer->analyzeFiles($this->project_analyzer, 1, false);
} catch (UnexpectedValueException $e) {
} catch (UnexpectedValueException | InvalidArgumentException $e) {
error_log('codeAction errored on file ' . $file_path. ', Reason: '.$e->getMessage());
return new Success(null);
}

View File

@ -70,7 +70,7 @@ class AssertionReconciler extends Reconciler
*
* @param string[] $suppressed_issues
* @param array<string, array<string, Union>> $template_type_map
* @param-out 0|1|2 $failed_reconciliation
* @param-out Reconciler::RECONCILIATION_* $failed_reconciliation
*/
public static function reconcile(
string $assertion,
@ -306,10 +306,10 @@ class AssertionReconciler extends Reconciler
* This method is called when SimpleAssertionReconciler was not enough. It receives the existing type, the assertion
* and also a new type created from the assertion string.
*
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
* @param string[] $suppressed_issues
* @param array<string, array<string, Union>> $template_type_map
* @param-out 0|1|2 $failed_reconciliation
* @param-out Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function refine(
StatementsAnalyzer $statements_analyzer,

View File

@ -37,7 +37,7 @@ class NegatedAssertionReconciler extends Reconciler
/**
* @param array<string, array<string, Union>> $template_type_map
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
public static function reconcile(
StatementsAnalyzer $statements_analyzer,

View File

@ -74,7 +74,7 @@ class SimpleAssertionReconciler extends Reconciler
{
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
public static function reconcile(
string $assertion,
@ -477,7 +477,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileIsset(
Union $existing_var_type,
@ -544,7 +544,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileNonEmptyCountable(
Union $existing_var_type,
@ -666,7 +666,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcilePositiveNumeric(
Union $existing_var_type,
@ -744,7 +744,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileHasMethod(
Codebase $codebase,
@ -849,7 +849,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileString(
Union $existing_var_type,
@ -943,7 +943,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileInt(
Union $existing_var_type,
@ -1041,7 +1041,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileBool(
Union $existing_var_type,
@ -1120,7 +1120,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileScalar(
Union $existing_var_type,
@ -1195,7 +1195,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileNumeric(
Union $existing_var_type,
@ -1287,7 +1287,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileObject(
Union $existing_var_type,
@ -1380,7 +1380,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileResource(
Union $existing_var_type,
@ -1437,7 +1437,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileCountable(
Codebase $codebase,
@ -1506,7 +1506,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileIterable(
Codebase $codebase,
@ -1566,7 +1566,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileInArray(
Codebase $codebase,
@ -1841,7 +1841,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileTraversable(
Codebase $codebase,
@ -1912,7 +1912,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileArray(
Union $existing_var_type,
@ -2007,7 +2007,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileList(
Union $existing_var_type,
@ -2110,7 +2110,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileStringArrayAccess(
Codebase $codebase,
@ -2175,7 +2175,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileIntArrayAccess(
Codebase $codebase,
@ -2235,7 +2235,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileCallable(
Codebase $codebase,
@ -2345,7 +2345,7 @@ class SimpleAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileFalsyOrEmpty(
string $assertion,
@ -2525,7 +2525,7 @@ class SimpleAssertionReconciler extends Reconciler
}
/**
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileClassConstant(
Codebase $codebase,

View File

@ -62,7 +62,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
{
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
public static function reconcile(
Codebase $codebase,
@ -403,7 +403,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileBool(
Union $existing_var_type,
@ -471,7 +471,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileNonEmptyCountable(
Union $existing_var_type,
@ -544,7 +544,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileNull(
Union $existing_var_type,
@ -610,7 +610,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileFalse(
Union $existing_var_type,
@ -676,7 +676,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileFalsyOrEmpty(
string $assertion,
@ -871,7 +871,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileScalar(
Union $existing_var_type,
@ -957,7 +957,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileObject(
Union $existing_var_type,
@ -1058,7 +1058,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileNumeric(
Union $existing_var_type,
@ -1148,7 +1148,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileInt(
Union $existing_var_type,
@ -1248,7 +1248,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileFloat(
Union $existing_var_type,
@ -1343,7 +1343,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileString(
Union $existing_var_type,
@ -1447,7 +1447,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileArray(
Union $existing_var_type,
@ -1548,7 +1548,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileResource(
Union $existing_var_type,

View File

@ -750,6 +750,75 @@ class MagicMethodAnnotationTest extends TestCase
(new Cache)->bar(new \DateTime(), new Cache());'
],
'magicMethodInheritance' => [
'<?php
/**
* @method string foo()
*/
interface I {}
/**
* @method int bar()
*/
class A implements I {}
class B extends A {
public function __call(string $method, array $args) {}
}
$b = new B();
function consumeString(string $s): void {}
function consumeInt(int $i): void {}
consumeString($b->foo());
consumeInt($b->bar());'
],
'magicMethodInheritanceOnInterface' => [
'<?php
/**
* @method string foo()
*/
interface I {}
interface I2 extends I {}
function consumeString(string $s): void {}
/** @var I2 $i */
consumeString($i->foo());'
],
'magicStaticMethodInheritance' => [
'<?php
/**
* @method static string foo()
*/
interface I {}
/**
* @method static int bar()
*/
class A implements I {}
class B extends A {
public static function __callStatic(string $name, array $arguments) {}
}
function consumeString(string $s): void {}
function consumeInt(int $i): void {}
consumeString(B::foo());
consumeInt(B::bar());'
],
'magicStaticMethodInheritanceWithoutCallStatic' => [
'<?php
/**
* @method static int bar()
*/
class A {}
class B extends A {}
function consumeInt(int $i): void {}
consumeInt(B::bar());'
],
];
}

View File

@ -3580,6 +3580,46 @@ class ClassTemplateTest extends TestCase
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());
'
],
];
}
@ -4339,6 +4379,26 @@ class ClassTemplateTest extends TestCase
}',
'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',
],
];
}
}