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

Made most of callbacks static. Reworked some array_map() into foreach()

This commit is contained in:
Aleksandr Zhuravlev 2022-04-09 21:58:26 +12:00
parent c8cc3f4607
commit b4fdc3e326
89 changed files with 672 additions and 753 deletions

View File

@ -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

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="dev-master@485e516088a22efd18e902926a2efdad1b040d72">
<files psalm-version="dev-master@f7fc28bf5c23969ef1d9c8fd26f020ade0a2db9e">
<file src="examples/TemplateChecker.php">
<PossiblyUndefinedIntArrayOffset occurrences="2">
<code>$comment_block-&gt;tags['variablesfrom'][0]</code>
@ -17,9 +17,6 @@
<code>$matches[0]</code>
<code>$symbol_parts[1]</code>
</PossiblyUndefinedIntArrayOffset>
<PossiblyUnusedProperty occurrences="1">
<code>$analysis_php_version_id</code>
</PossiblyUnusedProperty>
</file>
<file src="src/Psalm/Config/FileFilter.php">
<PossiblyUndefinedIntArrayOffset occurrences="1">
@ -70,11 +67,6 @@
<code>$traverser-&gt;traverse([$switch_condition])[0]</code>
</PossiblyUndefinedIntArrayOffset>
</file>
<file src="src/Psalm/Internal/Analyzer/Statements/Block/TryAnalyzer.php">
<InvalidPropertyAssignmentValue occurrences="1">
<code>$catch_context-&gt;assigned_var_ids += $old_catch_assigned_var_ids</code>
</InvalidPropertyAssignmentValue>
</file>
<file src="src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php">
<PossiblyUndefinedIntArrayOffset occurrences="34">
<code>$assertion-&gt;rule[0]</code>
@ -308,7 +300,7 @@
</PossiblyUndefinedIntArrayOffset>
</file>
<file src="src/Psalm/Internal/Type/TypeParser.php">
<PossiblyUndefinedIntArrayOffset occurrences="9">
<PossiblyUndefinedIntArrayOffset occurrences="8">
<code>$intersection_types[0]</code>
<code>$parse_tree-&gt;children[0]</code>
<code>$parse_tree-&gt;condition-&gt;children[0]</code>
@ -316,15 +308,9 @@
<code>array_keys($template_type_map[$array_param_name])[0]</code>
<code>array_keys($template_type_map[$class_name])[0]</code>
<code>array_keys($template_type_map[$fq_classlike_name])[0]</code>
<code>array_keys($template_type_map[$param_name])[0]</code>
<code>array_keys($template_type_map[$template_param_name])[0]</code>
</PossiblyUndefinedIntArrayOffset>
</file>
<file src="src/Psalm/Issue/MethodSignatureMustProvideReturnType.php">
<UnusedClass occurrences="1">
<code>MethodSignatureMustProvideReturnType</code>
</UnusedClass>
</file>
<file src="src/Psalm/Node/Stmt/VirtualClass.php">
<PropertyNotSetInConstructor occurrences="1">
<code>VirtualClass</code>

View File

@ -49,7 +49,6 @@ use XdgBaseDir\Xdg;
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<int, class-string>
* @var array<int, lowercase-string>
*/
protected $universal_object_crates = [
stdClass::class,
SimpleXMLElement::class,
SimpleXMLIterator::class,
];
protected $universal_object_crates;
/**
* @var static|null
@ -619,6 +614,11 @@ class Config
{
self::$instance = $this;
$this->eventDispatcher = new EventDispatcher();
$this->universal_object_crates = [
strtolower(stdClass::class),
strtolower(SimpleXMLElement::class),
strtolower(SimpleXMLIterator::class),
];
}
/**
@ -2227,7 +2227,7 @@ class Config
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 @@ class Config
$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 @@ class Config
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 @@ class Config
*/
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;
}
}

View File

@ -141,7 +141,7 @@ final class Creator
// 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) {

View File

@ -434,7 +434,7 @@ class FileFilter
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;

View File

