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:
commit
5b82082bbd
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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());'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -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',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user