diff --git a/src/Psalm/CodeLocation/ParseErrorLocation.php b/src/Psalm/CodeLocation/ParseErrorLocation.php index 1714ff7fead..a5b91201d75 100644 --- a/src/Psalm/CodeLocation/ParseErrorLocation.php +++ b/src/Psalm/CodeLocation/ParseErrorLocation.php @@ -17,9 +17,9 @@ public function __construct( string $file_path, string $file_name ) { - /** @psalm-suppress PossiblyUndefinedStringArrayOffset, ImpureMethodCall */ + /** @psalm-suppress PossiblyUndefinedStringArrayOffset */ $this->file_start = (int)$error->getAttributes()['startFilePos']; - /** @psalm-suppress PossiblyUndefinedStringArrayOffset, ImpureMethodCall */ + /** @psalm-suppress PossiblyUndefinedStringArrayOffset */ $this->file_end = (int)$error->getAttributes()['endFilePos']; $this->raw_file_start = $this->file_start; $this->raw_file_end = $this->file_end; diff --git a/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php b/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php index 02dd19ad8b5..3d23cc1dcb7 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php +++ b/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php @@ -77,7 +77,7 @@ public static function getReturnTypes( break; } - if ($stmt instanceof PhpParser\Node\Stmt\Throw_) { + if (BCHelper::isThrowStatement($stmt)) { $return_types[] = Type::getNever(); break; diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php index 54b7e7f3f01..c274bbd6095 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php @@ -691,8 +691,8 @@ private static function simplifyCaseEqualityExpression( } /** - * @param array $in_array_values - * @return ?array + * @param array $in_array_values + * @return array|null */ private static function getOptionsFromNestedOr( PhpParser\Node\Expr $case_equality_expr, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php index 22f483c1bcd..f41469e7ec3 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php @@ -201,7 +201,8 @@ public static function handleMagicMethod( $result->existent_method_ids[$method_id->__toString()] = true; $array_values = array_map( - static fn(PhpParser\Node\Arg $arg): PhpParser\Node\Expr\ArrayItem => new VirtualArrayItem( + /** @return PhpParser\Node\Expr\ArrayItem|PhpParser\Node\ArrayItem */ + static fn(PhpParser\Node\Arg $arg) => new VirtualArrayItem( $arg->value, null, false, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php index 4768f024910..2f4153e285a 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php @@ -332,7 +332,8 @@ private static function convertCondsToConditional( } $array_items = array_map( - static fn(PhpParser\Node\Expr $cond): PhpParser\Node\Expr\ArrayItem => + /** @return PhpParser\Node\Expr\ArrayItem|PhpParser\Node\ArrayItem */ + static fn(PhpParser\Node\Expr $cond) => new VirtualArrayItem($cond, null, false, $cond->getAttributes()), $conds, ); diff --git a/src/Psalm/Internal/Analyzer/Statements/ThrowAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/ThrowAnalyzer.php index 6ae148f8b9e..bd39a52d2dd 100644 --- a/src/Psalm/Internal/Analyzer/Statements/ThrowAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/ThrowAnalyzer.php @@ -19,11 +19,11 @@ final class ThrowAnalyzer { /** - * @param PhpParser\Node\Stmt\Throw_|PhpParser\Node\Expr\Throw_ $stmt + * @param PhpParser\Node\Expr\Throw_|PhpParser\Node\Stmt\Throw_ $stmt */ public static function analyze( StatementsAnalyzer $statements_analyzer, - PhpParser\Node $stmt, + $stmt, Context $context ): bool { $context->inside_throw = true; diff --git a/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php b/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php index 26b93e12fa9..a9799125d27 100644 --- a/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php @@ -36,6 +36,7 @@ use Psalm\Internal\Analyzer\Statements\ThrowAnalyzer; use Psalm\Internal\Analyzer\Statements\UnsetAnalyzer; use Psalm\Internal\Analyzer\Statements\UnusedAssignmentRemover; +use Psalm\Internal\BCHelper; use Psalm\Internal\Codebase\DataFlowGraph; use Psalm\Internal\Codebase\TaintFlowGraph; use Psalm\Internal\Codebase\VariableUseGraph; @@ -543,7 +544,7 @@ private static function analyzeStatement( UnsetAnalyzer::analyze($statements_analyzer, $stmt, $context); } elseif ($stmt instanceof PhpParser\Node\Stmt\Return_) { ReturnAnalyzer::analyze($statements_analyzer, $stmt, $context); - } elseif ($stmt instanceof PhpParser\Node\Stmt\Throw_) { + } elseif (BCHelper::isThrowStatement($stmt)) { ThrowAnalyzer::analyze($statements_analyzer, $stmt, $context); } elseif ($stmt instanceof PhpParser\Node\Stmt\Switch_) { SwitchAnalyzer::analyze($statements_analyzer, $stmt, $context); diff --git a/src/Psalm/Internal/BCHelper.php b/src/Psalm/Internal/BCHelper.php index 9cdd9df1673..51332ef8408 100644 --- a/src/Psalm/Internal/BCHelper.php +++ b/src/Psalm/Internal/BCHelper.php @@ -3,7 +3,9 @@ namespace Psalm\Internal; use PhpParser; +use PhpParser\Lexer\Emulative; use PhpParser\Node; +use PhpParser\PhpVersion; use function class_exists; @@ -12,6 +14,28 @@ */ final class BCHelper { + private const CLASS_MAP = [ + Node\Expr\ArrayItem::class => Node\ArrayItem::class, + Node\Expr\ClosureUse::class => Node\ClosureUse::class, + Node\Scalar\DNumber::class => Node\Scalar\Float_::class, + Node\Scalar\Encapsed::class => Node\Scalar\InterpolatedString::class, + Node\Scalar\EncapsedStringPart::class => Node\InterpolatedStringPart::class, + Node\Scalar\LNumber::class => Node\Scalar\Int_::class, + Node\Stmt\DeclareDeclare::class => Node\DeclareItem::class, + Node\Stmt\PropertyProperty::class => Node\PropertyItem::class, + Node\Stmt\StaticVar::class => Node\StaticVar::class, + Node\Stmt\UseUse::class => Node\UseItem::class, + ]; + + public static function getPHPParserClassName(string $className): string + { + if (isset(self::CLASS_MAP[$className]) && class_exists(self::CLASS_MAP[$className])) { + return self::CLASS_MAP[$className]; + } + + return $className; + } + public static function usePHPParserV4(): bool { return class_exists('\PhpParser\Node\Stmt\Throw'); @@ -26,4 +50,27 @@ public static function isThrow(Node $stmt): bool return $stmt instanceof PhpParser\Node\Stmt\Expression && $stmt->expr instanceof PhpParser\Node\Expr\Throw_; } + + public static function isThrowStatement(Node $node): bool + { + if (self::usePHPParserV4()) { + return $node instanceof PhpParser\Node\Stmt\Throw_; + } + + return false; + } + + public static function createEmulative(int $major_version, int $minor_version): PhpParser\Lexer\Emulative + { + if (class_exists(PhpVersion::class)) { + return new Emulative(PhpVersion::fromComponents($major_version, $minor_version)); + } + + return new Emulative([ + 'usedAttributes' => [ + 'comments', 'startLine', 'startFilePos', 'endFilePos', + ], + 'phpVersion' => $major_version . '.' . $minor_version, + ]); + } } diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php index dd97adf9b80..a12a0ce86eb 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php @@ -169,7 +169,6 @@ public function start(PhpParser\Node\Stmt\ClassLike $node): ?bool $fq_classlike_name = ($this->aliases->namespace ? $this->aliases->namespace . '\\' : '') . $node->name->name; - assert($fq_classlike_name !== ""); $fq_classlike_name_lc = strtolower($fq_classlike_name); diff --git a/src/Psalm/Internal/PhpVisitor/SimpleNameResolver.php b/src/Psalm/Internal/PhpVisitor/SimpleNameResolver.php index 1eca6d3fd10..68a10ed5176 100644 --- a/src/Psalm/Internal/PhpVisitor/SimpleNameResolver.php +++ b/src/Psalm/Internal/PhpVisitor/SimpleNameResolver.php @@ -144,6 +144,9 @@ public function enterNode(Node $node): ?int return null; } + /** + * @param Stmt\Use_::TYPE_* $type + */ private function addAlias(Stmt\UseUse $use, int $type, ?Name $prefix = null): void { // Add prefix for group uses diff --git a/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php b/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php index 353bdb3f408..359f2f65b2a 100644 --- a/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php +++ b/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php @@ -4,6 +4,7 @@ use InvalidArgumentException; use LogicException; +use Psalm\Internal\BCHelper; use Psalm\Storage\ClassLikeStorage; use function array_merge; @@ -39,7 +40,7 @@ public function __construct(?ClassLikeStorageCacheProvider $cache = null) */ public function get(string $fq_classlike_name): ClassLikeStorage { - $fq_classlike_name_lc = strtolower($fq_classlike_name); + $fq_classlike_name_lc = $this->formatClassName($fq_classlike_name); /** @psalm-suppress ImpureStaticProperty Used only for caching */ if (!isset(self::$storage[$fq_classlike_name_lc])) { throw new InvalidArgumentException('Could not get class storage for ' . $fq_classlike_name_lc); @@ -49,12 +50,22 @@ public function get(string $fq_classlike_name): ClassLikeStorage return self::$storage[$fq_classlike_name_lc]; } + /** + * @psalm-mutation-free + * + * @return lowercase-string + */ + private function formatClassName(string $class): string + { + return strtolower(BCHelper::getPHPParserClassName($class)); + } + /** * @psalm-mutation-free */ public function has(string $fq_classlike_name): bool { - $fq_classlike_name_lc = strtolower($fq_classlike_name); + $fq_classlike_name_lc = $this->formatClassName($fq_classlike_name); /** @psalm-suppress ImpureStaticProperty Used only for caching */ return isset(self::$storage[$fq_classlike_name_lc]); @@ -62,7 +73,7 @@ public function has(string $fq_classlike_name): bool public function exhume(string $fq_classlike_name, string $file_path, string $file_contents): ClassLikeStorage { - $fq_classlike_name_lc = strtolower($fq_classlike_name); + $fq_classlike_name_lc = $this->formatClassName($fq_classlike_name); if (isset(self::$storage[$fq_classlike_name_lc])) { return self::$storage[$fq_classlike_name_lc]; @@ -112,7 +123,7 @@ public function makeNew(string $fq_classlike_name_lc): void public function create(string $fq_classlike_name): ClassLikeStorage { - $fq_classlike_name_lc = strtolower($fq_classlike_name); + $fq_classlike_name_lc = $this->formatClassName($fq_classlike_name); $storage = new ClassLikeStorage($fq_classlike_name); self::$storage[$fq_classlike_name_lc] = $storage; @@ -123,7 +134,7 @@ public function create(string $fq_classlike_name): ClassLikeStorage public function remove(string $fq_classlike_name): void { - $fq_classlike_name_lc = strtolower($fq_classlike_name); + $fq_classlike_name_lc = $this->formatClassName($fq_classlike_name); unset(self::$storage[$fq_classlike_name_lc]); } diff --git a/src/Psalm/Internal/Provider/StatementsProvider.php b/src/Psalm/Internal/Provider/StatementsProvider.php index e923d405327..e896baf9623 100644 --- a/src/Psalm/Internal/Provider/StatementsProvider.php +++ b/src/Psalm/Internal/Provider/StatementsProvider.php @@ -7,10 +7,10 @@ use PhpParser\Lexer\Emulative; use PhpParser\Node\Stmt; use PhpParser\Parser; -use PhpParser\PhpVersion; use Psalm\CodeLocation\ParseErrorLocation; use Psalm\Codebase; use Psalm\Config; +use Psalm\Internal\BCHelper; use Psalm\Internal\Diff\FileDiffer; use Psalm\Internal\Diff\FileStatementsDiffer; use Psalm\Internal\PhpTraverser\CustomTraverser; @@ -394,7 +394,8 @@ public static function parseStatements( if (!self::$lexer) { $major_version = Codebase::transformPhpVersionId($analysis_php_version_id, 10_000); $minor_version = Codebase::transformPhpVersionId($analysis_php_version_id % 10_000, 100); - self::$lexer = new Emulative(PhpVersion::fromComponents($major_version, $minor_version)); + + self::$lexer = BCHelper::createEmulative($major_version, $minor_version); } if (!self::$parser) {