@ -161,10 +161,10 @@ final class IssueHandler
{
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'

View File

@ -48,7 +48,7 @@ final class ErrorBaseline
/**
* @param array{o:int, s:array<int, string>} $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 @@ final class ErrorBaseline
*
* @return array<string,array<string,array{o:int, s:array<int, string>}>>
*/
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 @@ final class ErrorBaseline
('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 @@ final class ErrorBaseline
/**
* @param string[] $matches
*/
fn(array $matches): string => '<files' .
static fn(array $matches): string => '<files' .
"\n " .
$matches[1] .
"\n" .

View File

@ -10,10 +10,10 @@ use UnexpectedValueException;
use function array_filter;
use function array_intersect_key;
use function array_keys;
use function array_map;
use function array_merge;
use function array_pop;
use function array_values;
use function assert;
use function count;
use function in_array;
use function mt_rand;
@ -33,39 +33,36 @@ class Algebra
*/
public static function negateTypes(array $all_types): array
{
return array_filter(
array_map(
/**
* @param non-empty-list<non-empty-list<Assertion>> $anded_types
*
* @return list<non-empty-list<Assertion>>
*/
function (array $anded_types): array {
if (count($anded_types) > 1) {
$new_anded_types = [];
$negated_types = [];
foreach ($anded_types as $orred_types) {
if (count($orred_types) > 1) {
return [];
}
foreach ($all_types as $key => $anded_types) {
if (count($anded_types) > 1) {
$new_anded_types = [];
$new_anded_types[] = $orred_types[0]->getNegation();
}
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 @@ class Algebra
{
$clauses = array_filter(
$clauses,
fn($clause) => $clause->reconcilable
static fn(Clause $clause): bool => $clause->reconcilable
);
if (!$clauses) {

View File

@ -894,7 +894,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
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 @@ class ClassAnalyzer extends ClassLikeAnalyzer
$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 @@ class ClassAnalyzer extends ClassLikeAnalyzer
);
$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 @@ class ClassAnalyzer extends ClassLikeAnalyzer
}
$overridden_method_ids = array_map(
fn($method_id) => $method_id->__toString(),
static fn($method_id): string => $method_id->__toString(),
$overridden_method_ids
);

View File

@ -16,7 +16,6 @@ use Psalm\Type;
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 @@ class ClosureAnalyzer extends FunctionLikeAnalyzer
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)) {

View File

@ -127,9 +127,7 @@ class MethodComparator
&& !$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(

View File

@ -522,7 +522,7 @@ class ProjectAnalyzer
$reader = new ProtocolStreamReader($socket);
$reader->on(
'close',
function (): void {
static function (): void {
fwrite(STDOUT, "Connection closed\n");
}
);
@ -953,7 +953,7 @@ class ProjectAnalyzer
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 @@ class ProjectAnalyzer
{
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);
},

View File

@ -124,7 +124,7 @@ class ScopeAnalyzer
$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 @@ class ScopeAnalyzer
&& $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 @@ class ScopeAnalyzer
$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 @@ class ScopeAnalyzer
$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 @@ class ScopeAnalyzer
$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 @@ class ScopeAnalyzer
$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 @@ class ScopeAnalyzer
$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 @@ class ScopeAnalyzer
$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 @@ class ScopeAnalyzer
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 @@ class ScopeAnalyzer
$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
);
}
}

View File

@ -72,7 +72,7 @@ class DoAnalyzer
$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);

View File

@ -1011,7 +1011,7 @@ class ForeachAnalyzer
: array_values(
array_map(
/** @param array<string, Union> $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
)
);

View File

@ -18,12 +18,10 @@ use Psalm\Issue\RedundantConditionGivenDocblockType;
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 @@ class IfConditionalAnalyzer
$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 @@ class IfConditionalAnalyzer
$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);

View File

@ -27,7 +27,6 @@ use function array_diff_key;
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 @@ class ElseIfAnalyzer
$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 @@ class ElseIfAnalyzer
$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);
$elseif_clauses = $elseif_clauses_handled;
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);
}
$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 @@ class ElseIfAnalyzer
$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 @@ class ElseIfAnalyzer
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 @@ class ElseIfAnalyzer
* @param array<string> $carry
* @return array<string>
*/
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)),
[]
);

View File

@ -75,7 +75,7 @@ class IfAnalyzer
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 @@ class IfAnalyzer
* @param array<string> $carry
* @return array<string>
*/
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 @@ class IfAnalyzer
);
$old_if_context = clone $if_context;
$codebase = $statements_analyzer->getCodebase();
$assigned_var_ids = $if_context->assigned_var_ids;

View File

@ -28,7 +28,6 @@ use function array_diff;
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 @@ class IfElseAnalyzer
$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 @@ class IfElseAnalyzer
$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)
)
);

