diff --git a/UPGRADING.md b/UPGRADING.md index b44f1d37cbc..56fa6b6d5e0 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -779,6 +779,8 @@ - [BC] Class `Psalm\Type\Atomic\TClassConstant` became final - [BC] Class `Psalm\Type\TaintKind` became final - [BC] Class `Psalm\Type\Union` became final + - [BC] Property `Psalm\Config::$universal_object_crates` changed default value + from `array{'stdClass','SimpleXMLElement','SimpleXMLIterator'}` to `null` ## Removed - [BC] Property `Psalm\Codebase::$php_major_version` was removed, use diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 1983f4837a4..95396607e4d 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + $comment_block->tags['variablesfrom'][0] @@ -17,9 +17,6 @@ $matches[0] $symbol_parts[1] - - $analysis_php_version_id - @@ -70,11 +67,6 @@ $traverser->traverse([$switch_condition])[0] - - - $catch_context->assigned_var_ids += $old_catch_assigned_var_ids - - $assertion->rule[0] @@ -308,7 +300,7 @@ - + $intersection_types[0] $parse_tree->children[0] $parse_tree->condition->children[0] @@ -316,15 +308,9 @@ array_keys($template_type_map[$array_param_name])[0] array_keys($template_type_map[$class_name])[0] array_keys($template_type_map[$fq_classlike_name])[0] - array_keys($template_type_map[$param_name])[0] array_keys($template_type_map[$template_param_name])[0] - - - MethodSignatureMustProvideReturnType - - VirtualClass diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index cb86bc2c929..83b887510ce 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -49,7 +49,6 @@ use stdClass; use function array_key_exists; -use function array_map; use function array_merge; use function array_pad; use function array_pop; @@ -164,13 +163,9 @@ class Config /** * These are special object classes that allow any and all properties to be get/set on them - * @var array + * @var array */ - protected $universal_object_crates = [ - stdClass::class, - SimpleXMLElement::class, - SimpleXMLIterator::class, - ]; + protected $universal_object_crates; /** * @var static|null @@ -619,6 +614,11 @@ protected function __construct() { self::$instance = $this; $this->eventDispatcher = new EventDispatcher(); + $this->universal_object_crates = [ + strtolower(stdClass::class), + strtolower(SimpleXMLElement::class), + strtolower(SimpleXMLIterator::class), + ]; } /** @@ -2227,7 +2227,7 @@ public function visitComposerAutoloadFiles(ProjectAnalyzer $project_analyzer, ?P if (file_exists($vendor_autoload_files_path)) { $this->include_collector->runAndCollect( - fn(): array => + static fn(): array => /** * @psalm-suppress UnresolvableInclude * @var string[] @@ -2246,11 +2246,7 @@ public function visitComposerAutoloadFiles(ProjectAnalyzer $project_analyzer, ?P $codebase->classlikes->forgetMissingClassLikes(); $this->include_collector->runAndCollect( - function (): void { - // do this in a separate method so scope does not leak - /** @psalm-suppress UnresolvableInclude */ - require $this->autoloader; - } + [$this, 'requireAutoloader'] ); } @@ -2438,7 +2434,7 @@ public function addUniversalObjectCrate(string $class): void if (!class_exists($class)) { throw new UnexpectedValueException($class . ' is not a known class'); } - $this->universal_object_crates[] = $class; + $this->universal_object_crates[] = strtolower($class); } /** @@ -2446,6 +2442,13 @@ public function addUniversalObjectCrate(string $class): void */ public function getUniversalObjectCrates(): array { - return array_map('strtolower', $this->universal_object_crates); + return $this->universal_object_crates; + } + + /** @internal */ + public function requireAutoloader(): void + { + /** @psalm-suppress UnresolvableInclude */ + require $this->autoloader; } } diff --git a/src/Psalm/Config/Creator.php b/src/Psalm/Config/Creator.php index d33a07f453d..ee8a7c9d059 100644 --- a/src/Psalm/Config/Creator.php +++ b/src/Psalm/Config/Creator.php @@ -141,7 +141,7 @@ public static function getLevel(array $issues, int $counted_types): int // remove any issues where < 0.1% of expressions are affected $filtered_issues = array_filter( $issues, - fn($amount): bool => $amount > 0.1 + static fn($amount): bool => $amount > 0.1 ); if (array_sum($filtered_issues) > 0.5) { diff --git a/src/Psalm/Config/FileFilter.php b/src/Psalm/Config/FileFilter.php index c585214a927..45c59af7014 100644 --- a/src/Psalm/Config/FileFilter.php +++ b/src/Psalm/Config/FileFilter.php @@ -434,7 +434,7 @@ public static function loadFromXMLElement( private static function isRegularExpression(string $string): bool { set_error_handler( - fn(): bool => false, + static fn(): bool => false, E_WARNING ); $is_regexp = preg_match($string, '') !== false; diff --git a/src/Psalm/Config/IssueHandler.php b/src/Psalm/Config/IssueHandler.php index 26925be4f69..1fbe8b7489b 100644 --- a/src/Psalm/Config/IssueHandler.php +++ b/src/Psalm/Config/IssueHandler.php @@ -161,10 +161,10 @@ public static function getAllIssueTypes(): array { return array_filter( array_map( - fn(string $file_name): string => substr($file_name, 0, -4), + static fn(string $file_name): string => substr($file_name, 0, -4), scandir(dirname(__DIR__) . '/Issue', SCANDIR_SORT_NONE) ), - fn(string $issue_name): bool => $issue_name !== '' + static fn(string $issue_name): bool => $issue_name !== '' && $issue_name !== 'MethodIssue' && $issue_name !== 'PropertyIssue' && $issue_name !== 'ClassConstantIssue' diff --git a/src/Psalm/ErrorBaseline.php b/src/Psalm/ErrorBaseline.php index 3b91979547f..d6fdfa90627 100644 --- a/src/Psalm/ErrorBaseline.php +++ b/src/Psalm/ErrorBaseline.php @@ -48,7 +48,7 @@ public static function countTotalIssues(array $existingIssues): int /** * @param array{o:int, s:array} $existingIssue */ - fn(int $carry, array $existingIssue): int => $carry + $existingIssue['o'], + static fn(int $carry, array $existingIssue): int => $carry + $existingIssue['o'], 0 ); } @@ -196,7 +196,7 @@ private static function countIssueTypesByFile(array $issues): array * * @return array}>> */ - function (array $carry, IssueData $issue): array { + static function (array $carry, IssueData $issue): array { if ($issue->severity !== Config::REPORT_ERROR) { return $carry; } @@ -258,7 +258,7 @@ private static function writeToFile( ('php:' . PHP_VERSION), ], array_map( - fn(string $extension): string => $extension . ':' . phpversion($extension), + static fn(string $extension): string => $extension . ':' . phpversion($extension), $extensions ) ))); @@ -295,7 +295,7 @@ private static function writeToFile( /** * @param string[] $matches */ - fn(array $matches): string => ' '> $anded_types - * - * @return list> - */ - function (array $anded_types): array { - if (count($anded_types) > 1) { - $new_anded_types = []; - - foreach ($anded_types as $orred_types) { - if (count($orred_types) > 1) { - return []; - } + $negated_types = []; - $new_anded_types[] = $orred_types[0]->getNegation(); - } + foreach ($all_types as $key => $anded_types) { + if (count($anded_types) > 1) { + $new_anded_types = []; - return [$new_anded_types]; + foreach ($anded_types as $orred_types) { + if (count($orred_types) === 1) { + $new_anded_types[] = $orred_types[0]->getNegation(); + } else { + continue 2; } + } - $new_orred_types = []; + assert($new_anded_types !== []); - foreach ($anded_types[0] as $orred_type) { - $new_orred_types[] = [$orred_type->getNegation()]; - } + $negated_types[$key] = [$new_anded_types]; + continue; + } - return $new_orred_types; - }, - $all_types - ) - ); + $new_orred_types = []; + + foreach ($anded_types[0] as $orred_type) { + $new_orred_types[] = [$orred_type->getNegation()]; + } + + $negated_types[$key] = $new_orred_types; + } + + return $negated_types; } /** @@ -654,7 +651,7 @@ public static function negateFormula(array $clauses): array { $clauses = array_filter( $clauses, - fn($clause) => $clause->reconcilable + static fn(Clause $clause): bool => $clause->reconcilable ); if (!$clauses) { diff --git a/src/Psalm/Internal/Analyzer/ClassAnalyzer.php b/src/Psalm/Internal/Analyzer/ClassAnalyzer.php index 4a7e754ba66..46d051356df 100644 --- a/src/Psalm/Internal/Analyzer/ClassAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ClassAnalyzer.php @@ -894,7 +894,7 @@ public static function addContextProperties( if ($property_type_location && !$fleshed_out_type->isMixed()) { $stmt = array_filter( $stmts, - fn($stmt): bool => $stmt instanceof PhpParser\Node\Stmt\Property + static fn($stmt): bool => $stmt instanceof PhpParser\Node\Stmt\Property && isset($stmt->props[0]->name->name) && $stmt->props[0]->name->name === $property_name ); @@ -1114,7 +1114,7 @@ private function checkPropertyInitialization( $constructor_storage = $constructor_class_storage->methods['__construct']; $fake_constructor_params = array_map( - function (FunctionLikeParameter $param): PhpParser\Node\Param { + static function (FunctionLikeParameter $param): PhpParser\Node\Param { $fake_param = (new PhpParser\Builder\Param($param->name)); if ($param->signature_type) { $fake_param->setType((string)$param->signature_type); @@ -1138,7 +1138,7 @@ function (FunctionLikeParameter $param): PhpParser\Node\Param { ); $fake_constructor_stmt_args = array_map( - function (FunctionLikeParameter $param): PhpParser\Node\Arg { + static function (FunctionLikeParameter $param): PhpParser\Node\Arg { $attributes = $param->location ? [ 'startFilePos' => $param->location->raw_file_start, @@ -1943,7 +1943,7 @@ public static function analyzeClassMethodReturnType( } $overridden_method_ids = array_map( - fn($method_id) => $method_id->__toString(), + static fn($method_id): string => $method_id->__toString(), $overridden_method_ids ); diff --git a/src/Psalm/Internal/Analyzer/ClosureAnalyzer.php b/src/Psalm/Internal/Analyzer/ClosureAnalyzer.php index cbed8f768f3..4a3ce8a2fc3 100644 --- a/src/Psalm/Internal/Analyzer/ClosureAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ClosureAnalyzer.php @@ -16,7 +16,6 @@ use Psalm\Type\Atomic\TNamedObject; use Psalm\Type\Union; -use function array_map; use function in_array; use function is_string; use function preg_match; @@ -231,17 +230,15 @@ public static function analyzeClosureUses( PhpParser\Node\Expr\Closure $stmt, Context $context ): ?bool { - $param_names = array_map( - function (PhpParser\Node\Param $p): string { - if (!$p->var instanceof PhpParser\Node\Expr\Variable - || !is_string($p->var->name) - ) { - return ''; - } - return $p->var->name; - }, - $stmt->params - ); + $param_names = []; + + foreach ($stmt->params as $i => $param) { + if ($param->var instanceof PhpParser\Node\Expr\Variable && is_string($param->var->name)) { + $param_names[$i] = $param->var->name; + } else { + $param_names[$i] = ''; + } + } foreach ($stmt->uses as $use) { if (!is_string($use->var->name)) { diff --git a/src/Psalm/Internal/Analyzer/MethodComparator.php b/src/Psalm/Internal/Analyzer/MethodComparator.php index 5403a7ec7c6..58476245364 100644 --- a/src/Psalm/Internal/Analyzer/MethodComparator.php +++ b/src/Psalm/Internal/Analyzer/MethodComparator.php @@ -127,9 +127,7 @@ public static function compare( && !$implementer_method_storage->signature_return_type && !array_filter( $implementer_method_storage->attributes, - function (AttributeStorage $s) { - return $s->fq_class_name === 'ReturnTypeWillChange'; - } + static fn (AttributeStorage $s): bool => $s->fq_class_name === 'ReturnTypeWillChange' ) ) { IssueBuffer::maybeAdd( diff --git a/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php b/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php index 740735dc44a..105b32587e2 100644 --- a/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php @@ -522,7 +522,7 @@ public function server(?string $address = '127.0.0.1:12345', bool $socket_server $reader = new ProtocolStreamReader($socket); $reader->on( 'close', - function (): void { + static function (): void { fwrite(STDOUT, "Connection closed\n"); } ); @@ -953,7 +953,7 @@ public function migrateCode(): void foreach ($migration_manipulations as $file_path => $file_manipulations) { usort( $file_manipulations, - function (FileManipulation $a, FileManipulation $b): int { + static function (FileManipulation $a, FileManipulation $b): int { if ($a->start === $b->start) { if ($b->end === $a->end) { return $b->insertion_text > $a->insertion_text ? 1 : -1; @@ -1517,7 +1517,7 @@ public static function getSupportedIssuesToFix(): array { return array_map( /** @param class-string $issue_class */ - function (string $issue_class): string { + static function (string $issue_class): string { $parts = explode('\\', $issue_class); return end($parts); }, diff --git a/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php b/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php index b5f68770bc8..2661cc14f69 100644 --- a/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php @@ -124,7 +124,7 @@ public static function getControlActions( $all_leave = !array_filter( $if_statement_actions, - fn($action) => $action === self::ACTION_NONE + static fn(string $action): bool => $action === self::ACTION_NONE ); $else_statement_actions = $stmt->else @@ -139,7 +139,7 @@ public static function getControlActions( && $else_statement_actions && !array_filter( $else_statement_actions, - fn($action) => $action === self::ACTION_NONE + static fn(string $action): bool => $action === self::ACTION_NONE ); $all_elseif_actions = []; @@ -156,7 +156,7 @@ public static function getControlActions( $all_leave = $all_leave && !array_filter( $elseif_control_actions, - fn($action) => $action === self::ACTION_NONE + static fn(string $action): bool => $action === self::ACTION_NONE ); $all_elseif_actions = array_merge($elseif_control_actions, $all_elseif_actions); @@ -183,7 +183,7 @@ public static function getControlActions( $else_statement_actions, $all_elseif_actions ), - fn($action) => $action !== self::ACTION_NONE + static fn(string $action): bool => $action !== self::ACTION_NONE ); } @@ -243,7 +243,7 @@ public static function getControlActions( $all_case_actions = array_filter( $all_case_actions, - fn($action) => $action !== self::ACTION_NONE + static fn(string $action): bool => $action !== self::ACTION_NONE ); if ($has_default_terminator || $stmt->getAttribute('allMatched', false)) { @@ -270,7 +270,7 @@ public static function getControlActions( $control_actions = array_filter( array_merge($control_actions, $loop_actions), - fn($action) => $action !== self::ACTION_NONE + static fn(string $action): bool => $action !== self::ACTION_NONE ); if ($stmt instanceof PhpParser\Node\Stmt\While_ @@ -326,7 +326,7 @@ public static function getControlActions( $try_leaves = !array_filter( $try_statement_actions, - fn($action) => $action === self::ACTION_NONE + static fn(string $action): bool => $action === self::ACTION_NONE ); $all_catch_actions = []; @@ -345,7 +345,7 @@ public static function getControlActions( $all_leave = $all_leave && !array_filter( $catch_actions, - fn($action) => $action === self::ACTION_NONE + static fn(string $action): bool => $action === self::ACTION_NONE ); if (!$all_leave) { @@ -382,7 +382,7 @@ public static function getControlActions( return array_merge( array_filter( $control_actions, - fn($action) => $action !== self::ACTION_NONE + static fn(string $action): bool => $action !== self::ACTION_NONE ), $finally_statement_actions ); @@ -391,7 +391,7 @@ public static function getControlActions( $control_actions = array_filter( array_merge($control_actions, $try_statement_actions), - fn($action) => $action !== self::ACTION_NONE + static fn(string $action): bool => $action !== self::ACTION_NONE ); } } diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/DoAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/DoAnalyzer.php index 47226c94c06..fa463769c98 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/DoAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/DoAnalyzer.php @@ -72,7 +72,7 @@ public static function analyze( $while_clauses = array_values( array_filter( $while_clauses, - function (Clause $c) use ($mixed_var_ids): bool { + static function (Clause $c) use ($mixed_var_ids): bool { $keys = array_keys($c->possibilities); $mixed_var_ids = array_diff($mixed_var_ids, $keys); diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php index 7b950335d8e..209080eefef 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php @@ -1011,7 +1011,7 @@ public static function getKeyValueParamsForTraversableObject( : array_values( array_map( /** @param array $arr */ - fn(array $arr): Union => $arr[$iterator_atomic_type->value] ?? Type::getMixed(), + static fn(array $arr): Union => $arr[$iterator_atomic_type->value] ?? Type::getMixed(), $generic_storage->template_types ) ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/IfConditionalAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/IfConditionalAnalyzer.php index e1b8be725f1..188b094a55f 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/IfConditionalAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/IfConditionalAnalyzer.php @@ -18,12 +18,10 @@ use Psalm\Issue\TypeDoesNotContainType; use Psalm\IssueBuffer; use Psalm\Type\Reconciler; -use Psalm\Type\Union; use function array_diff_key; use function array_filter; use function array_keys; -use function array_map; use function array_merge; use function array_values; use function count; @@ -77,7 +75,7 @@ public static function analyze( $entry_clauses = array_values( array_filter( $entry_clauses, - fn(Clause $c): bool => count($c->possibilities) > 1 + static fn(Clause $c): bool => count($c->possibilities) > 1 || $c->wedge || !isset($changed_var_ids[array_keys($c->possibilities)[0]]) ) @@ -202,20 +200,16 @@ public static function analyze( $assigned_in_conditional_var_ids = $first_cond_assigned_var_ids; } - $newish_var_ids = array_map( - /** - * @param Union $_ - * - * @return true - */ - fn(Union $_): bool => true, - array_diff_key( - $if_conditional_context->vars_in_scope, - $pre_condition_vars_in_scope, - $cond_referenced_var_ids, - $assigned_in_conditional_var_ids - ) - ); + $newish_var_ids = []; + + foreach (array_diff_key( + $if_conditional_context->vars_in_scope, + $pre_condition_vars_in_scope, + $cond_referenced_var_ids, + $assigned_in_conditional_var_ids + ) as $name => $_value) { + $newish_var_ids[$name] = true; + } self::handleParadoxicalCondition($statements_analyzer, $cond, true); diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseIfAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseIfAnalyzer.php index 9cda6b597b5..8e24075719f 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseIfAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseIfAnalyzer.php @@ -27,7 +27,6 @@ use function array_filter; use function array_key_exists; use function array_keys; -use function array_map; use function array_merge; use function array_reduce; use function array_unique; @@ -70,7 +69,6 @@ public static function analyze( $elseif_context = $if_conditional_scope->if_context; $cond_referenced_var_ids = $if_conditional_scope->cond_referenced_var_ids; $assigned_in_conditional_var_ids = $if_conditional_scope->assigned_in_conditional_var_ids; - $entry_clauses = $if_conditional_scope->entry_clauses; } catch (ScopeAnalysisException $e) { return false; } @@ -94,47 +92,40 @@ public static function analyze( $codebase ); - $elseif_clauses = array_map( - /** - * @return Clause - */ - function (Clause $c) use ($mixed_var_ids, $elseif_cond_id): Clause { - $keys = array_keys($c->possibilities); + $elseif_clauses_handled = []; - $mixed_var_ids = array_diff($mixed_var_ids, $keys); + foreach ($elseif_clauses as $clause) { + $keys = array_keys($clause->possibilities); + $mixed_var_ids = array_diff($mixed_var_ids, $keys); - foreach ($keys as $key) { - foreach ($mixed_var_ids as $mixed_var_id) { - if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) { - return new Clause([], $elseif_cond_id, $elseif_cond_id, true); - } + foreach ($keys as $key) { + foreach ($mixed_var_ids as $mixed_var_id) { + if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) { + $elseif_clauses_handled[] = new Clause([], $elseif_cond_id, $elseif_cond_id, true); + break 2; } } + } - return $c; - }, - $elseif_clauses - ); + $elseif_clauses_handled[] = $clause; + } - $entry_clauses = array_map( - /** - * @return Clause - */ - function (Clause $c) use ($assigned_in_conditional_var_ids, $elseif_cond_id): Clause { - $keys = array_keys($c->possibilities); - - foreach ($keys as $key) { - foreach ($assigned_in_conditional_var_ids as $conditional_assigned_var_id => $_) { - if (preg_match('/^' . preg_quote($conditional_assigned_var_id, '/') . '(\[|-|$)/', $key)) { - return new Clause([], $elseif_cond_id, $elseif_cond_id, true); - } + $elseif_clauses = $elseif_clauses_handled; + + $entry_clauses = []; + + foreach ($if_conditional_scope->entry_clauses as $c) { + foreach ($c->possibilities as $key => $_value) { + foreach ($assigned_in_conditional_var_ids as $conditional_assigned_var_id => $_) { + if (preg_match('/^'.preg_quote($conditional_assigned_var_id, '/').'(\[|-|$)/', $key)) { + $entry_clauses[] = new Clause([], $elseif_cond_id, $elseif_cond_id, true); + break 2; } } + } - return $c; - }, - $entry_clauses - ); + $entry_clauses[] = $c; + } // this will see whether any of the clauses in set A conflict with the clauses in set B AlgebraAnalyzer::checkForParadox( @@ -153,7 +144,7 @@ function (Clause $c) use ($assigned_in_conditional_var_ids, $elseif_cond_id): Cl $elseif_context_clauses = array_values( array_filter( $elseif_context_clauses, - fn($c): bool => !in_array($c->hash, $reconciled_expression_clauses) + static fn(Clause $c): bool => !in_array($c->hash, $reconciled_expression_clauses, true) ) ); } @@ -165,7 +156,7 @@ function (Clause $c) use ($assigned_in_conditional_var_ids, $elseif_cond_id): Cl try { if (array_filter( $entry_clauses, - fn($clause): bool => (bool)$clause->possibilities + static fn(Clause $clause): bool => (bool) $clause->possibilities )) { $omit_keys = array_reduce( $entry_clauses, @@ -173,7 +164,8 @@ function (Clause $c) use ($assigned_in_conditional_var_ids, $elseif_cond_id): Cl * @param array $carry * @return array */ - fn(array $carry, Clause $clause): array => array_merge($carry, array_keys($clause->possibilities)), + static fn(array $carry, Clause $clause): array + => array_merge($carry, array_keys($clause->possibilities)), [] ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/IfAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/IfAnalyzer.php index fccf832d839..de76e3bb531 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/IfAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/IfAnalyzer.php @@ -75,7 +75,7 @@ public static function analyze( if (array_filter( $outer_context->clauses, - fn($clause): bool => (bool)$clause->possibilities + static fn(Clause $clause): bool => (bool) $clause->possibilities )) { $omit_keys = array_reduce( $outer_context->clauses, @@ -83,7 +83,8 @@ public static function analyze( * @param array $carry * @return array */ - fn(array $carry, Clause $clause): array => array_merge($carry, array_keys($clause->possibilities)), + static fn(array $carry, Clause $clause): array + => array_merge($carry, array_keys($clause->possibilities)), [] ); @@ -153,7 +154,7 @@ public static function analyze( ); $old_if_context = clone $if_context; - + $codebase = $statements_analyzer->getCodebase(); $assigned_var_ids = $if_context->assigned_var_ids; diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/IfElseAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/IfElseAnalyzer.php index ac208372ffb..91bb49a50b8 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/IfElseAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/IfElseAnalyzer.php @@ -28,7 +28,6 @@ use function array_filter; use function array_intersect_key; use function array_keys; -use function array_map; use function array_merge; use function array_unique; use function array_values; @@ -139,27 +138,25 @@ public static function analyze( $if_clauses = []; } - $if_clauses = array_map( - /** - * @return Clause - */ - function (Clause $c) use ($mixed_var_ids, $cond_object_id): Clause { - $keys = array_keys($c->possibilities); + $if_clauses_handled = []; + foreach ($if_clauses as $clause) { + $keys = array_keys($clause->possibilities); - $mixed_var_ids = array_diff($mixed_var_ids, $keys); + $mixed_var_ids = array_diff($mixed_var_ids, $keys); - foreach ($keys as $key) { - foreach ($mixed_var_ids as $mixed_var_id) { - if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) { - return new Clause([], $cond_object_id, $cond_object_id, true); - } + foreach ($keys as $key) { + foreach ($mixed_var_ids as $mixed_var_id) { + if (preg_match('/^' . preg_quote($mixed_var_id, '/') . '(\[|-)/', $key)) { + $clause = new Clause([], $cond_object_id, $cond_object_id, true); + break 2; } } + } - return $c; - }, - $if_clauses - ); + $if_clauses_handled[] = $clause; + } + + $if_clauses = $if_clauses_handled; $entry_clauses = $context->clauses; @@ -186,7 +183,7 @@ function (Clause $c) use ($mixed_var_ids, $cond_object_id): Clause { $if_context->clauses = array_values( array_filter( $if_context->clauses, - fn($c): bool => !in_array($c->hash, $reconciled_expression_clauses) + static fn(Clause $c): bool => !in_array($c->hash, $reconciled_expression_clauses) ) ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/TryAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/TryAnalyzer.php index 940329162d3..1160f615394 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/TryAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/TryAnalyzer.php @@ -265,7 +265,7 @@ public static function analyze( $catch_context->vars_in_scope[$catch_var_id] = new Union( array_map( - function (string $fq_catch_class) use ($codebase): TNamedObject { + static function (string $fq_catch_class) use ($codebase): TNamedObject { $catch_class_type = new TNamedObject($fq_catch_class); if (version_compare(PHP_VERSION, '7.0.0dev', '>=') diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php index 9ecfcabbea7..17262341b2a 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php @@ -780,12 +780,12 @@ private static function taintAssignment( $unspecialized_parent_nodes = array_filter( $parent_nodes, - fn($parent_node) => !$parent_node->specialization_key + static fn($parent_node): bool => !$parent_node->specialization_key ); $specialized_parent_nodes = array_filter( $parent_nodes, - fn($parent_node) => (bool) $parent_node->specialization_key + static fn($parent_node): bool => (bool) $parent_node->specialization_key ); $new_parent_nodes = []; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php index 68d333f1a3d..6f877ae0414 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php @@ -11,6 +11,7 @@ use Psalm\Internal\Analyzer\Statements\Block\IfElseAnalyzer; use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer; use Psalm\Internal\Analyzer\StatementsAnalyzer; +use Psalm\Internal\Clause; use Psalm\Node\Stmt\VirtualExpression; use Psalm\Node\Stmt\VirtualIf; use Psalm\Type\Reconciler; @@ -104,7 +105,7 @@ public static function analyze( $context_clauses = array_values( array_filter( $context_clauses, - fn($c): bool => !in_array($c->hash, $reconciled_expression_clauses) + static fn(Clause $c): bool => !in_array($c->hash, $reconciled_expression_clauses, true) ) ); @@ -202,7 +203,8 @@ public static function analyze( $if_body_context->reconciled_expression_clauses = array_merge( $if_body_context->reconciled_expression_clauses, array_map( - fn($c) => $c->hash, + /** @return string|int */ + static fn(Clause $c) => $c->hash, $partitioned_clauses[1] ) ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php index c4cb437a444..12720dbc19d 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php @@ -15,6 +15,7 @@ use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier; use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer; use Psalm\Internal\Analyzer\StatementsAnalyzer; +use Psalm\Internal\Clause; use Psalm\Internal\Scope\IfScope; use Psalm\Internal\Type\AssertionReconciler; use Psalm\Node\Expr\VirtualBooleanNot; @@ -26,7 +27,6 @@ use function array_diff_key; use function array_filter; -use function array_map; use function array_merge; use function array_values; use function count; @@ -173,7 +173,7 @@ public static function analyze( $negated_left_clauses = array_values( array_filter( $negated_left_clauses, - fn($c): bool => !in_array($c->hash, $reconciled_expression_clauses) + static fn(Clause $c): bool => !in_array($c->hash, $reconciled_expression_clauses) ) ); @@ -242,23 +242,18 @@ public static function analyze( if ($changed_var_ids) { $partitioned_clauses = Context::removeReconciledClauses($right_context->clauses, $changed_var_ids); $right_context->clauses = $partitioned_clauses[0]; - $right_context->reconciled_expression_clauses = array_merge( - $context->reconciled_expression_clauses, - array_map( - fn($c) => $c->hash, - $partitioned_clauses[1] - ) - ); + $right_context->reconciled_expression_clauses = $context->reconciled_expression_clauses; + + foreach ($partitioned_clauses[1] as $clause) { + $right_context->reconciled_expression_clauses[] = $clause->hash; + } $partitioned_clauses = Context::removeReconciledClauses($context->clauses, $changed_var_ids); $context->clauses = $partitioned_clauses[0]; - $context->reconciled_expression_clauses = array_merge( - $context->reconciled_expression_clauses, - array_map( - fn($c) => $c->hash, - $partitioned_clauses[1] - ) - ); + + foreach ($partitioned_clauses[1] as $clause) { + $context->reconciled_expression_clauses[] = $clause->hash; + } } $right_context->if_body_context = null; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php index 8221971da34..09c6f0b6d03 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php @@ -578,8 +578,8 @@ private static function handleClosureArg( $replace_template_result = new TemplateResult( array_map( - fn($template_map) => array_map( - fn($lower_bounds) => TemplateStandinTypeReplacer::getMostSpecificTypeFromBounds( + static fn(array $template_map): array => array_map( + static fn(array $lower_bounds): Union => TemplateStandinTypeReplacer::getMostSpecificTypeFromBounds( $lower_bounds, $codebase ), diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php index 27e262be596..a34f9e6d030 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php @@ -147,7 +147,7 @@ public static function handleAddition( $unpacked_args = array_filter( $args, - fn($arg) => $arg->unpack + static fn(PhpParser\Node\Arg $arg): bool => $arg->unpack ); if ($method_id === 'array_push' && !$unpacked_args) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php index 6914c45fa0d..40f5ab54234 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php @@ -369,7 +369,7 @@ public static function analyze( $statements_analyzer->node_data->setIfTrueAssertions( $stmt, array_map( - fn(Possibilities $assertion): Possibilities => + static fn(Possibilities $assertion): Possibilities => $assertion->getUntemplatedCopy($template_result, null, $codebase), $function_call_info->function_storage->if_true_assertions ) @@ -380,7 +380,7 @@ public static function analyze( $statements_analyzer->node_data->setIfFalseAssertions( $stmt, array_map( - fn(Possibilities $assertion): Possibilities => + static fn(Possibilities $assertion): Possibilities => $assertion->getUntemplatedCopy($template_result, null, $codebase), $function_call_info->function_storage->if_false_assertions ) @@ -965,7 +965,7 @@ private static function processAssertFunctionEffects( $context->references_in_scope, $changed_var_ids, array_map( - fn($_): bool => true, + static fn($_): bool => true, $assert_type_assertions ), $statements_analyzer, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php index 2020a8490e8..c07110f5688 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php @@ -421,7 +421,7 @@ public static function analyze( $statements_analyzer->node_data->setIfTrueAssertions( $stmt, array_map( - fn(Possibilities $assertion): Possibilities => $assertion->getUntemplatedCopy( + static fn(Possibilities $assertion): Possibilities => $assertion->getUntemplatedCopy( $template_result, $lhs_var_id, $codebase @@ -435,7 +435,7 @@ public static function analyze( $statements_analyzer->node_data->setIfFalseAssertions( $stmt, array_map( - fn(Possibilities $assertion): Possibilities => $assertion->getUntemplatedCopy( + static fn(Possibilities $assertion): Possibilities => $assertion->getUntemplatedCopy( $template_result, $lhs_var_id, $codebase diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php index 0d81a13a688..2ed17b4bc67 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php @@ -329,12 +329,12 @@ public static function taintMethodCallResult( $unspecialized_parent_nodes = array_filter( $parent_nodes, - fn($parent_node) => !$parent_node->specialization_key + static fn(DataFlowNode $parent_node): bool => !$parent_node->specialization_key ); $specialized_parent_nodes = array_filter( $parent_nodes, - fn($parent_node) => (bool) $parent_node->specialization_key + static fn(DataFlowNode $parent_node): bool => (bool) $parent_node->specialization_key ); $var_node = DataFlowNode::getForAssignment( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php index c247daa736e..086a8d255dd 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php @@ -201,10 +201,7 @@ public static function handleMagicMethod( $result->existent_method_ids[] = $method_id->__toString(); $array_values = array_map( - /** - * @return PhpParser\Node\Expr\ArrayItem - */ - fn(PhpParser\Node\Arg $arg): PhpParser\Node\Expr\ArrayItem => new VirtualArrayItem( + static fn(PhpParser\Node\Arg $arg): PhpParser\Node\Expr\ArrayItem => new VirtualArrayItem( $arg->value, null, false, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php index f1bdc971616..a755115db7f 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php @@ -228,7 +228,7 @@ public static function analyze( if (count($possible_new_class_types) > 0) { $class_type = array_reduce( $possible_new_class_types, - fn(?Union $type_1, Union $type_2): Union => Type::combineUnionTypes($type_1, $type_2, $codebase) + static fn(?Union $type_1, Union $type_2): Union => Type::combineUnionTypes($type_1, $type_2, $codebase) ); } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NamedFunctionCallHandler.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NamedFunctionCallHandler.php index 0116cbe7e0d..e0c8dd51d42 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NamedFunctionCallHandler.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NamedFunctionCallHandler.php @@ -391,7 +391,7 @@ public static function handle( foreach ($anded_assertions as $assertions) { $referenced_var_ids = array_map( - fn(array $_): bool => true, + static fn(array $_): bool => true, $assertions ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php index 507aed4125d..bf368dc072f 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php @@ -33,6 +33,7 @@ use Psalm\Issue\UnsafeGenericInstantiation; use Psalm\Issue\UnsafeInstantiation; use Psalm\IssueBuffer; +use Psalm\Storage\Possibilities; use Psalm\Type; use Psalm\Type\Atomic\TAnonymousClassInstance; use Psalm\Type\Atomic\TClassString; @@ -462,7 +463,8 @@ private static function analyzeNamedConstructor( $statements_analyzer->node_data->setIfTrueAssertions( $stmt, array_map( - fn($assertion) => $assertion->getUntemplatedCopy($template_result, null, $codebase), + static fn(Possibilities $assertion): Possibilities + => $assertion->getUntemplatedCopy($template_result, null, $codebase), $method_storage->if_true_assertions ) ); @@ -472,7 +474,8 @@ private static function analyzeNamedConstructor( $statements_analyzer->node_data->setIfFalseAssertions( $stmt, array_map( - fn($assertion) => $assertion->getUntemplatedCopy($template_result, null, $codebase), + static fn(Possibilities $assertion): Possibilities + => $assertion->getUntemplatedCopy($template_result, null, $codebase), $method_storage->if_false_assertions ) ); @@ -494,11 +497,12 @@ private static function analyzeNamedConstructor( $template_name, $storage->template_extended_params, array_map( - fn($type_map) => array_map( - fn($bounds) => TemplateStandinTypeReplacer::getMostSpecificTypeFromBounds( - $bounds, - $codebase - ), + static fn(array $type_map): array => array_map( + static fn(array $bounds): Union + => TemplateStandinTypeReplacer::getMostSpecificTypeFromBounds( + $bounds, + $codebase + ), $type_map ), $template_result->lower_bounds @@ -545,7 +549,7 @@ private static function analyzeNamedConstructor( $fq_class_name, array_values( array_map( - fn($map) => clone reset($map), + static fn($map) => clone reset($map), $storage->template_types ) ) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php index 19a5c0019ae..296af44417e 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php @@ -445,7 +445,7 @@ private static function handleNamedCall( $mixin_candidates_no_generic = array_filter( $mixin_candidates, - fn($check): bool => !($check instanceof TGenericObject) + static fn(Atomic $check): bool => !($check instanceof TGenericObject) ); // $mixin_candidates_no_generic will only be empty when there are TGenericObject entries. @@ -643,7 +643,7 @@ private static function handleNamedCall( } $array_values = array_map( - fn(PhpParser\Node\Arg $arg): PhpParser\Node\Expr\ArrayItem => new VirtualArrayItem( + static fn(PhpParser\Node\Arg $arg): PhpParser\Node\Expr\ArrayItem => new VirtualArrayItem( $arg->value, null, false, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php index 3e392a565ea..e98cbcdfe67 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php @@ -333,7 +333,7 @@ public static function analyze( $statements_analyzer->node_data->setIfTrueAssertions( $stmt, array_map( - fn(Possibilities $assertion): Possibilities => + static fn(Possibilities $assertion): Possibilities => $assertion->getUntemplatedCopy($template_result, null, $codebase), $method_storage->if_true_assertions ) @@ -344,7 +344,7 @@ public static function analyze( $statements_analyzer->node_data->setIfFalseAssertions( $stmt, array_map( - fn(Possibilities $assertion): Possibilities => + static fn(Possibilities $assertion): Possibilities => $assertion->getUntemplatedCopy($template_result, null, $codebase), $method_storage->if_false_assertions ) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php index 1aed64abd8b..85c8f132131 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php @@ -1058,7 +1058,7 @@ public static function checkTemplateResult( if (count($lower_bounds) > 1) { $bounds_with_equality = array_filter( $lower_bounds, - fn($lower_bound) => (bool)$lower_bound->equality_bound_classlike + static fn($lower_bound): bool => (bool)$lower_bound->equality_bound_classlike ); if (!$bounds_with_equality) { @@ -1067,7 +1067,7 @@ public static function checkTemplateResult( $equality_types = array_unique( array_map( - fn($bound_with_equality) => $bound_with_equality->type->getId(), + static fn($bound_with_equality) => $bound_with_equality->type->getId(), $bounds_with_equality ) ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php index cb312b06a51..1576f92f8f2 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php @@ -778,7 +778,8 @@ public static function getArrayAccessTypeGivenOffset( if ($key_values) { $used_offset = "using offset value of '" . - implode('|', array_map(fn (Atomic $atomic_type) => $atomic_type->value, $key_values)) . "'"; + implode('|', array_map(static fn(Atomic $atomic_type) + => $atomic_type->value, $key_values)) . "'"; } if ($has_valid_expected_offset && $has_valid_absolute_offset && $context->inside_isset) { @@ -1562,7 +1563,8 @@ private static function handleArrayAccessOnKeyedArray( $formatted_keys = implode( ', ', array_map( - fn($key) => is_int($key) ? $key : '\'' . $key . '\'', + /** @param int|string $key */ + static fn($key): string => is_int($key) ? "$key" : '\'' . $key . '\'', $object_like_keys ) ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php index 9e5423ae2a0..568213be301 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php @@ -23,6 +23,7 @@ use Psalm\Node\Name\VirtualFullyQualified; use Psalm\Node\VirtualArg; use Psalm\Type; +use Psalm\Type\Atomic; use Psalm\Type\Atomic\TEnumCase; use Psalm\Type\Atomic\TLiteralFloat; use Psalm\Type\Atomic\TLiteralInt; @@ -266,7 +267,7 @@ public static function analyze( if (isset($vars_in_scope_reconciled[$switch_var_id])) { $array_literal_types = array_filter( $vars_in_scope_reconciled[$switch_var_id]->getAtomicTypes(), - fn($type) => $type instanceof TLiteralInt + static fn(Atomic $type): bool => $type instanceof TLiteralInt || $type instanceof TLiteralString || $type instanceof TLiteralFloat || $type instanceof TEnumCase @@ -314,7 +315,7 @@ private static function convertCondsToConditional( } $array_items = array_map( - fn($cond): PhpParser\Node\Expr\ArrayItem => + static fn(PhpParser\Node\Expr $cond): PhpParser\Node\Expr\ArrayItem => new VirtualArrayItem($cond, null, false, $cond->getAttributes()), $conds ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php index ed6b042f998..32ad2c5ce60 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php @@ -98,10 +98,7 @@ public static function analyze( } $if_clauses = array_map( - /** - * @return Clause - */ - function (Clause $c) use ($mixed_var_ids, $cond_object_id): Clause { + static function (Clause $c) use ($mixed_var_ids, $cond_object_id): Clause { $keys = array_keys($c->possibilities); $mixed_var_ids = array_diff($mixed_var_ids, $keys); @@ -140,7 +137,7 @@ function (Clause $c) use ($mixed_var_ids, $cond_object_id): Clause { $ternary_context_clauses = array_values( array_filter( $ternary_context_clauses, - fn($c): bool => !in_array($c->hash, $reconciled_expression_clauses) + static fn(Clause $c): bool => !in_array($c->hash, $reconciled_expression_clauses) ) ); diff --git a/src/Psalm/Internal/Clause.php b/src/Psalm/Internal/Clause.php index bda2b74cd08..271dfdfe25c 100644 --- a/src/Psalm/Internal/Clause.php +++ b/src/Psalm/Internal/Clause.php @@ -11,8 +11,6 @@ use function array_diff; use function array_keys; -use function array_map; -use function array_values; use function count; use function implode; use function json_encode; @@ -141,55 +139,51 @@ public function contains(Clause $other_clause): bool */ public function __toString(): string { - $clause_strings = array_map( - /** - * @param non-empty-array $values - */ - function (string $var_id, array $values): string { - if ($var_id[0] === '*') { - $var_id = ''; - } - - $var_id_clauses = array_map( - function (Assertion $value) use ($var_id): string { - $value = (string) $value; - if ($value === 'falsy') { - return '!' . $var_id; - } + $clause_strings = []; - if ($value === '!falsy') { - return $var_id; - } + foreach ($this->possibilities as $var_id => $values) { + if ($var_id[0] === '*') { + $var_id = ''; + } - $negate = false; + $var_id_clauses = []; + foreach ($values as $value) { + $value = (string) $value; + if ($value === 'falsy') { + $var_id_clauses[] = '!'.$var_id; + continue; + } - if ($value[0] === '!') { - $negate = true; - $value = substr($value, 1); - } + if ($value === '!falsy') { + $var_id_clauses[] = $var_id; + continue; + } - if ($value[0] === '=') { - $value = substr($value, 1); - } + $negate = false; - if ($negate) { - return $var_id . ' is not ' . $value; - } + if ($value[0] === '!') { + $negate = true; + $value = substr($value, 1); + } - return $var_id . ' is ' . $value; - }, - $values - ); + if ($value[0] === '=') { + $value = substr($value, 1); + } - if (count($var_id_clauses) > 1) { - return '(' . implode(') || (', $var_id_clauses) . ')'; + if ($negate) { + $var_id_clauses[] = $var_id.' is not '.$value; + continue; } - return reset($var_id_clauses); - }, - array_keys($this->possibilities), - array_values($this->possibilities) - ); + $var_id_clauses[] = $var_id.' is '.$value; + } + + if (count($var_id_clauses) > 1) { + $clause_strings[] = '('.implode(') || (', $var_id_clauses).')'; + } else { + $clause_strings[] = reset($var_id_clauses); + } + } if (count($clause_strings) > 1) { return '(' . implode(') || (', $clause_strings) . ')'; diff --git a/src/Psalm/Internal/Cli/LanguageServer.php b/src/Psalm/Internal/Cli/LanguageServer.php index 22fd434ed87..327315ced54 100644 --- a/src/Psalm/Internal/Cli/LanguageServer.php +++ b/src/Psalm/Internal/Cli/LanguageServer.php @@ -94,7 +94,7 @@ public static function run(array $argv): void } array_map( - function (string $arg) use ($valid_long_options): void { + static function (string $arg) use ($valid_long_options): void { if (strpos($arg, '--') === 0 && $arg !== '--') { $arg_name = preg_replace('/=.*$/', '', substr($arg, 2)); @@ -224,7 +224,7 @@ function (string $arg) use ($valid_long_options): void { $first_autoloader = $include_collector->runAndCollect( // we ignore the FQN because of a hack in scoper.inc that needs full path // phpcs:ignore SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly.ReferenceViaFullyQualifiedName - fn(): ?\Composer\Autoload\ClassLoader => + static fn(): ?\Composer\Autoload\ClassLoader => CliUtils::requireAutoloaders($current_dir, isset($options['r']), $vendor_dir) ); diff --git a/src/Psalm/Internal/Cli/Psalm.php b/src/Psalm/Internal/Cli/Psalm.php index 2063355c711..122964f17a8 100644 --- a/src/Psalm/Internal/Cli/Psalm.php +++ b/src/Psalm/Internal/Cli/Psalm.php @@ -217,7 +217,7 @@ public static function run(array $argv): void $first_autoloader = $include_collector->runAndCollect( // we ignore the FQN because of a hack in scoper.inc that needs full path // phpcs:ignore SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly.ReferenceViaFullyQualifiedName - fn(): ?\Composer\Autoload\ClassLoader => + static fn(): ?\Composer\Autoload\ClassLoader => CliUtils::requireAutoloaders($current_dir, isset($options['r']), $vendor_dir) ); @@ -432,7 +432,7 @@ private static function initIsDiff(array $options): bool private static function validateCliArguments(array $args): void { array_map( - function (string $arg): void { + static function (string $arg): void { if (strpos($arg, '--') === 0 && $arg !== '--') { $arg_name = preg_replace('/=.*$/', '', substr($arg, 2)); @@ -500,7 +500,7 @@ private static function generateConfig(string $current_dir, array &$args): void $args = array_values(array_filter( $args, - fn(string $arg): bool => $arg !== '--ansi' + static fn(string $arg): bool => $arg !== '--ansi' && $arg !== '--no-ansi' && $arg !== '-i' && $arg !== '--init' @@ -1068,7 +1068,7 @@ private static function storeFlowGraph(array $options, ProjectAnalyzer $project_ if ($flow_graph !== null && $dump_taint_graph !== null) { file_put_contents($dump_taint_graph, "digraph Taints {\n\t". implode("\n\t", array_map( - fn(array $edges) => '"'.implode('" -> "', $edges).'"', + static fn(array $edges) => '"'.implode('" -> "', $edges).'"', $flow_graph->summarizeEdges() )) . "\n}\n"); diff --git a/src/Psalm/Internal/Cli/Psalter.php b/src/Psalm/Internal/Cli/Psalter.php index 7d02744e738..6b534b3b374 100644 --- a/src/Psalm/Internal/Cli/Psalter.php +++ b/src/Psalm/Internal/Cli/Psalter.php @@ -207,7 +207,7 @@ public static function run(array $argv): void $first_autoloader = $include_collector->runAndCollect( // we ignore the FQN because of a hack in scoper.inc that needs full path // phpcs:ignore SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly.ReferenceViaFullyQualifiedName - fn(): ?\Composer\Autoload\ClassLoader => + static fn(): ?\Composer\Autoload\ClassLoader => CliUtils::requireAutoloaders($current_dir, isset($options['r']), $vendor_dir) ); @@ -432,7 +432,7 @@ private static function setMemoryLimit(): void private static function validateCliArguments(array $args): void { array_map( - function (string $arg): void { + static function (string $arg): void { if (strpos($arg, '--') === 0 && $arg !== '--') { $arg_name = preg_replace('/=.*$/', '', substr($arg, 2)); @@ -497,7 +497,7 @@ private static function loadCodeowners(Providers $providers): array $codeowners_file = file_get_contents($codeowners_file_path); $codeowner_lines = array_map( - function (string $line): array { + static function (string $line): array { $line_parts = preg_split('/\s+/', $line); $file_selector = substr(array_shift($line_parts), 1); @@ -505,7 +505,7 @@ function (string $line): array { }, array_filter( explode("\n", $codeowners_file), - function (string $line): bool { + static function (string $line): bool { $line = trim($line); // currently we don’t match wildcard files or files that could appear anywhere diff --git a/src/Psalm/Internal/Cli/Refactor.php b/src/Psalm/Internal/Cli/Refactor.php index af1a59d12f8..37385bed493 100644 --- a/src/Psalm/Internal/Cli/Refactor.php +++ b/src/Psalm/Internal/Cli/Refactor.php @@ -83,7 +83,7 @@ public static function run(array $argv): void $options = getopt(implode('', $valid_short_options), $valid_long_options); array_map( - function (string $arg) use ($valid_long_options): void { + static function (string $arg) use ($valid_long_options): void { if (strpos($arg, '--') === 0 && $arg !== '--') { $arg_name = preg_replace('/=.*$/', '', substr($arg, 2)); @@ -182,7 +182,7 @@ function (string $arg) use ($valid_long_options): void { $first_autoloader = $include_collector->runAndCollect( // we ignore the FQN because of a hack in scoper.inc that needs full path // phpcs:ignore SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly.ReferenceViaFullyQualifiedName - fn(): ?\Composer\Autoload\ClassLoader => + static fn(): ?\Composer\Autoload\ClassLoader => CliUtils::requireAutoloaders($current_dir, isset($options['r']), $vendor_dir) ); diff --git a/src/Psalm/Internal/Codebase/Analyzer.php b/src/Psalm/Internal/Codebase/Analyzer.php index 7c5c1ea9dbe..8344aaff897 100644 --- a/src/Psalm/Internal/Codebase/Analyzer.php +++ b/src/Psalm/Internal/Codebase/Analyzer.php @@ -2,6 +2,7 @@ namespace Psalm\Internal\Codebase; +use Closure; use InvalidArgumentException; use PhpParser; use Psalm\CodeLocation; @@ -283,7 +284,7 @@ public function analyzeFiles( $this->files_to_analyze = array_filter( $this->files_to_analyze, - fn(string $file_path): bool => $this->file_provider->fileExists($file_path) + [$this->file_provider, 'fileExists'] ); $this->doAnalysis($project_analyzer, $pool_size); @@ -346,46 +347,9 @@ private function doAnalysis(ProjectAnalyzer $project_analyzer, int $pool_size): $codebase = $project_analyzer->getCodebase(); - $filetype_analyzers = $this->config->getFiletypeAnalyzers(); + $analysis_worker = Closure::fromCallable([$this, 'analysisWorker']); - $analysis_worker = - /** - * @return list - */ - function (int $_, string $file_path) use ($project_analyzer, $filetype_analyzers): array { - $file_analyzer = $this->getFileAnalyzer($project_analyzer, $file_path, $filetype_analyzers); - - $this->progress->debug('Analyzing ' . $file_analyzer->getFilePath() . "\n"); - - $file_analyzer->analyze(); - $file_analyzer->context = null; - $file_analyzer->clearSourceBeforeDestruction(); - unset($file_analyzer); - - return IssueBuffer::getIssuesDataForFile($file_path); - }; - - $task_done_closure = - /** - * @param array $issues - */ - function (array $issues): void { - $has_error = false; - $has_info = false; - - foreach ($issues as $issue) { - if ($issue->severity === 'error') { - $has_error = true; - break; - } - - if ($issue->severity === 'info') { - $has_info = true; - } - } - - $this->progress->taskDone($has_error ? 2 : ($has_info ? 1 : 0)); - }; + $task_done_closure = Closure::fromCallable([$this, 'taskDoneClosure']); if ($pool_size > 1 && count($this->files_to_analyze) > $pool_size) { $shuffle_count = $pool_size + 1; @@ -425,7 +389,7 @@ function (array $issues): void { $pool = new Pool( $this->config, $process_file_paths, - function (): void { + static function (): void { $project_analyzer = ProjectAnalyzer::getInstance(); $codebase = $project_analyzer->getCodebase(); @@ -446,49 +410,7 @@ function (): void { $file_reference_provider->setMethodParamUses([]); }, $analysis_worker, - /** @return WorkerData */ - function () { - $project_analyzer = ProjectAnalyzer::getInstance(); - $codebase = $project_analyzer->getCodebase(); - $analyzer = $codebase->analyzer; - $file_reference_provider = $codebase->file_reference_provider; - - $this->progress->debug('Gathering data for forked process' . "\n"); - - // @codingStandardsIgnoreStart - return [ - 'issues' => IssueBuffer::getIssuesData(), - 'fixable_issue_counts' => IssueBuffer::getFixableIssues(), - 'nonmethod_references_to_classes' => $file_reference_provider->getAllNonMethodReferencesToClasses(), - 'method_references_to_classes' => $file_reference_provider->getAllMethodReferencesToClasses(), - 'file_references_to_class_members' => $file_reference_provider->getAllFileReferencesToClassMembers(), - 'method_references_to_class_members' => $file_reference_provider->getAllMethodReferencesToClassMembers(), - 'method_dependencies' => $file_reference_provider->getAllMethodDependencies(), - 'file_references_to_class_properties' => $file_reference_provider->getAllFileReferencesToClassProperties(), - 'file_references_to_method_returns' => $file_reference_provider->getAllFileReferencesToMethodReturns(), - 'method_references_to_class_properties' => $file_reference_provider->getAllMethodReferencesToClassProperties(), - 'method_references_to_method_returns' => $file_reference_provider->getAllMethodReferencesToMethodReturns(), - 'file_references_to_missing_class_members' => $file_reference_provider->getAllFileReferencesToMissingClassMembers(), - 'method_references_to_missing_class_members' => $file_reference_provider->getAllMethodReferencesToMissingClassMembers(), - 'method_param_uses' => $file_reference_provider->getAllMethodParamUses(), - 'mixed_member_names' => $analyzer->getMixedMemberNames(), - 'file_manipulations' => FileManipulationBuffer::getAll(), - 'mixed_counts' => $analyzer->getMixedCounts(), - 'function_timings' => $analyzer->getFunctionTimings(), - 'analyzed_methods' => $analyzer->getAnalyzedMethods(), - 'file_maps' => $analyzer->getFileMaps(), - 'class_locations' => $file_reference_provider->getAllClassLocations(), - 'class_method_locations' => $file_reference_provider->getAllClassMethodLocations(), - 'class_property_locations' => $file_reference_provider->getAllClassPropertyLocations(), - 'possible_method_param_types' => $analyzer->getPossibleMethodParamTypes(), - 'taint_data' => $codebase->taint_flow_graph, - 'unused_suppressions' => $codebase->track_unused_suppressions ? IssueBuffer::getUnusedSuppressions() : [], - 'used_suppressions' => $codebase->track_unused_suppressions ? IssueBuffer::getUsedSuppressions() : [], - 'function_docblock_manipulators' => FunctionDocblockManipulator::getManipulators(), - 'mutable_classes' => $codebase->analyzer->mutable_classes, - ]; - // @codingStandardsIgnoreEnd - }, + Closure::fromCallable([$this, 'getWorkerData']), $task_done_closure ); @@ -1460,7 +1382,7 @@ public function updateFile(string $file_path, bool $dry_run): void usort( $file_manipulations, - function (FileManipulation $a, FileManipulation $b): int { + static function (FileManipulation $a, FileManipulation $b): int { if ($b->end === $a->end) { if ($a->start === $b->start) { return $b->insertion_text > $a->insertion_text ? 1 : -1; @@ -1639,4 +1561,92 @@ public function isMethodAlreadyAnalyzed(string $file_path, string $method_id, bo return isset($this->analyzed_methods[$file_path][$method_id]); } + + /** + * @param list $issues + */ + private function taskDoneClosure(array $issues): void + { + $has_error = false; + $has_info = false; + + foreach ($issues as $issue) { + if ($issue->severity === 'error') { + $has_error = true; + break; + } + + if ($issue->severity === 'info') { + $has_info = true; + } + } + + $this->progress->taskDone($has_error ? 2 : ($has_info ? 1 : 0)); + } + + /** + * @return list + */ + private function analysisWorker(int $_, string $file_path): array + { + $file_analyzer = $this->getFileAnalyzer( + ProjectAnalyzer::getInstance(), + $file_path, + $this->config->getFiletypeAnalyzers() + ); + + $this->progress->debug('Analyzing ' . $file_analyzer->getFilePath() . "\n"); + + $file_analyzer->analyze(); + $file_analyzer->context = null; + $file_analyzer->clearSourceBeforeDestruction(); + unset($file_analyzer); + + return IssueBuffer::getIssuesDataForFile($file_path); + } + + /** @return WorkerData */ + private function getWorkerData(): array + { + $project_analyzer = ProjectAnalyzer::getInstance(); + $codebase = $project_analyzer->getCodebase(); + $analyzer = $codebase->analyzer; + $file_reference_provider = $codebase->file_reference_provider; + + $this->progress->debug('Gathering data for forked process'."\n"); + + // @codingStandardsIgnoreStart + return [ + 'issues' => IssueBuffer::getIssuesData(), + 'fixable_issue_counts' => IssueBuffer::getFixableIssues(), + 'nonmethod_references_to_classes' => $file_reference_provider->getAllNonMethodReferencesToClasses(), + 'method_references_to_classes' => $file_reference_provider->getAllMethodReferencesToClasses(), + 'file_references_to_class_members' => $file_reference_provider->getAllFileReferencesToClassMembers(), + 'method_references_to_class_members' => $file_reference_provider->getAllMethodReferencesToClassMembers(), + 'method_dependencies' => $file_reference_provider->getAllMethodDependencies(), + 'file_references_to_class_properties' => $file_reference_provider->getAllFileReferencesToClassProperties(), + 'file_references_to_method_returns' => $file_reference_provider->getAllFileReferencesToMethodReturns(), + 'method_references_to_class_properties' => $file_reference_provider->getAllMethodReferencesToClassProperties(), + 'method_references_to_method_returns' => $file_reference_provider->getAllMethodReferencesToMethodReturns(), + 'file_references_to_missing_class_members' => $file_reference_provider->getAllFileReferencesToMissingClassMembers(), + 'method_references_to_missing_class_members' => $file_reference_provider->getAllMethodReferencesToMissingClassMembers(), + 'method_param_uses' => $file_reference_provider->getAllMethodParamUses(), + 'mixed_member_names' => $analyzer->getMixedMemberNames(), + 'file_manipulations' => FileManipulationBuffer::getAll(), + 'mixed_counts' => $analyzer->getMixedCounts(), + 'function_timings' => $analyzer->getFunctionTimings(), + 'analyzed_methods' => $analyzer->getAnalyzedMethods(), + 'file_maps' => $analyzer->getFileMaps(), + 'class_locations' => $file_reference_provider->getAllClassLocations(), + 'class_method_locations' => $file_reference_provider->getAllClassMethodLocations(), + 'class_property_locations' => $file_reference_provider->getAllClassPropertyLocations(), + 'possible_method_param_types' => $analyzer->getPossibleMethodParamTypes(), + 'taint_data' => $codebase->taint_flow_graph, + 'unused_suppressions' => $codebase->track_unused_suppressions ? IssueBuffer::getUnusedSuppressions() : [], + 'used_suppressions' => $codebase->track_unused_suppressions ? IssueBuffer::getUsedSuppressions() : [], + 'function_docblock_manipulators' => FunctionDocblockManipulator::getManipulators(), + 'mutable_classes' => $codebase->analyzer->mutable_classes, + ]; + // @codingStandardsIgnoreEnd + } } diff --git a/src/Psalm/Internal/Codebase/ClassLikes.php b/src/Psalm/Internal/Codebase/ClassLikes.php index 1d394e2fa37..cb6882623df 100644 --- a/src/Psalm/Internal/Codebase/ClassLikes.php +++ b/src/Psalm/Internal/Codebase/ClassLikes.php @@ -1603,7 +1603,7 @@ public function getConstantsForClass(string $class_name, int $visibility): array if ($visibility === ReflectionProperty::IS_PUBLIC) { return array_filter( $storage->constants, - fn($constant) => $constant->type + static fn(ClassConstantStorage $constant): bool => $constant->type && $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC ); } @@ -1611,7 +1611,7 @@ public function getConstantsForClass(string $class_name, int $visibility): array if ($visibility === ReflectionProperty::IS_PROTECTED) { return array_filter( $storage->constants, - fn($constant) => $constant->type + static fn(ClassConstantStorage $constant): bool => $constant->type && ($constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC || $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PROTECTED) ); @@ -1619,7 +1619,7 @@ public function getConstantsForClass(string $class_name, int $visibility): array return array_filter( $storage->constants, - fn($constant) => $constant->type !== null + static fn(ClassConstantStorage $constant): bool => $constant->type !== null ); } diff --git a/src/Psalm/Internal/Codebase/Populator.php b/src/Psalm/Internal/Codebase/Populator.php index 5fbd1d049cd..458d8c14f26 100644 --- a/src/Psalm/Internal/Codebase/Populator.php +++ b/src/Psalm/Internal/Codebase/Populator.php @@ -11,6 +11,7 @@ use Psalm\Issue\CircularReference; use Psalm\IssueBuffer; use Psalm\Progress\Progress; +use Psalm\Storage\ClassConstantStorage; use Psalm\Storage\ClassLikeStorage; use Psalm\Storage\FileStorage; use Psalm\Type\Atomic\TTemplateParam; @@ -542,8 +543,9 @@ private function populateDataFromParentClass( $storage->constants = array_merge( array_filter( $parent_storage->constants, - fn($constant) => $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC - || $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PROTECTED + static fn(ClassConstantStorage $constant): bool + => $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC + || $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PROTECTED ), $storage->constants ); @@ -586,7 +588,8 @@ private function populateInterfaceData( $storage->constants = array_merge( array_filter( $interface_storage->constants, - fn($constant) => $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC + static fn(ClassConstantStorage $constant): bool + => $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC ), $storage->constants ); diff --git a/src/Psalm/Internal/Codebase/Reflection.php b/src/Psalm/Internal/Codebase/Reflection.php index 45a12766cec..16bb8322015 100644 --- a/src/Psalm/Internal/Codebase/Reflection.php +++ b/src/Psalm/Internal/Codebase/Reflection.php @@ -432,7 +432,7 @@ public static function getPsalmTypeFromReflectionType(?ReflectionType $reflectio $type = implode( '|', array_map( - fn(ReflectionNamedType $reflection) => $reflection->getName(), + static fn(ReflectionNamedType $reflection): string => $reflection->getName(), $reflection_type->getTypes() ) ); diff --git a/src/Psalm/Internal/Codebase/Scanner.php b/src/Psalm/Internal/Codebase/Scanner.php index e510bf63827..228472d4d62 100644 --- a/src/Psalm/Internal/Codebase/Scanner.php +++ b/src/Psalm/Internal/Codebase/Scanner.php @@ -2,6 +2,7 @@ namespace Psalm\Internal\Codebase; +use Closure; use Psalm\Codebase; use Psalm\Config; use Psalm\Internal\Analyzer\IssueData; @@ -311,14 +312,18 @@ public function scanFiles(ClassLikes $classlikes, int $pool_size = 1): bool return $has_changes; } + private function shouldScan(string $file_path): bool + { + return $this->file_provider->fileExists($file_path) + && (!isset($this->scanned_files[$file_path]) + || (isset($this->files_to_deep_scan[$file_path]) && !$this->scanned_files[$file_path])); + } + private function scanFilePaths(int $pool_size): bool { - $filetype_scanners = $this->config->getFiletypeScanners(); $files_to_scan = array_filter( $this->files_to_scan, - fn(string $file_path): bool => $this->file_provider->fileExists($file_path) - && (!isset($this->scanned_files[$file_path]) - || (isset($this->files_to_deep_scan[$file_path]) && !$this->scanned_files[$file_path])) + [$this, 'shouldScan'] ); $this->files_to_scan = []; @@ -327,17 +332,6 @@ private function scanFilePaths(int $pool_size): bool return false; } - $files_to_deep_scan = $this->files_to_deep_scan; - - $scanner_worker = - function (int $_, string $file_path) use ($filetype_scanners, $files_to_deep_scan): void { - $this->scanFile( - $file_path, - $filetype_scanners, - isset($files_to_deep_scan[$file_path]) - ); - }; - if (!$this->is_forked && $pool_size > 1 && count($files_to_scan) > 512) { $pool_size = ceil(min($pool_size, count($files_to_scan) / 256)); } else { @@ -376,7 +370,7 @@ function (): void { $this->progress->debug('Have initialised forked process for scanning' . PHP_EOL); }, - $scanner_worker, + Closure::fromCallable([$this, 'scanAPath']), /** * @return PoolData */ @@ -454,7 +448,7 @@ function () { $i = 0; foreach ($files_to_scan as $file_path => $_) { - $scanner_worker($i, $file_path); + $this->scanAPath($i, $file_path); ++$i; } } @@ -806,4 +800,13 @@ public function isForked(): void { $this->is_forked = true; } + + private function scanAPath(int $_, string $file_path): void + { + $this->scanFile( + $file_path, + $this->config->getFiletypeScanners(), + isset($this->files_to_deep_scan[$file_path]) + ); + } } diff --git a/src/Psalm/Internal/Codebase/TaintFlowGraph.php b/src/Psalm/Internal/Codebase/TaintFlowGraph.php index 1f61889f6b4..9fa5a4c2462 100644 --- a/src/Psalm/Internal/Codebase/TaintFlowGraph.php +++ b/src/Psalm/Internal/Codebase/TaintFlowGraph.php @@ -521,7 +521,12 @@ private function getSpecializedSources(DataFlowNode $source): array return array_filter( $generated_sources, - fn($new_source): bool => isset($this->forward_edges[$new_source->id]) + [$this, 'doesForwardEdgeExist'] ); } + + private function doesForwardEdgeExist(DataFlowNode $new_source): bool + { + return isset($this->forward_edges[$new_source->id]); + } } diff --git a/src/Psalm/Internal/Diff/ClassStatementsDiffer.php b/src/Psalm/Internal/Diff/ClassStatementsDiffer.php index fab907dec7a..ccc6e947815 100644 --- a/src/Psalm/Internal/Diff/ClassStatementsDiffer.php +++ b/src/Psalm/Internal/Diff/ClassStatementsDiffer.php @@ -35,7 +35,7 @@ public static function diff(string $name, array $a, array $b, string $a_code, st $diff_map = []; [$trace, $x, $y, $bc] = self::calculateTrace( - function ( + static function ( PhpParser\Node\Stmt $a, PhpParser\Node\Stmt $b, string $a_code, diff --git a/src/Psalm/Internal/Diff/FileStatementsDiffer.php b/src/Psalm/Internal/Diff/FileStatementsDiffer.php index d5decb928d9..92620e0305c 100644 --- a/src/Psalm/Internal/Diff/FileStatementsDiffer.php +++ b/src/Psalm/Internal/Diff/FileStatementsDiffer.php @@ -30,7 +30,7 @@ class FileStatementsDiffer extends AstDiffer public static function diff(array $a, array $b, string $a_code, string $b_code): array { [$trace, $x, $y, $bc] = self::calculateTrace( - function ( + static function ( PhpParser\Node\Stmt $a, PhpParser\Node\Stmt $b, string $a_code, diff --git a/src/Psalm/Internal/Diff/NamespaceStatementsDiffer.php b/src/Psalm/Internal/Diff/NamespaceStatementsDiffer.php index 77eec387528..90019904aab 100644 --- a/src/Psalm/Internal/Diff/NamespaceStatementsDiffer.php +++ b/src/Psalm/Internal/Diff/NamespaceStatementsDiffer.php @@ -30,7 +30,7 @@ class NamespaceStatementsDiffer extends AstDiffer public static function diff(string $name, array $a, array $b, string $a_code, string $b_code): array { [$trace, $x, $y, $bc] = self::calculateTrace( - function ( + static function ( PhpParser\Node\Stmt $a, PhpParser\Node\Stmt $b, string $a_code, diff --git a/src/Psalm/Internal/Fork/PsalmRestarter.php b/src/Psalm/Internal/Fork/PsalmRestarter.php index 4f55442b336..2ec087adf85 100644 --- a/src/Psalm/Internal/Fork/PsalmRestarter.php +++ b/src/Psalm/Internal/Fork/PsalmRestarter.php @@ -39,7 +39,7 @@ protected function requiresRestart($default): bool { $this->required = (bool) array_filter( $this->disabledExtensions, - fn(string $extension): bool => extension_loaded($extension) + static fn(string $extension): bool => extension_loaded($extension) ); return $default || $this->required; diff --git a/src/Psalm/Internal/LanguageServer/Client/TextDocument.php b/src/Psalm/Internal/LanguageServer/Client/TextDocument.php index c30aa05ed07..380b5e0d18e 100644 --- a/src/Psalm/Internal/LanguageServer/Client/TextDocument.php +++ b/src/Psalm/Internal/LanguageServer/Client/TextDocument.php @@ -64,7 +64,7 @@ public function xcontent(TextDocumentIdentifier $textDocument): Promise /** * @return Generator, object, TextDocumentItem> */ - function () use ($textDocument) { + static function () use ($textDocument) { /** @var Promise */ $promise = $this->handler->request( 'textDocument/xcontent', diff --git a/src/Psalm/Internal/LanguageServer/ClientHandler.php b/src/Psalm/Internal/LanguageServer/ClientHandler.php index 8c70156556e..d884c6a7079 100644 --- a/src/Psalm/Internal/LanguageServer/ClientHandler.php +++ b/src/Psalm/Internal/LanguageServer/ClientHandler.php @@ -58,7 +58,7 @@ public function request(string $method, $params): Promise /** * @return Generator> */ - function () use ($id, $method, $params): Generator { + static function () use ($id, $method, $params): Generator { yield $this->protocolWriter->write( new Message( new Request($id, $method, (object) $params) @@ -68,7 +68,7 @@ function () use ($id, $method, $params): Generator { $deferred = new Deferred(); $listener = - function (Message $msg) use ($id, $deferred, &$listener): void { + static function (Message $msg) use ($id, $deferred, &$listener): void { error_log('request handler'); /** * @psalm-suppress UndefinedPropertyFetch diff --git a/src/Psalm/Internal/LanguageServer/LanguageServer.php b/src/Psalm/Internal/LanguageServer/LanguageServer.php index 80bd0f4fff4..cb43637dd49 100644 --- a/src/Psalm/Internal/LanguageServer/LanguageServer.php +++ b/src/Psalm/Internal/LanguageServer/LanguageServer.php @@ -217,7 +217,7 @@ public function initialize( ): Promise { return call( /** @return Generator */ - function () { + function (): Generator { $this->verboseLog("Initializing..."); $this->clientStatus('initializing'); @@ -373,63 +373,62 @@ public function emitIssues(array $uris): void $this->current_issues = $data; foreach ($uris as $file_path => $uri) { - $diagnostics = array_map( - function (IssueData $issue_data): Diagnostic { - //$check_name = $issue->check_name; - $description = $issue_data->message; - $severity = $issue_data->severity; - - $start_line = max($issue_data->line_from, 1); - $end_line = $issue_data->line_to; - $start_column = $issue_data->column_from; - $end_column = $issue_data->column_to; - // Language server has 0 based lines and columns, phan has 1-based lines and columns. - $range = new Range( - new Position($start_line - 1, $start_column - 1), - new Position($end_line - 1, $end_column - 1) - ); - switch ($severity) { - case Config::REPORT_INFO: - $diagnostic_severity = DiagnosticSeverity::WARNING; - break; - case Config::REPORT_ERROR: - default: - $diagnostic_severity = DiagnosticSeverity::ERROR; - break; - } - $diagnostic = new Diagnostic( - $description, - $range, - null, - $diagnostic_severity, - 'Psalm' - ); + $diagnostics = []; + + foreach (($data[$file_path] ?? []) as $issue_data) { + //$check_name = $issue->check_name; + $description = $issue_data->message; + $severity = $issue_data->severity; + + $start_line = max($issue_data->line_from, 1); + $end_line = $issue_data->line_to; + $start_column = $issue_data->column_from; + $end_column = $issue_data->column_to; + // Language server has 0 based lines and columns, phan has 1-based lines and columns. + $range = new Range( + new Position($start_line - 1, $start_column - 1), + new Position($end_line - 1, $end_column - 1) + ); + switch ($severity) { + case Config::REPORT_INFO: + $diagnostic_severity = DiagnosticSeverity::WARNING; + break; + case Config::REPORT_ERROR: + default: + $diagnostic_severity = DiagnosticSeverity::ERROR; + break; + } + $diagnostic = new Diagnostic( + $description, + $range, + null, + $diagnostic_severity, + 'Psalm' + ); - //$code = 'PS' . \str_pad((string) $issue_data->shortcode, 3, "0", \STR_PAD_LEFT); - $code = $issue_data->link; + //$code = 'PS' . \str_pad((string) $issue_data->shortcode, 3, "0", \STR_PAD_LEFT); + $code = $issue_data->link; - if ($this->project_analyzer->language_server_use_extended_diagnostic_codes) { - // Added in VSCode 1.43.0 and will be part of the LSP 3.16.0 standard. - // Since this new functionality is not backwards compatible, we use a - // configuration option so the end user must opt in to it using the cli argument. - // https://github.com/microsoft/vscode/blob/1.43.0/src/vs/vscode.d.ts#L4688-L4699 + if ($this->project_analyzer->language_server_use_extended_diagnostic_codes) { + // Added in VSCode 1.43.0 and will be part of the LSP 3.16.0 standard. + // Since this new functionality is not backwards compatible, we use a + // configuration option so the end user must opt in to it using the cli argument. + // https://github.com/microsoft/vscode/blob/1.43.0/src/vs/vscode.d.ts#L4688-L4699 + /** @psalm-suppress InvalidPropertyAssignmentValue */ + $diagnostic->code = [ + "value" => $code, + "target" => $issue_data->link, + ]; + } else { + // the Diagnostic constructor only takes `int` for the code, but the property can be + // `int` or `string`, so we set the property directly because we want to use a `string` /** @psalm-suppress InvalidPropertyAssignmentValue */ - $diagnostic->code = [ - "value" => $code, - "target" => $issue_data->link, - ]; - } else { - // the Diagnostic constructor only takes `int` for the code, but the property can be - // `int` or `string`, so we set the property directly because we want to use a `string` - /** @psalm-suppress InvalidPropertyAssignmentValue */ - $diagnostic->code = $code; - } + $diagnostic->code = $code; + } - return $diagnostic; - }, - $data[$file_path] ?? [] - ); + $diagnostics[] = $diagnostic; + } $this->client->textDocument->publishDiagnostics($uri, $diagnostics); } diff --git a/src/Psalm/Internal/PhpVisitor/CloningVisitor.php b/src/Psalm/Internal/PhpVisitor/CloningVisitor.php index 5ea8d9430ec..f6a089b1f87 100644 --- a/src/Psalm/Internal/PhpVisitor/CloningVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/CloningVisitor.php @@ -4,12 +4,9 @@ namespace Psalm\Internal\PhpVisitor; -use PhpParser\Comment; use PhpParser\Node; use PhpParser\NodeVisitorAbstract; -use function array_map; - /** * Visitor cloning all nodes and linking to the original nodes using an attribute. * @@ -22,17 +19,14 @@ class CloningVisitor extends NodeVisitorAbstract public function enterNode(Node $node): Node { $node = clone $node; - if ($cs = $node->getComments()) { - $node->setAttribute( - 'comments', - array_map( - /** - * @return Comment - */ - fn(Comment $c): Comment => clone $c, - $cs - ) - ); + + if (($cs = $node->getComments()) !== []) { + $comments = []; + foreach ($cs as $i => $comment) { + $comments[$i] = clone $comment; + } + + $node->setAttribute('comments', $comments); } return $node; diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php index ae25c5cadeb..03532b24117 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php @@ -64,8 +64,6 @@ use RuntimeException; use UnexpectedValueException; -use function array_filter; -use function array_map; use function array_merge; use function array_pop; use function array_shift; @@ -430,7 +428,7 @@ public function start(PhpParser\Node\Stmt\ClassLike $node): ?bool usort( $docblock_info->templates, - fn(array $l, array $r): int => $l[4] > $r[4] ? 1 : -1 + static fn(array $l, array $r): int => $l[4] > $r[4] ? 1 : -1 ); foreach ($docblock_info->templates as $i => $template_map) { @@ -794,30 +792,20 @@ public function finish(PhpParser\Node\Stmt\ClassLike $node): ClassLikeStorage } } - $converted_aliases = array_map( - function (InlineTypeAlias $t): ?ClassTypeAlias { - try { - $union = TypeParser::parseTokens( - $t->replacement_tokens, - null, - [], - $this->type_aliases - ); - - $union->setFromDocblock(); + $converted_aliases = []; + foreach ($this->classlike_type_aliases as $key => $type) { + try { + $union = TypeParser::parseTokens( + $type->replacement_tokens, + null, + [], + $this->type_aliases + ); - return new ClassTypeAlias( - array_values($union->getAtomicTypes()) - ); - } catch (Exception $e) { - return null; - } - }, - $this->classlike_type_aliases - ); + $union->setFromDocblock(); - foreach ($converted_aliases as $key => $type) { - if (!$type) { + $converted_aliases[$key] = new ClassTypeAlias(array_values($union->getAtomicTypes())); + } catch (Exception $e) { $classlike_storage->docblock_issues[] = new InvalidDocblock( '@psalm-type ' . $key . ' contains invalid references', new CodeLocation($this->file_scanner, $node, null, true) @@ -825,7 +813,7 @@ function (InlineTypeAlias $t): ?ClassTypeAlias { } } - $classlike_storage->type_aliases = array_filter($converted_aliases); + $classlike_storage->type_aliases = $converted_aliases; return $classlike_storage; } diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionResolver.php b/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionResolver.php index 65a78a746c9..11b1e4add92 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionResolver.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionResolver.php @@ -344,7 +344,7 @@ public static function enterConditional( ) ) { $php_version_id = $codebase->analysis_php_version_id; - $evaluator = new ConstExprEvaluator(function (Expr $expr) use ($php_version_id) { + $evaluator = new ConstExprEvaluator(static function (Expr $expr) use ($php_version_id) { if ($expr instanceof ConstFetch && $expr->name->parts === ['PHP_VERSION_ID']) { return $php_version_id; } diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php index 87c2c9edef5..90628a8922c 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php @@ -46,7 +46,6 @@ use Psalm\Type\Union; use function array_filter; -use function array_map; use function array_merge; use function array_values; use function count; @@ -182,9 +181,13 @@ public static function addDocblockInfo( $line ); - $class_names = array_filter(array_map('trim', explode('|', $throw))); + foreach (explode('|', $throw) as $throw_class) { + $throw_class = trim($throw_class); + + if ($throw_class === '') { + continue; + } - foreach ($class_names as $throw_class) { if ($throw_class !== 'self' && $throw_class !== 'static' && $throw_class !== 'parent') { $exception_fqcln = Type::getFQCLNFromString( $throw_class, @@ -285,7 +288,7 @@ public static function addDocblockInfo( if ($storage instanceof MethodStorage) { $storage->has_docblock_param_types = (bool) array_filter( $storage->params, - fn(FunctionLikeParameter $p): bool => $p->type !== null && $p->has_docblock_type + static fn(FunctionLikeParameter $p): bool => $p->type !== null && $p->has_docblock_type ); } @@ -895,7 +898,7 @@ private static function improveParamsFromDocblock( $params_without_docblock_type = array_filter( $storage->params, - fn(FunctionLikeParameter $p): bool => !$p->has_docblock_type && (!$p->type || $p->type->hasArray()) + static fn(FunctionLikeParameter $p): bool => !$p->has_docblock_type && (!$p->type || $p->type->hasArray()) ); if ($params_without_docblock_type) { diff --git a/src/Psalm/Internal/PluginManager/Command/ShowCommand.php b/src/Psalm/Internal/PluginManager/Command/ShowCommand.php index ce72f1cda02..e78f1395a62 100644 --- a/src/Psalm/Internal/PluginManager/Command/ShowCommand.php +++ b/src/Psalm/Internal/PluginManager/Command/ShowCommand.php @@ -59,7 +59,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** * @return array{0: null|string, 1: string} */ - fn(string $class, ?string $package): array => [$package, $class]; + static fn(string $class, ?string $package): array => [$package, $class]; $io->section('Enabled'); if (count($enabled)) { diff --git a/src/Psalm/Internal/Provider/FileProvider.php b/src/Psalm/Internal/Provider/FileProvider.php index 1296574b274..863bfc29e24 100644 --- a/src/Psalm/Internal/Provider/FileProvider.php +++ b/src/Psalm/Internal/Provider/FileProvider.php @@ -138,7 +138,7 @@ public function getFilesInDir(string $dir_path, array $file_extensions, callable $iterator = new RecursiveCallbackFilterIterator( $iterator, /** @param mixed $_ */ - function (string $current, $_, RecursiveIterator $iterator) use ($filter): bool { + static function (string $current, $_, RecursiveIterator $iterator) use ($filter): bool { if ($iterator->hasChildren()) { $path = $current . DIRECTORY_SEPARATOR; } else { diff --git a/src/Psalm/Internal/Provider/FileReferenceProvider.php b/src/Psalm/Internal/Provider/FileReferenceProvider.php index ece9a2ac06e..08636f673ba 100644 --- a/src/Psalm/Internal/Provider/FileReferenceProvider.php +++ b/src/Psalm/Internal/Provider/FileReferenceProvider.php @@ -186,7 +186,7 @@ public function getDeletedReferencedFiles(): array if (self::$deleted_files === null) { self::$deleted_files = array_filter( array_keys(self::$file_references), - fn(string $file_name): bool => !file_exists($file_name) + static fn(string $file_name): bool => !file_exists($file_name) ); } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php index 225b865d72b..5c55b900ce6 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php @@ -195,10 +195,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev if ($array_arg_atomic_type instanceof TKeyedArray && count($call_args) === 2) { $atomic_type = new TKeyedArray( array_map( - /** - * @return Union - */ - fn(Union $_): Union => clone $mapping_return_type, + static fn(Union $_): Union => clone $mapping_return_type, $array_arg_atomic_type->properties ) ); diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php index a1bd13926ee..805c51770d9 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php @@ -109,14 +109,14 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev if ($event->getFunctionId() === 'min') { assert(count($min_bounds) !== 0); //null values in $max_bounds doesn't make sense for min() so we remove them - $max_bounds = array_filter($max_bounds, fn($v) => $v !== null) ?: [null]; + $max_bounds = array_filter($max_bounds, static fn($v): bool => $v !== null) ?: [null]; $min_potential_int = in_array(null, $min_bounds, true) ? null : min($min_bounds); $max_potential_int = in_array(null, $max_bounds, true) ? null : min($max_bounds); } else { assert(count($max_bounds) !== 0); //null values in $min_bounds doesn't make sense for max() so we remove them - $min_bounds = array_filter($min_bounds, fn($v) => $v !== null) ?: [null]; + $min_bounds = array_filter($min_bounds, static fn($v): bool => $v !== null) ?: [null]; $min_potential_int = in_array(null, $min_bounds, true) ? null : max($min_bounds); $max_potential_int = in_array(null, $max_bounds, true) ? null : max($max_bounds); diff --git a/src/Psalm/Internal/Provider/StatementsProvider.php b/src/Psalm/Internal/Provider/StatementsProvider.php index d6d5d4abccc..988c93ee69b 100644 --- a/src/Psalm/Internal/Provider/StatementsProvider.php +++ b/src/Psalm/Internal/Provider/StatementsProvider.php @@ -218,7 +218,7 @@ public function getStatementsForFile( $file_path_hash = md5($file_path); $changed_members = array_map( - function (string $key) use ($file_path_hash): string { + static function (string $key) use ($file_path_hash): string { if (strpos($key, 'use:') === 0) { return $key . ':' . $file_path_hash; } diff --git a/src/Psalm/Internal/Scanner/PhpStormMetaScanner.php b/src/Psalm/Internal/Scanner/PhpStormMetaScanner.php index c412736a14d..95cce5e791a 100644 --- a/src/Psalm/Internal/Scanner/PhpStormMetaScanner.php +++ b/src/Psalm/Internal/Scanner/PhpStormMetaScanner.php @@ -104,7 +104,7 @@ public static function handleOverride(array $args, Codebase $codebase): void /** * @param list $call_args */ - function ( + static function ( MethodReturnTypeProviderEvent $event ) use ( $map, @@ -160,7 +160,7 @@ function ( /** * @param list $call_args */ - function ( + static function ( MethodReturnTypeProviderEvent $event ) use ( $type_offset, @@ -197,7 +197,7 @@ function ( /** * @param list $call_args */ - function ( + static function ( MethodReturnTypeProviderEvent $event ) use ( $element_type_offset, @@ -262,7 +262,7 @@ function ( * @param non-empty-string $function_id * @param list $call_args */ - function ( + static function ( FunctionReturnTypeProviderEvent $event ) use ( $map, @@ -316,7 +316,7 @@ function ( * @param non-empty-string $function_id * @param list $call_args */ - function ( + static function ( FunctionReturnTypeProviderEvent $event ) use ( $type_offset @@ -350,7 +350,7 @@ function ( * @param non-empty-string $function_id * @param list $call_args */ - function ( + static function ( FunctionReturnTypeProviderEvent $event ) use ( $element_type_offset diff --git a/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php b/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php index ce43e4cb664..b267ffca9e4 100644 --- a/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php @@ -192,7 +192,7 @@ public static function isContainedBy( // if the array has a known size < 10, make sure the array keys are literal ints if ($input_type_part->count !== null && $input_type_part->count < 10) { $literal_ints = array_map( - fn($i) => new TLiteralInt($i), + static fn($i): TLiteralInt => new TLiteralInt($i), range(0, $input_type_part->count - 1) ); diff --git a/src/Psalm/Internal/Type/TemplateResult.php b/src/Psalm/Internal/Type/TemplateResult.php index b4d2c17986e..341c12ccaa9 100644 --- a/src/Psalm/Internal/Type/TemplateResult.php +++ b/src/Psalm/Internal/Type/TemplateResult.php @@ -4,8 +4,6 @@ use Psalm\Type\Union; -use function array_map; - /** * This class captures the result of running Psalm's argument analysis with * regard to generic parameters. @@ -59,13 +57,12 @@ class TemplateResult public function __construct(array $template_types, array $lower_bounds) { $this->template_types = $template_types; + $this->lower_bounds = []; - $this->lower_bounds = array_map( - fn($type_map) => array_map( - fn($type) => [new TemplateBound($type)], - $type_map - ), - $lower_bounds - ); + foreach ($lower_bounds as $key1 => $boundSet) { + foreach ($boundSet as $key2 => $bound) { + $this->lower_bounds[$key1][$key2] = [new TemplateBound($bound)]; + } + } } } diff --git a/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php b/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php index afe2ed24d4b..c9d49188cfc 100644 --- a/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php +++ b/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php @@ -1116,7 +1116,7 @@ public static function getMostSpecificTypeFromBounds(array $lower_bounds, ?Codeb usort( $lower_bounds, - fn(TemplateBound $bound_a, TemplateBound $bound_b) => $bound_b->appearance_depth <=> $bound_a->appearance_depth + static fn(TemplateBound $bound_a, TemplateBound $bound_b): int => $bound_b->appearance_depth <=> $bound_a->appearance_depth ); $current_depth = null; diff --git a/src/Psalm/Internal/Type/TypeCombiner.php b/src/Psalm/Internal/Type/TypeCombiner.php index c28f119ef45..84778f983b5 100644 --- a/src/Psalm/Internal/Type/TypeCombiner.php +++ b/src/Psalm/Internal/Type/TypeCombiner.php @@ -743,7 +743,7 @@ private static function scrapeTypeProperties( $min_prop_count = count( array_filter( $type->properties, - fn($p) => !$p->possibly_undefined + static fn(Union $p): bool => !$p->possibly_undefined ) ); $combination->array_min_counts[$min_prop_count] = true; @@ -1174,7 +1174,7 @@ private static function scrapeIntProperties( $all_nonnegative = !array_filter( $combination->ints, - fn($int): bool => $int->value < 0 + static fn($int): bool => $int->value < 0 ); if (isset($combination->value_types['int'])) { @@ -1350,7 +1350,7 @@ private static function handleKeyedArrayEntries( ) { $combination->objectlike_entries = array_filter( $combination->objectlike_entries, - fn(Union $type): bool => !$type->possibly_undefined + static fn(Union $type): bool => !$type->possibly_undefined ); } diff --git a/src/Psalm/Internal/Type/TypeExpander.php b/src/Psalm/Internal/Type/TypeExpander.php index 156774860e9..effe35c3458 100644 --- a/src/Psalm/Internal/Type/TypeExpander.php +++ b/src/Psalm/Internal/Type/TypeExpander.php @@ -264,7 +264,7 @@ public static function expandAtomic( if ($const_name_part) { $matching_constants = array_filter( $matching_constants, - fn($constant_name): bool => $constant_name !== $const_name_part + static fn($constant_name): bool => $constant_name !== $const_name_part && strpos($constant_name, $const_name_part) === 0 ); } @@ -603,14 +603,14 @@ private static function expandNamedObject( if ($container_class_storage->template_types && array_filter( $container_class_storage->template_types, - fn($type_map) => !reset($type_map)->hasMixed() + static fn($type_map): bool => !reset($type_map)->hasMixed() ) ) { $return_type = new TGenericObject( $return_type->value, array_values( array_map( - fn($type_map) => clone reset($type_map), + static fn($type_map) => clone reset($type_map), $container_class_storage->template_types ) ) diff --git a/src/Psalm/Internal/Type/TypeParser.php b/src/Psalm/Internal/Type/TypeParser.php index 6ae6c3ef10c..992bda57226 100644 --- a/src/Psalm/Internal/Type/TypeParser.php +++ b/src/Psalm/Internal/Type/TypeParser.php @@ -499,7 +499,7 @@ public static function getComputedIntsFromMask(array $potential_ints): array $potential_values = array_unique($potential_values); return array_map( - fn($int) => new TLiteralInt($int), + static fn($int): TLiteralInt => new TLiteralInt($int), array_values($potential_values) ); } @@ -981,26 +981,25 @@ private static function getTypeFromIntersectionTree( array $template_type_map, array $type_aliases ): Atomic { - $intersection_types = array_map( - function (ParseTree $child_tree) use ($codebase, $template_type_map, $type_aliases) { - $atomic_type = self::getTypeFromTree( - $child_tree, - $codebase, - null, - $template_type_map, - $type_aliases - ); + $intersection_types = []; - if (!$atomic_type instanceof Atomic) { - throw new TypeParseTreeException( - 'Intersection types cannot contain unions' - ); - } + foreach ($parse_tree->children as $name => $child_tree) { + $atomic_type = self::getTypeFromTree( + $child_tree, + $codebase, + null, + $template_type_map, + $type_aliases + ); - return $atomic_type; - }, - $parse_tree->children - ); + if (!$atomic_type instanceof Atomic) { + throw new TypeParseTreeException( + 'Intersection types cannot contain unions' + ); + } + + $intersection_types[$name] = $atomic_type; + } $first_type = reset($intersection_types); $last_type = end($intersection_types); @@ -1143,67 +1142,58 @@ private static function getTypeFromCallableTree( array $template_type_map, array $type_aliases ) { - $params = array_map( - /** - * @return FunctionLikeParameter - */ - function (ParseTree $child_tree) use ( - $codebase, - $template_type_map, - $type_aliases - ): FunctionLikeParameter { - $is_variadic = false; - $is_optional = false; - - if ($child_tree instanceof CallableParamTree) { - if (isset($child_tree->children[0])) { - $tree_type = self::getTypeFromTree( - $child_tree->children[0], - $codebase, - null, - $template_type_map, - $type_aliases - ); - } else { - $tree_type = new TMixed(); - } + $params = []; - $is_variadic = $child_tree->variadic; - $is_optional = $child_tree->has_default; - } else { - if ($child_tree instanceof Value && strpos($child_tree->value, '$') > 0) { - $child_tree->value = preg_replace('/(.+)\$.*/', '$1', $child_tree->value); - } + foreach ($parse_tree->children as $child_tree) { + $is_variadic = false; + $is_optional = false; + if ($child_tree instanceof CallableParamTree) { + if (isset($child_tree->children[0])) { $tree_type = self::getTypeFromTree( - $child_tree, + $child_tree->children[0], $codebase, null, $template_type_map, $type_aliases ); + } else { + $tree_type = new TMixed(); } - $tree_type = $tree_type instanceof Union ? $tree_type : new Union([$tree_type]); + $is_variadic = $child_tree->variadic; + $is_optional = $child_tree->has_default; + } else { + if ($child_tree instanceof Value && strpos($child_tree->value, '$') > 0) { + $child_tree->value = preg_replace('/(.+)\$.*/', '$1', $child_tree->value); + } - $param = new FunctionLikeParameter( - '', - false, - $tree_type, - null, + $tree_type = self::getTypeFromTree( + $child_tree, + $codebase, null, - $is_optional, - false, - $is_variadic + $template_type_map, + $type_aliases ); + } - // type is not authoritative - $param->signature_type = null; + $param = new FunctionLikeParameter( + '', + false, + $tree_type instanceof Union ? $tree_type : new Union([$tree_type]), + null, + null, + $is_optional, + false, + $is_variadic + ); + + // type is not authoritative + $param->signature_type = null; + + $params[] = $param; + } - return $param; - }, - $parse_tree->children - ); $pure = strpos($parse_tree->value, 'pure-') === 0 ? true : null; if (in_array(strtolower($parse_tree->value), ['closure', '\closure', 'pure-closure'], true)) { diff --git a/src/Psalm/IssueBuffer.php b/src/Psalm/IssueBuffer.php index 94f38c4a357..2f07b0a81aa 100644 --- a/src/Psalm/IssueBuffer.php +++ b/src/Psalm/IssueBuffer.php @@ -569,7 +569,7 @@ public static function finish( foreach (self::$issues_data as $file_path => $file_issues) { usort( $file_issues, - function (IssueData $d1, IssueData $d2): int { + static function (IssueData $d1, IssueData $d2): int { if ($d1->file_path === $d2->file_path) { if ($d1->line_from === $d2->line_from) { if ($d1->column_from === $d2->column_from) { diff --git a/src/Psalm/Report.php b/src/Psalm/Report.php index e8441107321..e1956d1991e 100644 --- a/src/Psalm/Report.php +++ b/src/Psalm/Report.php @@ -90,7 +90,7 @@ public function __construct( if (!$report_options->show_info) { $this->issues_data = array_filter( $issues_data, - fn($issue_data): bool => $issue_data->severity !== Config::REPORT_INFO + static fn(IssueData $issue_data): bool => $issue_data->severity !== Config::REPORT_INFO ); } else { $this->issues_data = $issues_data; diff --git a/src/Psalm/Report/CodeClimateReport.php b/src/Psalm/Report/CodeClimateReport.php index 4eb31c92fb0..8aa412a5aa9 100644 --- a/src/Psalm/Report/CodeClimateReport.php +++ b/src/Psalm/Report/CodeClimateReport.php @@ -27,37 +27,7 @@ public function create(): string $options = $this->pretty ? Json::PRETTY : Json::DEFAULT; $issues_data = array_map( - fn(IssueData $issue): array => - /** - * map fields to new structure. - * Expected fields: - * - type - * - check_name - * - description* - * - content - * - categories[] - * - severity - * - fingerprint* - * - location.path* - * - location.lines.begin* - * - * Fields with * are the one used by Gitlab for Code Quality - */ - [ - 'type' => 'issue', - 'check_name' => $issue->type, - 'description' => $issue->message, - 'categories' => [$issue->type], - 'severity' => $this->convertSeverity($issue->severity), - 'fingerprint' => $this->calculateFingerprint($issue), - 'location' => [ - 'path' => $issue->file_name, - 'lines' => [ - 'begin' => $issue->line_from, - 'end' => $issue->line_to, - ], - ], - ], + [$this, 'mapToNewStructure'], $this->issues_data ); @@ -91,4 +61,38 @@ protected function calculateFingerprint(IssueData $issue): string { return md5($issue->type.$issue->message.$issue->file_name.$issue->from.$issue->to); } + + /** + * map fields to new structure. + * Expected fields: + * - type + * - check_name + * - description* + * - content + * - categories[] + * - severity + * - fingerprint* + * - location.path* + * - location.lines.begin* + * + * Fields with * are the one used by Gitlab for Code Quality + */ + private function mapToNewStructure(IssueData $issue): array + { + return [ + 'type' => 'issue', + 'check_name' => $issue->type, + 'description' => $issue->message, + 'categories' => [$issue->type], + 'severity' => $this->convertSeverity($issue->severity), + 'fingerprint' => $this->calculateFingerprint($issue), + 'location' => [ + 'path' => $issue->file_name, + 'lines' => [ + 'begin' => $issue->line_from, + 'end' => $issue->line_to, + ], + ], + ]; + } } diff --git a/src/Psalm/Report/JsonReport.php b/src/Psalm/Report/JsonReport.php index 11f1fd188c5..a6eed4cb1c9 100644 --- a/src/Psalm/Report/JsonReport.php +++ b/src/Psalm/Report/JsonReport.php @@ -2,6 +2,7 @@ namespace Psalm\Report; +use Psalm\Internal\Analyzer\IssueData; use Psalm\Internal\Json\Json; use Psalm\Report; @@ -15,7 +16,7 @@ public function create(): string $options = $this->pretty ? Json::PRETTY : Json::DEFAULT; $issues_data = array_map( - function ($issue_data): array { + static function (IssueData $issue_data): array { $issue_data = (array) $issue_data; unset($issue_data['dupe_key']); return $issue_data; diff --git a/src/Psalm/Report/XmlReport.php b/src/Psalm/Report/XmlReport.php index 0136b51e583..1d657c70cbb 100644 --- a/src/Psalm/Report/XmlReport.php +++ b/src/Psalm/Report/XmlReport.php @@ -18,20 +18,20 @@ public function create(): string 'report', [ 'item' => array_map( - function (IssueData $issue_data): array { + static function (IssueData $issue_data): array { $issue_data = get_object_vars($issue_data); unset($issue_data['dupe_key']); if (null !== $issue_data['taint_trace']) { $issue_data['taint_trace'] = array_map( - fn($trace): array => (array) $trace, + static fn($trace): array => (array) $trace, $issue_data['taint_trace'] ); } if (null !== $issue_data['other_references']) { $issue_data['other_references'] = array_map( - fn(DataFlowNodeData $reference): array => (array) $reference, + static fn(DataFlowNodeData $reference): array => (array) $reference, $issue_data['other_references'] ); } diff --git a/src/Psalm/Storage/FunctionLikeStorage.php b/src/Psalm/Storage/FunctionLikeStorage.php index 82cc20fcad9..aea1600e1c3 100644 --- a/src/Psalm/Storage/FunctionLikeStorage.php +++ b/src/Psalm/Storage/FunctionLikeStorage.php @@ -248,7 +248,7 @@ public function getSignature(bool $allow_newlines): string . implode( ',' . ($newlines ? "\n" : ' '), array_map( - fn(FunctionLikeParameter $param): string => + static fn(FunctionLikeParameter $param): string => ($newlines ? ' ' : '') . ($param->type ? $param->type->getId(false) : 'mixed') . ' $' . $param->name, diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php index 55f11f098ad..02c182c5eda 100644 --- a/src/Psalm/Type/Atomic.php +++ b/src/Psalm/Type/Atomic.php @@ -353,7 +353,7 @@ public function isNamedObjectType(): bool && ($this->as->hasNamedObjectType() || array_filter( $this->extra_types ?: [], - fn($extra_type) => $extra_type->isNamedObjectType() + static fn($extra_type): bool => $extra_type->isNamedObjectType() ) ) ); @@ -404,7 +404,7 @@ public function hasTraversableInterface(Codebase $codebase): bool $this->extra_types && array_filter( $this->extra_types, - fn(Atomic $a): bool => $a->hasTraversableInterface($codebase) + static fn(Atomic $a): bool => $a->hasTraversableInterface($codebase) ) ) ); @@ -427,7 +427,7 @@ public function hasCountableInterface(Codebase $codebase): bool $this->extra_types && array_filter( $this->extra_types, - fn(Atomic $a): bool => $a->hasCountableInterface($codebase) + static fn(Atomic $a): bool => $a->hasCountableInterface($codebase) ) ) ); @@ -466,7 +466,7 @@ public function hasArrayAccessInterface(Codebase $codebase): bool $this->extra_types && array_filter( $this->extra_types, - fn(Atomic $a): bool => $a->hasArrayAccessInterface($codebase) + static fn(Atomic $a): bool => $a->hasArrayAccessInterface($codebase) ) ) ); diff --git a/src/Psalm/Type/Atomic/CallableTrait.php b/src/Psalm/Type/Atomic/CallableTrait.php index 0b5252f0af3..8e8e1e633e8 100644 --- a/src/Psalm/Type/Atomic/CallableTrait.php +++ b/src/Psalm/Type/Atomic/CallableTrait.php @@ -12,7 +12,6 @@ use Psalm\Type\TypeNode; use Psalm\Type\Union; -use function array_map; use function count; use function implode; @@ -110,29 +109,19 @@ public function toNamespacedString( $return_type_string = ''; if ($this->params !== null) { - $param_string = '(' . implode( - ', ', - array_map( - /** - * @return string - */ - function (FunctionLikeParameter $param) use ($namespace, $aliased_classes, $this_class): string { - if (!$param->type) { - $type_string = 'mixed'; - } else { - $type_string = $param->type->toNamespacedString( - $namespace, - $aliased_classes, - $this_class, - false - ); - } - - return ($param->is_variadic ? '...' : '') . $type_string . ($param->is_optional ? '=' : ''); - }, - $this->params - ) - ) . ')'; + $params_array = []; + + foreach ($this->params as $param) { + if (!$param->type) { + $type_string = 'mixed'; + } else { + $type_string = $param->type->toNamespacedString($namespace, $aliased_classes, $this_class, false); + } + + $params_array[] = ($param->is_variadic ? '...' : '') . $type_string . ($param->is_optional ? '=' : ''); + } + + $param_string = '(' . implode(', ', $params_array) . ')'; } if ($this->return_type !== null) { diff --git a/src/Psalm/Type/Atomic/GenericTrait.php b/src/Psalm/Type/Atomic/GenericTrait.php index 6e82cb64923..ba4df1e9873 100644 --- a/src/Psalm/Type/Atomic/GenericTrait.php +++ b/src/Psalm/Type/Atomic/GenericTrait.php @@ -35,7 +35,7 @@ public function getId(bool $exact = true, bool $nested = false): string $extra_types = '&' . implode( '&', array_map( - fn($type) => $type->getId($exact, true), + static fn(Atomic $type): string => $type->getId($exact, true), $this->extra_types ) ); @@ -119,10 +119,7 @@ public function toNamespacedString( $extra_types = '&' . implode( '&', array_map( - /** - * @return string - */ - fn(Atomic $extra_type): string => + static fn(Atomic $extra_type): string => $extra_type->toNamespacedString($namespace, $aliased_classes, $this_class, false), $this->extra_types ) @@ -134,10 +131,7 @@ public function toNamespacedString( implode( ', ', array_map( - /** - * @return string - */ - fn(Union $type_param): string => + static fn(Union $type_param): string => $type_param->toNamespacedString($namespace, $aliased_classes, $this_class, false), $type_params ) diff --git a/src/Psalm/Type/Atomic/HasIntersectionTrait.php b/src/Psalm/Type/Atomic/HasIntersectionTrait.php index 368ebfef191..720379d9295 100644 --- a/src/Psalm/Type/Atomic/HasIntersectionTrait.php +++ b/src/Psalm/Type/Atomic/HasIntersectionTrait.php @@ -35,10 +35,8 @@ private function getNamespacedIntersectionTypes( array_map( /** * @param TNamedObject|TTemplateParam|TIterable|TObjectWithProperties $extra_type - * - * @return string */ - fn(Atomic $extra_type): string => $extra_type->toNamespacedString( + static fn(Atomic $extra_type): string => $extra_type->toNamespacedString( $namespace, $aliased_classes, $this_class, diff --git a/src/Psalm/Type/Atomic/TKeyedArray.php b/src/Psalm/Type/Atomic/TKeyedArray.php index 75d37a9b02c..b72e6a62321 100644 --- a/src/Psalm/Type/Atomic/TKeyedArray.php +++ b/src/Psalm/Type/Atomic/TKeyedArray.php @@ -20,8 +20,6 @@ use UnexpectedValueException; use function addslashes; -use function array_keys; -use function array_map; use function count; use function get_class; use function implode; @@ -87,25 +85,24 @@ public function __construct(array $properties, ?array $class_strings = null) public function getId(bool $exact = true, bool $nested = false): string { - $property_strings = array_map( - function ($name, Union $type) use ($exact): string { - if ($this->is_list && $this->sealed) { - return $type->getId($exact); - } - - $class_string_suffix = ''; - if (isset($this->class_strings[$name])) { - $class_string_suffix = '::class'; - } - - $name = $this->escapeAndQuote($name); - - return $name . $class_string_suffix . ($type->possibly_undefined ? '?' : '') - . ': ' . $type->getId($exact); - }, - array_keys($this->properties), - $this->properties - ); + $property_strings = []; + + foreach ($this->properties as $name => $type) { + if ($this->is_list && $this->sealed) { + $property_strings[$name] = $type->getId($exact); + continue; + } + + $class_string_suffix = ''; + if (isset($this->class_strings[$name])) { + $class_string_suffix = '::class'; + } + + $name = $this->escapeAndQuote($name); + + $property_strings[$name] = $name . $class_string_suffix . ($type->possibly_undefined ? '?' : '') + . ': ' . $type->getId($exact); + } if (!$this->is_list) { sort($property_strings); @@ -141,39 +138,26 @@ public function toNamespacedString( ); } - return static::KEY . '{' . - implode( - ', ', - array_map( - function ( - $name, - Union $type - ) use ( - $namespace, - $aliased_classes, - $this_class, - $use_phpdoc_format - ): string { - $class_string_suffix = ''; - if (isset($this->class_strings[$name])) { - $class_string_suffix = '::class'; - } - - $name = $this->escapeAndQuote($name); - - return $name . $class_string_suffix . ($type->possibly_undefined ? '?' : '') . ': ' . - $type->toNamespacedString( - $namespace, - $aliased_classes, - $this_class, - $use_phpdoc_format - ); - }, - array_keys($this->properties), - $this->properties - ) - ) . - '}'; + $suffixed_properties = []; + + foreach ($this->properties as $name => $type) { + $class_string_suffix = ''; + if (isset($this->class_strings[$name])) { + $class_string_suffix = '::class'; + } + + $name = $this->escapeAndQuote($name); + + $suffixed_properties[$name] = $name . $class_string_suffix . ($type->possibly_undefined ? '?' : '') . ': ' . + $type->toNamespacedString( + $namespace, + $aliased_classes, + $this_class, + false + ); + } + + return static::KEY . '{' . implode(', ', $suffixed_properties) . '}'; } /** diff --git a/src/Psalm/Type/Atomic/TNamedObject.php b/src/Psalm/Type/Atomic/TNamedObject.php index 16bdaa3ee20..88643acd73f 100644 --- a/src/Psalm/Type/Atomic/TNamedObject.php +++ b/src/Psalm/Type/Atomic/TNamedObject.php @@ -64,7 +64,7 @@ public function getId(bool $exact = true, bool $nested = false): string return $this->value . '&' . implode( '&', array_map( - fn($type) => $type->getId($exact, true), + static fn(Atomic $type): string => $type->getId($exact, true), $this->extra_types ) ); diff --git a/src/Psalm/Type/Atomic/TObjectWithProperties.php b/src/Psalm/Type/Atomic/TObjectWithProperties.php index 2ae470848cc..81d3fc76f66 100644 --- a/src/Psalm/Type/Atomic/TObjectWithProperties.php +++ b/src/Psalm/Type/Atomic/TObjectWithProperties.php @@ -60,7 +60,7 @@ public function getId(bool $exact = true, bool $nested = false): string /** * @param string|int $name */ - fn($name, Union $type): string => $name . ($type->possibly_undefined ? '?' : '') . ':' + static fn($name, Union $type): string => $name . ($type->possibly_undefined ? '?' : '') . ':' . $type->getId($exact), array_keys($this->properties), $this->properties @@ -70,7 +70,7 @@ public function getId(bool $exact = true, bool $nested = false): string $methods_string = implode( ', ', array_map( - fn(string $name): string => $name . '()', + static fn(string $name): string => $name . '()', array_keys($this->methods) ) ); @@ -102,7 +102,7 @@ public function toNamespacedString( /** * @param string|int $name */ - fn($name, Union $type): string => + static fn($name, Union $type): string => $name . ($type->possibly_undefined ? '?' : '') . ':' diff --git a/src/Psalm/Type/Atomic/TTemplateParam.php b/src/Psalm/Type/Atomic/TTemplateParam.php index a19bffd8f92..b34d9e4d49f 100644 --- a/src/Psalm/Type/Atomic/TTemplateParam.php +++ b/src/Psalm/Type/Atomic/TTemplateParam.php @@ -61,7 +61,8 @@ public function getId(bool $exact = true, bool $nested = false): string if ($this->extra_types) { return '(' . $this->param_name . ':' . $this->defining_class . ' as ' . $this->as->getId($exact) - . ')&' . implode('&', array_map(fn($type) => $type->getId($exact, true), $this->extra_types)); + . ')&' . implode('&', array_map(static fn(Atomic $type): string + => $type->getId($exact, true), $this->extra_types)); } return ($nested ? '(' : '') . $this->param_name diff --git a/src/Psalm/Type/Atomic/TTypeAlias.php b/src/Psalm/Type/Atomic/TTypeAlias.php index 09e7987f968..8e70ff71951 100644 --- a/src/Psalm/Type/Atomic/TTypeAlias.php +++ b/src/Psalm/Type/Atomic/TTypeAlias.php @@ -37,7 +37,7 @@ public function getId(bool $exact = true, bool $nested = false): string return $this->getKey() . '&' . implode( '&', array_map( - fn($type) => $type->getId($exact, true), + static fn(Atomic $type): string => $type->getId($exact, true), $this->extra_types ) ); diff --git a/src/Psalm/Type/Union.php b/src/Psalm/Type/Union.php index 0eedd9bf404..1adc2717c2e 100644 --- a/src/Psalm/Type/Union.php +++ b/src/Psalm/Type/Union.php @@ -598,7 +598,7 @@ public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool return !array_filter( $types, - fn($atomic_type) => !$atomic_type->canBeFullyExpressedInPhp($analysis_php_version_id) + static fn($atomic_type): bool => !$atomic_type->canBeFullyExpressedInPhp($analysis_php_version_id) ); } @@ -688,7 +688,7 @@ public function isTemplatedClassString(): bool && count( array_filter( $this->types, - fn($type): bool => $type instanceof TTemplateParamClass + static fn($type): bool => $type instanceof TTemplateParamClass ) ) === 1; } @@ -697,7 +697,7 @@ public function hasArrayAccessInterface(Codebase $codebase): bool { return (bool)array_filter( $this->types, - fn($type) => $type->hasArrayAccessInterface($codebase) + static fn($type): bool => $type->hasArrayAccessInterface($codebase) ); } @@ -713,7 +713,7 @@ public function getCallableTypes(): array { return array_filter( $this->types, - fn($type): bool => $type instanceof TCallable + static fn($type): bool => $type instanceof TCallable ); } @@ -724,7 +724,7 @@ public function getClosureTypes(): array { return array_filter( $this->types, - fn($type): bool => $type instanceof TClosure + static fn($type): bool => $type instanceof TClosure ); } @@ -854,7 +854,7 @@ public function hasLiteralClassString(): bool public function hasInt(): bool { return isset($this->types['int']) || isset($this->types['array-key']) || $this->literal_int_types - || array_filter($this->types, fn(Atomic $type) => $type instanceof TIntRange); + || array_filter($this->types, static fn(Atomic $type): bool => $type instanceof TIntRange); } public function hasArrayKey(): bool @@ -899,12 +899,12 @@ public function hasTemplate(): bool { return (bool) array_filter( $this->types, - fn(Atomic $type): bool => $type instanceof TTemplateParam + static fn(Atomic $type): bool => $type instanceof TTemplateParam || ($type instanceof TNamedObject && $type->extra_types && array_filter( $type->extra_types, - fn($t): bool => $t instanceof TTemplateParam + static fn($t): bool => $t instanceof TTemplateParam ) ) ); @@ -914,7 +914,7 @@ public function hasConditional(): bool { return (bool) array_filter( $this->types, - fn(Atomic $type): bool => $type instanceof TConditional + static fn(Atomic $type): bool => $type instanceof TConditional ); } @@ -922,13 +922,13 @@ public function hasTemplateOrStatic(): bool { return (bool) array_filter( $this->types, - fn(Atomic $type): bool => $type instanceof TTemplateParam + static fn(Atomic $type): bool => $type instanceof TTemplateParam || ($type instanceof TNamedObject && ($type->is_static || ($type->extra_types && array_filter( $type->extra_types, - fn($t): bool => $t instanceof TTemplateParam + static fn($t): bool => $t instanceof TTemplateParam ) ) ) @@ -1161,7 +1161,7 @@ public function isInt(bool $check_templates = false): bool return count( array_filter( $this->types, - fn($type): bool => $type instanceof TInt + static fn($type): bool => $type instanceof TInt || ($check_templates && $type instanceof TTemplateParam && $type->as->isInt() @@ -1190,7 +1190,7 @@ public function isString(bool $check_templates = false): bool return count( array_filter( $this->types, - fn($type): bool => $type instanceof TString + static fn($type): bool => $type instanceof TString || ($check_templates && $type instanceof TTemplateParam && $type->as->isString()