Skip to content

Commit

Permalink
Merge tag '5.25.0' into fix-core-function-flow
Browse files Browse the repository at this point in the history
  • Loading branch information
mmcev106 committed Jul 4, 2024
2 parents 4c159f3 + 01a8eb0 commit 31c40f7
Show file tree
Hide file tree
Showing 17 changed files with 194 additions and 35 deletions.
8 changes: 4 additions & 4 deletions dictionaries/CallMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -6446,7 +6446,7 @@
'litespeed_request_headers' => ['array'],
'litespeed_response_headers' => ['array'],
'Locale::acceptFromHttp' => ['string|false', 'header'=>'string'],
'Locale::canonicalize' => ['string', 'locale'=>'string'],
'Locale::canonicalize' => ['?string', 'locale'=>'string'],
'Locale::composeLocale' => ['string', 'subtags'=>'array'],
'Locale::filterMatches' => ['?bool', 'languageTag'=>'string', 'locale'=>'string', 'canonicalize='=>'bool'],
'Locale::getAllVariants' => ['array', 'locale'=>'string'],
Expand Down Expand Up @@ -9418,9 +9418,9 @@
'preg_filter' => ['string|string[]|null', 'pattern'=>'string|string[]', 'replacement'=>'string|string[]', 'subject'=>'string|string[]', 'limit='=>'int', '&w_count='=>'int'],
'preg_grep' => ['array|false', 'pattern'=>'string', 'array'=>'array', 'flags='=>'int'],
'preg_last_error' => ['int'],
'preg_match' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'string[]', 'flags='=>'0', 'offset='=>'int'],
'preg_match\'1' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'],
'preg_match_all' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'],
'preg_match' => ['0|1|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'string[]', 'flags='=>'0', 'offset='=>'int'],
'preg_match\'1' => ['0|1|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'],
'preg_match_all' => ['int<0,max>|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'],
'preg_quote' => ['string', 'str'=>'string', 'delimiter='=>'?string'],
'preg_replace' => ['string|string[]|null', 'pattern'=>'string|array', 'replacement'=>'string|array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'],
'preg_replace_callback' => ['string|null', 'pattern'=>'string|array', 'callback'=>'callable(string[]):string', 'subject'=>'string', 'limit='=>'int', '&w_count='=>'int', 'flags='=>'int'],
Expand Down
8 changes: 4 additions & 4 deletions dictionaries/CallMap_historical.php
Original file line number Diff line number Diff line change
Expand Up @@ -3393,7 +3393,7 @@
'LimitIterator::seek' => ['int', 'offset'=>'int'],
'LimitIterator::valid' => ['bool'],
'Locale::acceptFromHttp' => ['string|false', 'header'=>'string'],
'Locale::canonicalize' => ['string', 'locale'=>'string'],
'Locale::canonicalize' => ['?string', 'locale'=>'string'],
'Locale::composeLocale' => ['string', 'subtags'=>'array'],
'Locale::filterMatches' => ['?bool', 'languageTag'=>'string', 'locale'=>'string', 'canonicalize='=>'bool'],
'Locale::getAllVariants' => ['array', 'locale'=>'string'],
Expand Down Expand Up @@ -13499,9 +13499,9 @@
'preg_filter' => ['string|string[]|null', 'pattern'=>'string|string[]', 'replacement'=>'string|string[]', 'subject'=>'string|string[]', 'limit='=>'int', '&w_count='=>'int'],
'preg_grep' => ['array|false', 'pattern'=>'string', 'array'=>'array', 'flags='=>'int'],
'preg_last_error' => ['int'],
'preg_match' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'string[]', 'flags='=>'0', 'offset='=>'int'],
'preg_match\'1' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'],
'preg_match_all' => ['int|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'],
'preg_match' => ['0|1|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'string[]', 'flags='=>'0', 'offset='=>'int'],
'preg_match\'1' => ['0|1|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'],
'preg_match_all' => ['int<0,max>|false', 'pattern'=>'string', 'subject'=>'string', '&w_matches='=>'array', 'flags='=>'int', 'offset='=>'int'],
'preg_quote' => ['string', 'str'=>'string', 'delimiter='=>'string'],
'preg_replace' => ['string|string[]|null', 'pattern'=>'string|array', 'replacement'=>'string|array', 'subject'=>'string|array', 'limit='=>'int', '&w_count='=>'int'],
'preg_replace_callback' => ['string|null', 'pattern'=>'string|array', 'callback'=>'callable(string[]):string', 'subject'=>'string', 'limit='=>'int', '&w_count='=>'int'],
Expand Down
16 changes: 1 addition & 15 deletions src/Psalm/Internal/Analyzer/ProjectAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -329,21 +329,7 @@ public static function getFileReportOptions(array $report_file_paths, bool $show
{
$report_options = [];

$mapping = [
'checkstyle.xml' => Report::TYPE_CHECKSTYLE,
'sonarqube.json' => Report::TYPE_SONARQUBE,
'codeclimate.json' => Report::TYPE_CODECLIMATE,
'summary.json' => Report::TYPE_JSON_SUMMARY,
'junit.xml' => Report::TYPE_JUNIT,
'.xml' => Report::TYPE_XML,
'.json' => Report::TYPE_JSON,
'.txt' => Report::TYPE_TEXT,
'.emacs' => Report::TYPE_EMACS,
'.pylint' => Report::TYPE_PYLINT,
'.console' => Report::TYPE_CONSOLE,
'.sarif' => Report::TYPE_SARIF,
'count.txt' => Report::TYPE_COUNT,
];
$mapping = Report::getMapping();

foreach ($report_file_paths as $report_file_path) {
foreach ($mapping as $extension => $type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
use Psalm\Type\Union;
use UnexpectedValueException;

use function array_filter;
use function count;
use function explode;
use function implode;
Expand Down Expand Up @@ -955,12 +956,29 @@ public static function verifyType(
) {
$potential_method_ids = [];

$param_types_without_callable = array_filter(
$param_type->getAtomicTypes(),
static fn(Atomic $atomic) => !$atomic instanceof Atomic\TCallableInterface,
);
$param_type_without_callable = [] !== $param_types_without_callable
? new Union($param_types_without_callable)
: null;

foreach ($input_type->getAtomicTypes() as $input_type_part) {
if ($input_type_part instanceof TList) {
$input_type_part = $input_type_part->getKeyedArray();
}

if ($input_type_part instanceof TKeyedArray) {
// If the param accept an array, we don't report arrays as wrong callbacks.
if (null !== $param_type_without_callable && UnionTypeComparator::isContainedBy(
$codebase,
$input_type,
$param_type_without_callable,
)) {
continue;
}

$potential_method_id = CallableTypeComparator::getCallableMethodIdFromTKeyedArray(
$input_type_part,
$codebase,
Expand Down Expand Up @@ -1006,6 +1024,15 @@ public static function verifyType(
} elseif ($input_type_part instanceof TLiteralString
&& strpos($input_type_part->value, '::')
) {
// If the param also accept a string, we don't report string as wrong callbacks.
if (null !== $param_type_without_callable && UnionTypeComparator::isContainedBy(
$codebase,
$input_type,
$param_type_without_callable,
)) {
continue;
}

$parts = explode('::', $input_type_part->value);
/** @psalm-suppress PossiblyUndefinedIntArrayOffset */
$potential_method_id = new MethodIdentifier(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use Psalm\Type\Atomic\TFalse;
use Psalm\Type\Atomic\TFloat;
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TIntRange;
use Psalm\Type\Atomic\TKeyedArray;
use Psalm\Type\Atomic\TList;
use Psalm\Type\Atomic\TLiteralFloat;
Expand All @@ -53,6 +54,7 @@
use function array_pop;
use function array_values;
use function get_class;
use function range;
use function strtolower;

/**
Expand Down Expand Up @@ -537,6 +539,18 @@ public static function castFloatAttempt(
continue;
}

if ($atomic_type instanceof TIntRange
&& $atomic_type->min_bound !== null
&& $atomic_type->max_bound !== null
&& ($atomic_type->max_bound - $atomic_type->min_bound) < 500
) {
foreach (range($atomic_type->min_bound, $atomic_type->max_bound) as $literal_int_value) {
$valid_floats[] = new TLiteralFloat((float) $literal_int_value);
}

continue;
}

if ($atomic_type instanceof TInt) {
if ($atomic_type instanceof TLiteralInt) {
$valid_floats[] = new TLiteralFloat((float) $atomic_type->value);
Expand Down Expand Up @@ -721,9 +735,17 @@ public static function castStringAttempt(
|| $atomic_type instanceof TNumeric
) {
if ($atomic_type instanceof TLiteralInt || $atomic_type instanceof TLiteralFloat) {
$castable_types[] = Type::getAtomicStringFromLiteral((string) $atomic_type->value);
$valid_strings[] = Type::getAtomicStringFromLiteral((string) $atomic_type->value);
} elseif ($atomic_type instanceof TNonspecificLiteralInt) {
$castable_types[] = new TNonspecificLiteralString();
} elseif ($atomic_type instanceof TIntRange
&& $atomic_type->min_bound !== null
&& $atomic_type->max_bound !== null
&& ($atomic_type->max_bound - $atomic_type->min_bound) < 500
) {
foreach (range($atomic_type->min_bound, $atomic_type->max_bound) as $literal_int_value) {
$valid_strings[] = Type::getAtomicStringFromLiteral((string) $literal_int_value);
}
} else {
$castable_types[] = new TNumericString();
}
Expand Down
27 changes: 23 additions & 4 deletions src/Psalm/Internal/Cli/Psalm.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@
use Psalm\Progress\VoidProgress;
use Psalm\Report;
use Psalm\Report\ReportOptions;
use ReflectionClass;
use RuntimeException;
use Symfony\Component\Filesystem\Path;

use function array_filter;
use function array_key_exists;
use function array_keys;
use function array_map;
use function array_merge;
use function array_slice;
Expand Down Expand Up @@ -67,11 +69,13 @@
use function preg_replace;
use function realpath;
use function setlocale;
use function sort;
use function str_repeat;
use function str_replace;
use function strlen;
use function strpos;
use function substr;
use function wordwrap;

use const DIRECTORY_SEPARATOR;
use const JSON_THROW_ON_ERROR;
Expand All @@ -87,6 +91,7 @@
require_once __DIR__ . '/../Composer.php';
require_once __DIR__ . '/../IncludeCollector.php';
require_once __DIR__ . '/../../IssueBuffer.php';
require_once __DIR__ . '/../../Report.php';

/**
* @internal
Expand Down Expand Up @@ -1250,6 +1255,21 @@ private static function generateStubs(
*/
private static function getHelpText(): string
{
$formats = [];
/** @var string $value */
foreach ((new ReflectionClass(Report::class))->getConstants() as $constant => $value) {
if (strpos($constant, 'TYPE_') === 0) {
$formats[] = $value;
}
}
sort($formats);
$outputFormats = wordwrap(implode(', ', $formats), 75, "\n ");

/** @psalm-suppress ImpureMethodCall */
$reports = array_keys(Report::getMapping());
sort($reports);
$reportFormats = wordwrap('"' . implode('", "', $reports) . '"', 75, "\n ");

return <<<HELP
Usage:
psalm [options] [file...]
Expand Down Expand Up @@ -1333,8 +1353,8 @@ private static function getHelpText(): string
--output-format=console
Changes the output format.
Available formats: compact, console, text, emacs, json, pylint, xml, checkstyle, junit, sonarqube,
github, phpstorm, codeclimate, by-issue-level
Available formats:
$outputFormats
--no-progress
Disable the progress indicator
Expand All @@ -1348,8 +1368,7 @@ private static function getHelpText(): string
Reports:
--report=PATH
The path where to output report file. The output format is based on the file extension.
(Currently supported formats: ".json", ".xml", ".txt", ".emacs", ".pylint", ".console",
".sarif", "checkstyle.xml", "sonarqube.json", "codeclimate.json", "summary.json", "junit.xml")
(Currently supported formats: $reportFormats)
--report-show-info[=BOOLEAN]
Whether the report should include non-errors in its output (defaults to true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1338,10 +1338,8 @@ private function visitClassConstDeclaration(
);

$type_location = null;
$suppressed_issues = [];
if ($var_comment !== null && $var_comment->type !== null) {
if ($var_comment && $var_comment->type !== null) {
$const_type = $var_comment->type;
$suppressed_issues = $var_comment->suppressed_issues;

if ($var_comment->type_start !== null
&& $var_comment->type_end !== null
Expand All @@ -1357,6 +1355,7 @@ private function visitClassConstDeclaration(
} else {
$const_type = $inferred_type;
}
$suppressed_issues = $var_comment ? $var_comment->suppressed_issues : [];

$attributes = [];
foreach ($stmt->attrGroups as $attr_group) {
Expand Down Expand Up @@ -1420,8 +1419,8 @@ private function visitClassConstDeclaration(
$description,
);


if ($this->codebase->analysis_php_version_id >= 8_03_00
&& !$storage->final
&& $stmt->type === null
) {
IssueBuffer::maybeAdd(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,7 @@ public static function parse(

$info->variadic = isset($parsed_docblock->tags['psalm-variadic']);
$info->pure = isset($parsed_docblock->tags['psalm-pure'])
|| isset($parsed_docblock->tags['phpstan-pure'])
|| isset($parsed_docblock->tags['pure']);

if (isset($parsed_docblock->tags['psalm-mutation-free'])) {
Expand Down
9 changes: 8 additions & 1 deletion src/Psalm/Internal/Type/TypeCombiner.php
Original file line number Diff line number Diff line change
Expand Up @@ -1227,9 +1227,16 @@ private static function scrapeStringProperties(
) {
$combination->value_types['string'] = new TNonEmptyString();
} elseif (get_class($type) === TNonEmptyNonspecificLiteralString::class
&& $combination->value_types['string'] instanceof TNonEmptyString
&& (
$combination->value_types['string'] instanceof TNonEmptyString
|| $combination->value_types['string'] instanceof TNonspecificLiteralString
)
) {
// do nothing
} elseif (get_class($type) === TNonspecificLiteralString::class
&& get_class($combination->value_types['string']) === TNonEmptyNonspecificLiteralString::class
) {
$combination->value_types['string'] = $type;
} else {
$combination->value_types['string'] = new TString();
}
Expand Down
23 changes: 23 additions & 0 deletions src/Psalm/Report.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,27 @@ protected function xmlEncode(string $data): string
}

abstract public function create(): string;

/**
* @return array<string, self::TYPE_*>
*/
public static function getMapping(): array
{
return [
'checkstyle.xml' => self::TYPE_CHECKSTYLE,
'sonarqube.json' => self::TYPE_SONARQUBE,
'codeclimate.json' => self::TYPE_CODECLIMATE,
'summary.json' => self::TYPE_JSON_SUMMARY,
'junit.xml' => self::TYPE_JUNIT,
'.xml' => self::TYPE_XML,
'.sarif.json' => self::TYPE_SARIF,
'.json' => self::TYPE_JSON,
'.txt' => self::TYPE_TEXT,
'.emacs' => self::TYPE_EMACS,
'.pylint' => self::TYPE_PYLINT,
'.console' => self::TYPE_CONSOLE,
'.sarif' => self::TYPE_SARIF,
'count.txt' => self::TYPE_COUNT,
];
}
}
2 changes: 1 addition & 1 deletion stubs/CoreGenericFunctions.phpstub
Original file line number Diff line number Diff line change
Expand Up @@ -1487,7 +1487,7 @@ function preg_replace_callback($pattern, $callback, $subject, int $limit = -1, &
* )
* )
* ) $matches
* @return int|false
* @return int<0,max>|false
* @psalm-ignore-falsable-return
*/
function preg_match_all($pattern, $subject, &$matches = [], int $flags = 1, int $offset = 0) {}
Expand Down
1 change: 1 addition & 0 deletions stubs/extensions/redis.phpstub
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Redis {
/** @return false|int|Redis */
public function append(string $key, string $value) {}

/** @param array{string,string}|array{string}|string $credentials */
public function auth(mixed $credentials): bool {}

public function bgSave(): bool {}
Expand Down
28 changes: 28 additions & 0 deletions tests/CallableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2210,6 +2210,24 @@ function foo($arg): void {}
foo(["a", "b"]);',
],
'notCallableArray' => [
'code' => '<?php
/**
* @param array{class-string, string}|callable $arg
*/
function foo($arg): void {}
foo([\DateTime::class, "format"]);',
],
'notCallableString' => [
'code' => '<?php
/**
* @param string|callable $arg
*/
function foo($arg): void {}
foo("notACallable");',
],
'callableOptionalOrAdditionalOptional' => [
'code' => '<?php
/**
Expand Down Expand Up @@ -3635,6 +3653,16 @@ public function run($callable) {
}',
'error_message' => 'ParentNotFound',
],
'wrongCallableInUnion' => [
'code' => '<?php
/**
* @param int|callable $arg
*/
function foo($arg): void {}
foo([\DateTime::class, "wrongMethod"]);',
'error_message' => 'InvalidArgument',
],
];
}
}
Loading

0 comments on commit 31c40f7

Please sign in to comment.