View File

@ -265,7 +265,7 @@ class TryAnalyzer
$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', '>=')

View File

@ -780,12 +780,12 @@ class AssignmentAnalyzer
$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 = [];

View File

@ -11,6 +11,7 @@ use Psalm\Internal\Analyzer\Statements\Block\IfConditionalAnalyzer;
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 @@ class AndAnalyzer
$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 @@ class AndAnalyzer
$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]
)
);

View File

@ -15,6 +15,7 @@ use Psalm\Internal\Analyzer\Statements\Block\IfElseAnalyzer;
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 Psalm\Type\Reconciler;
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 @@ class OrAnalyzer
$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 @@ class OrAnalyzer
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;

View File

@ -578,8 +578,8 @@ class ArgumentsAnalyzer
$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
),

View File

@ -147,7 +147,7 @@ class ArrayFunctionArgumentsAnalyzer
$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) {

View File

@ -369,7 +369,7 @@ class FunctionCallAnalyzer extends CallAnalyzer
$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 @@ class FunctionCallAnalyzer extends CallAnalyzer
$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 @@ class FunctionCallAnalyzer extends CallAnalyzer
$context->references_in_scope,
$changed_var_ids,
array_map(
fn($_): bool => true,
static fn($_): bool => true,
$assert_type_assertions
),
$statements_analyzer,

View File

@ -421,7 +421,7 @@ class ExistingAtomicMethodCallAnalyzer extends CallAnalyzer
$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 @@ class ExistingAtomicMethodCallAnalyzer extends CallAnalyzer
$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

View File

@ -329,12 +329,12 @@ class MethodCallReturnTypeFetcher
$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(

View File

@ -201,10 +201,7 @@ class MissingMethodCallHandler
$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,

View File

@ -228,7 +228,7 @@ class MethodCallAnalyzer extends CallAnalyzer
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)
);
}

View File

@ -391,7 +391,7 @@ class NamedFunctionCallHandler
foreach ($anded_assertions as $assertions) {
$referenced_var_ids = array_map(
fn(array $_): bool => true,
static fn(array $_): bool => true,
$assertions
);

View File

@ -33,6 +33,7 @@ use Psalm\Issue\UndefinedClass;
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 @@ class NewAnalyzer extends CallAnalyzer
$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 @@ class NewAnalyzer extends CallAnalyzer
$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 @@ class NewAnalyzer extends CallAnalyzer
$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 @@ class NewAnalyzer extends CallAnalyzer
$fq_class_name,
array_values(
array_map(
fn($map) => clone reset($map),
static fn($map) => clone reset($map),
$storage->template_types
)
)

View File

@ -445,7 +445,7 @@ class AtomicStaticCallAnalyzer
$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 @@ class AtomicStaticCallAnalyzer
}
$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,

View File

@ -333,7 +333,7 @@ class ExistingAtomicStaticCallAnalyzer
$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 @@ class ExistingAtomicStaticCallAnalyzer
$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
)

View File

@ -1058,7 +1058,7 @@ class CallAnalyzer
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 @@ class CallAnalyzer
$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
)
);

View File

@ -778,7 +778,8 @@ class ArrayFetchAnalyzer
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 @@ class ArrayFetchAnalyzer
$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
)
);

View File

@ -23,6 +23,7 @@ use Psalm\Node\Expr\VirtualVariable;
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 @@ class MatchAnalyzer
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 @@ class MatchAnalyzer
}
$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
);

View File

@ -98,10 +98,7 @@ class TernaryAnalyzer
}
$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 @@ class TernaryAnalyzer
$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)
)
);

View File

