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:
parent
c8cc3f4607
commit
b4fdc3e326
@ -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
|
||||
|
@ -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->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->traverse([$switch_condition])[0]</code>
|
||||
</PossiblyUndefinedIntArrayOffset>
|
||||
</file>
|
||||
<file src="src/Psalm/Internal/Analyzer/Statements/Block/TryAnalyzer.php">
|
||||
<InvalidPropertyAssignmentValue occurrences="1">
|
||||
<code>$catch_context->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->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->children[0]</code>
|
||||
<code>$parse_tree->condition->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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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'
|
||||
|
@ -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" .
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
);
|
||||
|
||||
|
@ -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)) {
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
},
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
)
|
||||
);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)),
|
||||
[]
|
||||
);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -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', '>=')
|
||||
|
@ -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 = [];
|
||||
|
@ -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]
|
||||
)
|
||||
);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
),
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
);
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
)
|
||||
);
|
||||
|
@ -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
|
||||
)
|
||||
);
|
||||
|
@ -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
|
||||
);
|
||||
|
@ -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)
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -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) . ')';
|
||||
|
@ -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)
|
||||
);
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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 don’t match wildcard files or files that could appear anywhere
|
||||
|
@ -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)
|
||||
);
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
);
|
||||
|
@ -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()
|
||||
)
|
||||
);
|
||||
|
@ -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])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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)) {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
);
|
||||
|
||||
|
@ -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)];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
)
|
||||
|
@ -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)) {
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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']
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -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) . '}';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
)
|
||||
);
|
||||
|
@ -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 ? '?' : '')
|
||||
. ':'
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
);
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user