@ -11,8 +11,6 @@ use Psalm\Type\Atomic\TLiteralString;
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 @@ class Clause
*/
public function __toString(): string
{
$clause_strings = array_map(
/**
* @param non-empty-array<string, Assertion> $values
*/
function (string $var_id, array $values): string {
if ($var_id[0] === '*') {
$var_id = '<expr>';
$clause_strings = [];
foreach ($this->possibilities as $var_id => $values) {
if ($var_id[0] === '*') {
$var_id = '<expr>';
}
$var_id_clauses = [];
foreach ($values as $value) {
$value = (string) $value;
if ($value === 'falsy') {
$var_id_clauses[] = '!'.$var_id;
continue;
}
$var_id_clauses = array_map(
function (Assertion $value) use ($var_id): string {
$value = (string) $value;
if ($value === 'falsy') {
return '!' . $var_id;
}
if ($value === '!falsy') {
return $var_id;
}
$negate = false;
if ($value[0] === '!') {
$negate = true;
$value = substr($value, 1);
}
if ($value[0] === '=') {
$value = substr($value, 1);
}
if ($negate) {
return $var_id . ' is not ' . $value;
}
return $var_id . ' is ' . $value;
},
$values
);
if (count($var_id_clauses) > 1) {
return '(' . implode(') || (', $var_id_clauses) . ')';
if ($value === '!falsy') {
$var_id_clauses[] = $var_id;
continue;
}
return reset($var_id_clauses);
},
array_keys($this->possibilities),
array_values($this->possibilities)
);
$negate = false;
if ($value[0] === '!') {
$negate = true;
$value = substr($value, 1);
}
if ($value[0] === '=') {
$value = substr($value, 1);
}
if ($negate) {
$var_id_clauses[] = $var_id.' is not '.$value;
continue;
}
$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) . ')';

View File

@ -94,7 +94,7 @@ final class LanguageServer
}
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 @@ final class LanguageServer
$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)
);

View File

@ -217,7 +217,7 @@ final class Psalm
$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 @@ final class Psalm
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 @@ final class Psalm
$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 @@ final class Psalm
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");

View File

@ -207,7 +207,7 @@ final class Psalter
$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 @@ final class Psalter
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 @@ final class Psalter
$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 @@ final class Psalter
},
array_filter(
explode("\n", $codeowners_file),
function (string $line): bool {
static function (string $line): bool {
$line = trim($line);
// currently we dont match wildcard files or files that could appear anywhere

View File

@ -83,7 +83,7 @@ final class Refactor
$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 @@ final class Refactor
$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)
);

View File

@ -2,6 +2,7 @@
namespace Psalm\Internal\Codebase;
use Closure;
use InvalidArgumentException;
use PhpParser;
use Psalm\CodeLocation;
@ -283,7 +284,7 @@ class Analyzer
$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 @@ class Analyzer
$codebase = $project_analyzer->getCodebase();
$filetype_analyzers = $this->config->getFiletypeAnalyzers();
$analysis_worker = Closure::fromCallable([$this, 'analysisWorker']);
$analysis_worker =
/**
* @return list<IssueData>
*/
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<IssueData> $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 @@ class Analyzer
$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 @@ class Analyzer
$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 @@ class Analyzer
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 @@ class Analyzer
return isset($this->analyzed_methods[$file_path][$method_id]);
}
/**
* @param list<IssueData> $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<IssueData>
*/
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
}
}

View File

@ -1603,7 +1603,7 @@ class ClassLikes
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 @@ class ClassLikes
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 @@ class ClassLikes
return array_filter(
$storage->constants,
fn($constant) => $constant->type !== null
static fn(ClassConstantStorage $constant): bool => $constant->type !== null
);
}

View File

@ -11,6 +11,7 @@ use Psalm\Internal\Provider\FileStorageProvider;
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 @@ class Populator
$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 @@ class Populator
$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
);

View File

@ -432,7 +432,7 @@ class Reflection
$type = implode(
'|',
array_map(
fn(ReflectionNamedType $reflection) => $reflection->getName(),
static fn(ReflectionNamedType $reflection): string => $reflection->getName(),
$reflection_type->getTypes()
)
);

View File

@ -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 @@ class Scanner
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 @@ class Scanner
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 @@ class Scanner
$this->progress->debug('Have initialised forked process for scanning' . PHP_EOL);
},
$scanner_worker,
Closure::fromCallable([$this, 'scanAPath']),
/**
* @return PoolData
*/
@ -454,7 +448,7 @@ class Scanner
$i = 0;
foreach ($files_to_scan as $file_path => $_) {
$scanner_worker($i, $file_path);
$this->scanAPath($i, $file_path);
++$i;
}
}
@ -806,4 +800,13 @@ class Scanner
{
$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])
);
}
}

View File

@ -521,7 +521,12 @@ class TaintFlowGraph extends DataFlowGraph
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]);
}
}

View File

@ -35,7 +35,7 @@ class ClassStatementsDiffer extends AstDiffer
$diff_map = [];
[$trace, $x, $y, $bc] = self::calculateTrace(
function (
static function (
PhpParser\Node\Stmt $a,
PhpParser\Node\Stmt $b,
string $a_code,

View File

@ -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,

View File

@ -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,

View File

@ -39,7 +39,7 @@ class PsalmRestarter extends XdebugHandler
{
$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;

View File

@ -64,7 +64,7 @@ class TextDocument
/**
* @return Generator<int, Promise<object>, object, TextDocumentItem>
*/
function () use ($textDocument) {
static function () use ($textDocument) {
/** @var Promise<object> */
$promise = $this->handler->request(
'textDocument/xcontent',

View File

@ -58,7 +58,7 @@ class ClientHandler
/**
* @return Generator<int, Promise, mixed, Promise<mixed>>
*/
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 @@ class ClientHandler
$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

View File

@ -217,7 +217,7 @@ class LanguageServer extends Dispatcher
): Promise {
return call(
/** @return Generator<int, true, mixed, InitializeResult> */
function () {
function (): Generator {
$this->verboseLog("Initializing...");
$this->clientStatus('initializing');
@ -373,63 +373,62 @@ class LanguageServer extends Dispatcher
$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;
$diagnostics = [];
$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'
);
foreach (($data[$file_path] ?? []) as $issue_data) {
//$check_name = $issue->check_name;
$description = $issue_data->message;
$severity = $issue_data->severity;
//$code = 'PS' . \str_pad((string) $issue_data->shortcode, 3, "0", \STR_PAD_LEFT);
$code = $issue_data->link;
$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'
);
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
//$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
/** @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);
}

View File

@ -4,12 +4,9 @@ declare(strict_types=1);
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;

View File

@ -64,8 +64,6 @@ use Psalm\Type\Union;
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 @@ class ClassLikeNodeScanner
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 @@ class ClassLikeNodeScanner
}
}
$converted_aliases = array_map(
function (InlineTypeAlias $t): ?ClassTypeAlias {
try {
$union = TypeParser::parseTokens(
$t->replacement_tokens,
null,
[],
$this->type_aliases
);
$converted_aliases = [];
foreach ($this->classlike_type_aliases as $key => $type) {
try {
$union = TypeParser::parseTokens(
$type->replacement_tokens,
null,
[],
$this->type_aliases
);
$union->setFromDocblock();
$union->setFromDocblock();
return new ClassTypeAlias(
array_values($union->getAtomicTypes())
);
} catch (Exception $e) {
return null;
}
},
$this->classlike_type_aliases
);
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 @@ class ClassLikeNodeScanner
}
}
$classlike_storage->type_aliases = array_filter($converted_aliases);
$classlike_storage->type_aliases = $converted_aliases;
return $classlike_storage;
}

View File

@ -344,7 +344,7 @@ class ExpressionResolver
)
) {
$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;
}

View File

@ -46,7 +46,6 @@ use Psalm\Type\TaintKindGroup;
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 @@ class FunctionLikeDocblockScanner
$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 @@ class FunctionLikeDocblockScanner
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 @@ class FunctionLikeDocblockScanner
$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) {

View File

@ -59,7 +59,7 @@ class ShowCommand extends Command
/**
* @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)) {

View File

@ -138,7 +138,7 @@ class FileProvider
$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 {

View File

@ -186,7 +186,7 @@ class FileReferenceProvider
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)
);
}

View File

@ -195,10 +195,7 @@ class ArrayMapReturnTypeProvider implements FunctionReturnTypeProviderInterface
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
)
);

View File

@ -109,14 +109,14 @@ class MinMaxReturnTypeProvider implements FunctionReturnTypeProviderInterface
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);

View File

@ -218,7 +218,7 @@ class StatementsProvider
$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;
}

View File

@ -104,7 +104,7 @@ class PhpStormMetaScanner
/**
* @param list<PhpParser\Node\Arg> $call_args
*/
function (
static function (
MethodReturnTypeProviderEvent $event
) use (
$map,
@ -160,7 +160,7 @@ class PhpStormMetaScanner
/**
* @param list<PhpParser\Node\Arg> $call_args
*/
function (
static function (
MethodReturnTypeProviderEvent $event
) use (
$type_offset,
@ -197,7 +197,7 @@ class PhpStormMetaScanner
/**
* @param list<PhpParser\Node\Arg> $call_args
*/
function (
static function (
MethodReturnTypeProviderEvent $event
) use (
$element_type_offset,
@ -262,7 +262,7 @@ class PhpStormMetaScanner
* @param non-empty-string $function_id
* @param list<PhpParser\Node\Arg> $call_args
*/
function (
static function (
FunctionReturnTypeProviderEvent $event
) use (
$map,
@ -316,7 +316,7 @@ class PhpStormMetaScanner
* @param non-empty-string $function_id
* @param list<PhpParser\Node\Arg> $call_args
*/
function (
static function (
FunctionReturnTypeProviderEvent $event
) use (
$type_offset
@ -350,7 +350,7 @@ class PhpStormMetaScanner
* @param non-empty-string $function_id
* @param list<PhpParser\Node\Arg> $call_args
*/
function (
static function (
FunctionReturnTypeProviderEvent $event
) use (
$element_type_offset

View File

@ -192,7 +192,7 @@ class ArrayTypeComparator
// 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)
);

View File

@ -4,8 +4,6 @@ namespace Psalm\Internal\Type;
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)];
}
}
}
}

View File

@ -1116,7 +1116,7 @@ class TemplateStandinTypeReplacer
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;

View File

@ -743,7 +743,7 @@ class TypeCombiner
$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 @@ class TypeCombiner
$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 @@ class TypeCombiner
) {
$combination->objectlike_entries = array_filter(
$combination->objectlike_entries,
fn(Union $type): bool => !$type->possibly_undefined
static fn(Union $type): bool => !$type->possibly_undefined
);
}

View File

@ -264,7 +264,7 @@ class TypeExpander
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 @@ class TypeExpander
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
)
)

View File

@ -499,7 +499,7 @@ class TypeParser
$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 @@ class TypeParser
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 = [];
foreach ($parse_tree->children as $name => $child_tree) {
$atomic_type = self::getTypeFromTree(
$child_tree,
$codebase,
null,
$template_type_map,
$type_aliases
);
if (!$atomic_type instanceof Atomic) {
throw new TypeParseTreeException(
'Intersection types cannot contain unions'
);
}
if (!$atomic_type instanceof Atomic) {
throw new TypeParseTreeException(
'Intersection types cannot contain unions'
);
}
return $atomic_type;
},
$parse_tree->children
);
$intersection_types[$name] = $atomic_type;
}
$first_type = reset($intersection_types);
$last_type = end($intersection_types);
@ -1143,67 +1142,58 @@ class TypeParser
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;
$params = [];
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();
}
$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,
$tree_type = self::getTypeFromTree(
$child_tree,
$codebase,
null,
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)) {

View File

@ -569,7 +569,7 @@ final class IssueBuffer
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) {

View File

@ -90,7 +90,7 @@ abstract class Report
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;

View File

@ -27,37 +27,7 @@ final class CodeClimateReport extends Report
$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 @@ final class CodeClimateReport extends Report
{
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,
],
],
];
}
}

View File

@ -2,6 +2,7 @@
namespace Psalm\Report;
use Psalm\Internal\Analyzer\IssueData;
use Psalm\Internal\Json\Json;
use Psalm\Report;
@ -15,7 +16,7 @@ final class JsonReport extends Report
$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;

View File

@ -18,20 +18,20 @@ final class XmlReport extends Report
'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']
);
}

View File

@ -248,7 +248,7 @@ abstract class FunctionLikeStorage implements HasAttributesInterface
. implode(
',' . ($newlines ? "\n" : ' '),
array_map(
fn(FunctionLikeParameter $param): string =>
static fn(FunctionLikeParameter $param): string =>
($newlines ? ' ' : '')
. ($param->type ? $param->type->getId(false) : 'mixed')
. ' $' . $param->name,

View File

@ -353,7 +353,7 @@ abstract class Atomic implements TypeNode
&& ($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 @@ abstract class Atomic implements TypeNode
$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 @@ abstract class Atomic implements TypeNode
$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 @@ abstract class Atomic implements TypeNode
$this->extra_types
&& array_filter(
$this->extra_types,
fn(Atomic $a): bool => $a->hasArrayAccessInterface($codebase)
static fn(Atomic $a): bool => $a->hasArrayAccessInterface($codebase)
)
)
);

View File

@ -12,7 +12,6 @@ use Psalm\Type\Atomic;
use Psalm\Type\TypeNode;
use Psalm\Type\Union;
use function array_map;
use function count;
use function implode;
@ -110,29 +109,19 @@ trait CallableTrait
$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
);
}
$params_array = [];
return ($param->is_variadic ? '...' : '') . $type_string . ($param->is_optional ? '=' : '');
},
$this->params
)
) . ')';
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) {

View File

@ -35,7 +35,7 @@ trait GenericTrait
$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 @@ trait GenericTrait
$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 @@ trait GenericTrait
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
)

View File

@ -35,10 +35,8 @@ trait HasIntersectionTrait
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,

View File

@ -20,8 +20,6 @@ use Psalm\Type\Union;
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 @@ class TKeyedArray extends Atomic
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);
}
$property_strings = [];
$class_string_suffix = '';
if (isset($this->class_strings[$name])) {
$class_string_suffix = '::class';
}
foreach ($this->properties as $name => $type) {
if ($this->is_list && $this->sealed) {
$property_strings[$name] = $type->getId($exact);
continue;
}
$name = $this->escapeAndQuote($name);
$class_string_suffix = '';
if (isset($this->class_strings[$name])) {
$class_string_suffix = '::class';
}
return $name . $class_string_suffix . ($type->possibly_undefined ? '?' : '')
. ': ' . $type->getId($exact);
},
array_keys($this->properties),
$this->properties
);
$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 @@ class TKeyedArray extends Atomic
);
}
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';
}
$suffixed_properties = [];
$name = $this->escapeAndQuote($name);
foreach ($this->properties as $name => $type) {
$class_string_suffix = '';
if (isset($this->class_strings[$name])) {
$class_string_suffix = '::class';
}
return $name . $class_string_suffix . ($type->possibly_undefined ? '?' : '') . ': ' .
$type->toNamespacedString(
$namespace,
$aliased_classes,
$this_class,
$use_phpdoc_format
);
},
array_keys($this->properties),
$this->properties
)
) .
'}';
$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) . '}';
}
/**

View File

@ -64,7 +64,7 @@ class TNamedObject extends Atomic
return $this->value . '&' . implode(
'&',
array_map(
fn($type) => $type->getId($exact, true),
static fn(Atomic $type): string => $type->getId($exact, true),
$this->extra_types
)
);

View File

@ -60,7 +60,7 @@ final class TObjectWithProperties extends TObject
/**
* @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 @@ final class TObjectWithProperties extends TObject
$methods_string = implode(
', ',
array_map(
fn(string $name): string => $name . '()',
static fn(string $name): string => $name . '()',
array_keys($this->methods)
)
);
@ -102,7 +102,7 @@ final class TObjectWithProperties extends TObject
/**
* @param string|int $name
*/
fn($name, Union $type): string =>
static fn($name, Union $type): string =>
$name .
($type->possibly_undefined ? '?' : '')
. ':'

View File

@ -61,7 +61,8 @@ final class TTemplateParam extends Atomic
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

View File

@ -37,7 +37,7 @@ final class TTypeAlias extends Atomic
return $this->getKey() . '&' . implode(
'&',
array_map(
fn($type) => $type->getId($exact, true),
static fn(Atomic $type): string => $type->getId($exact, true),
$this->extra_types
)
);

View File

@ -598,7 +598,7 @@ final class Union implements TypeNode
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 @@ final class Union implements TypeNode
&& count(
array_filter(
$this->types,
fn($type): bool => $type instanceof TTemplateParamClass
static fn($type): bool => $type instanceof TTemplateParamClass
)
) === 1;
}
@ -697,7 +697,7 @@ final class Union implements TypeNode
{
return (bool)array_filter(
$this->types,
fn($type) => $type->hasArrayAccessInterface($codebase)
static fn($type): bool => $type->hasArrayAccessInterface($codebase)
);
}
@ -713,7 +713,7 @@ final class Union implements TypeNode
{
return array_filter(
$this->types,
fn($type): bool => $type instanceof TCallable
static fn($type): bool => $type instanceof TCallable
);
}
@ -724,7 +724,7 @@ final class Union implements TypeNode
{
return array_filter(
$this->types,
fn($type): bool => $type instanceof TClosure
static fn($type): bool => $type instanceof TClosure
);
}
@ -854,7 +854,7 @@ final class Union implements TypeNode
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 @@ final class Union implements TypeNode
{
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 @@ final class Union implements TypeNode
{
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 @@ final class Union implements TypeNode
{
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 @@ final class Union implements TypeNode
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 @@ final class Union implements TypeNode
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()