From b606f4e30b82779d50a6c4c12dfd19dc877f023f Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 3 Jul 2024 11:19:08 +0200 Subject: [PATCH 01/11] Try to add v5 support --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index aed943f1dd5..310f73a28e6 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "felixfbecker/language-server-protocol": "^1.5.2", "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "nikic/php-parser": "^4.17", + "nikic/php-parser": "^5.1", "sebastian/diff": "^4.0 || ^5.0 || ^6.0", "spatie/array-to-xml": "^2.17.0 || ^3.0", "symfony/console": "^4.1.6 || ^5.0 || ^6.0 || ^7.0", From 40db4c7d3cb713b39a4a7993cdaf8a7c98d7a349 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 3 Jul 2024 11:43:45 +0200 Subject: [PATCH 02/11] Fix --- src/Psalm/Internal/Provider/StatementsProvider.php | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Psalm/Internal/Provider/StatementsProvider.php b/src/Psalm/Internal/Provider/StatementsProvider.php index bd8e34a4b8c..e923d405327 100644 --- a/src/Psalm/Internal/Provider/StatementsProvider.php +++ b/src/Psalm/Internal/Provider/StatementsProvider.php @@ -7,6 +7,7 @@ use PhpParser\Lexer\Emulative; use PhpParser\Node\Stmt; use PhpParser\Parser; +use PhpParser\PhpVersion; use Psalm\CodeLocation\ParseErrorLocation; use Psalm\Codebase; use Psalm\Config; @@ -390,21 +391,14 @@ public static function parseStatements( ?array $existing_statements = null, ?array $file_changes = null ): array { - $attributes = [ - 'comments', 'startLine', 'startFilePos', 'endFilePos', - ]; - 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([ - 'usedAttributes' => $attributes, - 'phpVersion' => $major_version . '.' . $minor_version, - ]); + self::$lexer = new Emulative(PhpVersion::fromComponents($major_version, $minor_version)); } if (!self::$parser) { - self::$parser = (new PhpParser\ParserFactory())->create(PhpParser\ParserFactory::ONLY_PHP7, self::$lexer); + self::$parser = new Parser\Php7(self::$lexer); } $used_cached_statements = false; From e83723f138ba9532f5d55a9c19e918780ea57f13 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 3 Jul 2024 11:49:44 +0200 Subject: [PATCH 03/11] Fix --- src/Psalm/Internal/PhpTraverser/CustomTraverser.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Psalm/Internal/PhpTraverser/CustomTraverser.php b/src/Psalm/Internal/PhpTraverser/CustomTraverser.php index f1e2673572d..fbe4429db9d 100644 --- a/src/Psalm/Internal/PhpTraverser/CustomTraverser.php +++ b/src/Psalm/Internal/PhpTraverser/CustomTraverser.php @@ -27,9 +27,8 @@ public function __construct() * Recursively traverse a node. * * @param Node $node node to traverse - * @return Node Result of traversal (may be original node or new one) */ - protected function traverseNode(Node $node): Node + protected function traverseNode(Node $node): void { foreach ($node->getSubNodeNames() as $name) { $subNode = &$node->$name; @@ -60,7 +59,7 @@ protected function traverseNode(Node $node): Node } if ($traverseChildren) { - $subNode = $this->traverseNode($subNode); + $this->traverseNode($subNode); if ($this->stopTraversal) { break; } @@ -88,8 +87,6 @@ protected function traverseNode(Node $node): Node } } } - - return $node; } /** @@ -124,7 +121,7 @@ protected function traverseArray(array $nodes): array } if ($traverseChildren) { - $node = $this->traverseNode($node); + $this->traverseNode($node); if ($this->stopTraversal) { break; } From c7ddcd4ea8220ec3bfaba0d32a0d8d2417cac8d0 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 25 Jul 2024 08:49:18 +0200 Subject: [PATCH 04/11] Add BCHelper --- .../FunctionLike/ReturnTypeCollector.php | 4 ++- src/Psalm/Internal/Analyzer/ScopeAnalyzer.php | 7 +++-- .../Statements/ExpressionAnalyzer.php | 5 +++- src/Psalm/Internal/BCHelper.php | 29 +++++++++++++++++++ 4 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 src/Psalm/Internal/BCHelper.php diff --git a/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php b/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php index 2d99b3435af..02dd19ad8b5 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php +++ b/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php @@ -6,6 +6,7 @@ use PhpParser\NodeTraverser; use Psalm\Codebase; use Psalm\Internal\Analyzer\Statements\Block\ForeachAnalyzer; +use Psalm\Internal\BCHelper; use Psalm\Internal\PhpVisitor\YieldTypeCollector; use Psalm\Internal\Provider\NodeDataProvider; use Psalm\Type; @@ -83,7 +84,8 @@ public static function getReturnTypes( } if ($stmt instanceof PhpParser\Node\Stmt\Expression) { - if ($stmt->expr instanceof PhpParser\Node\Expr\Exit_) { + if ($stmt->expr instanceof PhpParser\Node\Expr\Exit_ + || (!BCHelper::usePHPParserV4() && $stmt->expr instanceof PhpParser\Node\Expr\Throw_)) { $return_types[] = Type::getNever(); break; diff --git a/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php b/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php index 808010d09bc..54bdd54c540 100644 --- a/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php @@ -3,6 +3,7 @@ namespace Psalm\Internal\Analyzer; use PhpParser; +use Psalm\Internal\BCHelper; use Psalm\Internal\Provider\NodeDataProvider; use Psalm\NodeTypeProvider; @@ -48,7 +49,7 @@ public static function getControlActions( foreach ($stmts as $stmt) { if ($stmt instanceof PhpParser\Node\Stmt\Return_ || - $stmt instanceof PhpParser\Node\Stmt\Throw_ || + BCHelper::isThrow($stmt) || ($stmt instanceof PhpParser\Node\Stmt\Expression && $stmt->expr instanceof PhpParser\Node\Expr\Exit_) ) { if (!$return_is_exit && $stmt instanceof PhpParser\Node\Stmt\Return_) { @@ -406,7 +407,7 @@ public static function onlyThrowsOrExits(NodeTypeProvider $type_provider, array for ($i = count($stmts) - 1; $i >= 0; --$i) { $stmt = $stmts[$i]; - if ($stmt instanceof PhpParser\Node\Stmt\Throw_ + if (BCHelper::isThrow($stmt) || ($stmt instanceof PhpParser\Node\Stmt\Expression && $stmt->expr instanceof PhpParser\Node\Expr\Exit_) ) { @@ -436,7 +437,7 @@ public static function onlyThrows(array $stmts): bool } foreach ($stmts as $stmt) { - if ($stmt instanceof PhpParser\Node\Stmt\Throw_) { + if (BCHelper::isThrow($stmt)) { return true; } } diff --git a/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php index c19c8df51a6..5474cdb03a3 100644 --- a/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php @@ -42,6 +42,7 @@ use Psalm\Internal\Analyzer\Statements\Expression\YieldAnalyzer; use Psalm\Internal\Analyzer\Statements\Expression\YieldFromAnalyzer; use Psalm\Internal\Analyzer\StatementsAnalyzer; +use Psalm\Internal\BCHelper; use Psalm\Internal\FileManipulation\FileManipulationBuffer; use Psalm\Internal\Type\TemplateResult; use Psalm\Issue\RiskyTruthyFalsyComparison; @@ -456,7 +457,9 @@ private static function handleExpression( return MatchAnalyzer::analyze($statements_analyzer, $stmt, $context); } - if ($stmt instanceof PhpParser\Node\Expr\Throw_ && $analysis_php_version_id >= 8_00_00) { + if ($stmt instanceof PhpParser\Node\Expr\Throw_ + && ($analysis_php_version_id >= 8_00_00 || !BCHelper::usePHPParserV4()) + ) { return ThrowAnalyzer::analyze($statements_analyzer, $stmt, $context); } diff --git a/src/Psalm/Internal/BCHelper.php b/src/Psalm/Internal/BCHelper.php new file mode 100644 index 00000000000..9cdd9df1673 --- /dev/null +++ b/src/Psalm/Internal/BCHelper.php @@ -0,0 +1,29 @@ +expr instanceof PhpParser\Node\Expr\Throw_; + } +} From 653cfa8261086ed0be7e022aa0c724394541ebc2 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 25 Jul 2024 13:53:44 +0200 Subject: [PATCH 05/11] From stmt --- .../Expression/AssignmentAnalyzer.php | 7 ++-- .../Expression/Call/ArgumentsAnalyzer.php | 2 +- .../Expression/EncapsulatedStringAnalyzer.php | 3 +- .../Statements/Expression/MatchAnalyzer.php | 1 + .../Statements/ExpressionAnalyzer.php | 12 +++---- .../Internal/Analyzer/StatementsAnalyzer.php | 2 +- .../PhpVisitor/CheckTrivialExprVisitor.php | 3 ++ .../Reflector/FunctionLikeNodeScanner.php | 8 +++-- .../Internal/PhpVisitor/ReflectorVisitor.php | 32 +++++++++++++++++-- 9 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php index 679abaa347a..95f17603e4b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssignmentAnalyzer.php @@ -896,9 +896,10 @@ public static function analyzeAssignmentOperation( public static function analyzeAssignmentRef( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Expr\AssignRef $stmt, - Context $context + Context $context, + ?PhpParser\Node\Stmt $from_stmt ): bool { - ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context, false, null, false, null, true); + ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context, false, null, null, null, true); $lhs_var_id = ExpressionIdentifier::getExtendedVarId( $stmt->var, @@ -912,7 +913,7 @@ public static function analyzeAssignmentRef( $statements_analyzer, ); - $doc_comment = $stmt->getDocComment(); + $doc_comment = $stmt->getDocComment() ?? ($from_stmt ? $from_stmt->getDocComment() : null); if ($doc_comment) { try { $var_comments = CommentAnalyzer::getTypeFromComment( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php index 3d825668dc9..900c529f934 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php @@ -240,7 +240,7 @@ public static function analyze( $context, false, null, - false, + null, $high_order_template_result, ) === false) { $context->inside_isset = $was_inside_isset; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php index 63c815ff521..af3f1f22305 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php @@ -3,6 +3,7 @@ namespace Psalm\Internal\Analyzer\Statements\Expression; use PhpParser; +use PhpParser\Node\Expr; use PhpParser\Node\Scalar\EncapsedStringPart; use Psalm\CodeLocation; use Psalm\Context; @@ -42,7 +43,7 @@ public static function analyze( $literal_string = ""; foreach ($stmt->parts as $part) { - if (ExpressionAnalyzer::analyze($statements_analyzer, $part, $context) === false) { + if ($part instanceof Expr && ExpressionAnalyzer::analyze($statements_analyzer, $part, $context) === false) { return false; } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php index ffaa0f5b387..4768f024910 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php @@ -316,6 +316,7 @@ public static function analyze( /** * @param non-empty-list $conds + * @param array $attributes */ private static function convertCondsToConditional( array $conds, diff --git a/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php index 5474cdb03a3..41c1ea8fd4a 100644 --- a/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php @@ -78,7 +78,7 @@ public static function analyze( Context $context, bool $array_assignment = false, ?Context $global_context = null, - bool $from_stmt = false, + ?PhpParser\Node\Stmt $from_stmt = null, ?TemplateResult $template_result = null, bool $assigned_to_reference = false ): bool { @@ -183,7 +183,7 @@ private static function handleExpression( Context $context, bool $array_assignment, ?Context $global_context, - bool $from_stmt, + ?PhpParser\Node\Stmt $from_stmt, ?TemplateResult $template_result = null, bool $assigned_to_reference = false ): bool { @@ -293,7 +293,7 @@ private static function handleExpression( $stmt, $context, 0, - $from_stmt, + $from_stmt !== null, ); } @@ -380,7 +380,7 @@ private static function handleExpression( } if ($stmt instanceof PhpParser\Node\Expr\AssignRef) { - if (!AssignmentAnalyzer::analyzeAssignmentRef($statements_analyzer, $stmt, $context)) { + if (!AssignmentAnalyzer::analyzeAssignmentRef($statements_analyzer, $stmt, $context, $from_stmt)) { IssueBuffer::maybeAdd( new UnsupportedReferenceUsage( "This reference cannot be analyzed by Psalm", @@ -499,7 +499,7 @@ private static function analyzeAssignment( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Expr $stmt, Context $context, - bool $from_stmt + ?PhpParser\Node\Stmt $from_stmt ): bool { $assignment_type = AssignmentAnalyzer::analyze( $statements_analyzer, @@ -507,7 +507,7 @@ private static function analyzeAssignment( $stmt->expr, null, $context, - $stmt->getDocComment(), + $stmt->getDocComment() ?? ($from_stmt ? $from_stmt->getDocComment() : null), [], !$from_stmt ? $stmt : null, ); diff --git a/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php b/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php index caf95b3801f..26b93e12fa9 100644 --- a/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php @@ -566,7 +566,7 @@ private static function analyzeStatement( $context, false, $global_context, - true, + $stmt, ) === false) { return false; } diff --git a/src/Psalm/Internal/PhpVisitor/CheckTrivialExprVisitor.php b/src/Psalm/Internal/PhpVisitor/CheckTrivialExprVisitor.php index 4179ac0d0e6..12d2f44b0fe 100644 --- a/src/Psalm/Internal/PhpVisitor/CheckTrivialExprVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/CheckTrivialExprVisitor.php @@ -63,6 +63,9 @@ public function enterNode(PhpParser\Node $node): ?int || $node instanceof PhpParser\Node\Expr\StaticPropertyFetch) { return PhpParser\NodeTraverser::STOP_TRAVERSAL; } + } elseif ($node instanceof PhpParser\Node\ClosureUse) { + $this->has_non_trivial_expr = true; + return PhpParser\NodeTraverser::STOP_TRAVERSAL; } return null; } diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php index fa2d09f2a98..d9f534ab1c0 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php @@ -122,7 +122,11 @@ public function __construct( * @param bool $fake_method in the case of @method annotations we do something a little strange * @return FunctionStorage|MethodStorage|false */ - public function start(PhpParser\Node\FunctionLike $stmt, bool $fake_method = false) + public function start( + PhpParser\Node\FunctionLike $stmt, + bool $fake_method = false, + PhpParser\Comment\Doc $doc_comment = null + ) { if ($stmt instanceof PhpParser\Node\Expr\Closure || $stmt instanceof PhpParser\Node\Expr\ArrowFunction @@ -433,7 +437,7 @@ public function start(PhpParser\Node\FunctionLike $stmt, bool $fake_method = fal $storage->returns_by_ref = true; } - $doc_comment = $stmt->getDocComment(); + $doc_comment = $stmt->getDocComment() ?? $doc_comment; if ($classlike_storage && !$classlike_storage->is_trait) { diff --git a/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php b/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php index 188e073153e..f20be2fe4dd 100644 --- a/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php @@ -32,6 +32,7 @@ use Psalm\Storage\FileStorage; use Psalm\Storage\MethodStorage; use Psalm\Type; +use SplObjectStorage; use UnexpectedValueException; use function array_merge; @@ -92,6 +93,11 @@ final class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements Fi private array $bad_classes = []; private EventDispatcher $eventDispatcher; + /** + * @var SplObjectStorage + */ + private SplObjectStorage $closure_statements; + public function __construct( Codebase $codebase, FileScanner $file_scanner, @@ -104,6 +110,7 @@ public function __construct( $this->file_storage = $file_storage; $this->aliases = $this->file_storage->aliases = new Aliases(); $this->eventDispatcher = $this->codebase->config->eventDispatcher; + $this->closure_statements = new SplObjectStorage(); } public function enterNode(PhpParser\Node $node): ?int @@ -171,13 +178,34 @@ public function enterNode(PhpParser\Node $node): ?int } } } - } elseif ($node instanceof PhpParser\Node\FunctionLike) { + } elseif ($node instanceof PhpParser\Node\FunctionLike + || ($node instanceof PhpParser\Node\Stmt\Expression + && ($node->expr instanceof PhpParser\Node\Expr\ArrowFunction + || $node->expr instanceof PhpParser\Node\Expr\Closure)) + || ($node instanceof PhpParser\Node\Arg + && ($node->value instanceof PhpParser\Node\Expr\ArrowFunction + || $node->value instanceof PhpParser\Node\Expr\Closure)) + ) { + $doc_comment = null; if ($node instanceof PhpParser\Node\Stmt\Function_ || $node instanceof PhpParser\Node\Stmt\ClassMethod ) { if ($this->skip_if_descendants) { return null; } + } elseif ($node instanceof PhpParser\Node\Stmt\Expression) { + $doc_comment = $node->getDocComment(); + /** @var PhpParser\Node\FunctionLike */ + $node = $node->expr; + $this->closure_statements->attach($node); + } elseif ($node instanceof PhpParser\Node\Arg) { + $doc_comment = $node->getDocComment(); + /** @var PhpParser\Node\FunctionLike */ + $node = $node->value; + $this->closure_statements->attach($node); + } elseif ($this->closure_statements->contains($node)) { + // This is a closure that was already processed at the statement level. + return null; } $classlike_storage = null; @@ -204,7 +232,7 @@ public function enterNode(PhpParser\Node $node): ?int $functionlike_types, ); - $functionlike_node_scanner->start($node); + $functionlike_node_scanner->start($node, false, $doc_comment); $this->functionlike_node_scanners[] = $functionlike_node_scanner; From c3796df73a07c8a0835e1582a4d5eedd799f0f41 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 25 Jul 2024 14:11:14 +0200 Subject: [PATCH 06/11] Fix --- .../PhpVisitor/Reflector/FunctionLikeNodeScanner.php | 3 +-- .../EventHandler/Event/AddRemoveTaintsEvent.php | 12 +++++++++--- tests/fixtures/SuicidalAutoloader/autoloader.php | 1 + 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php index d9f534ab1c0..7444d7d7db4 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php @@ -126,8 +126,7 @@ public function start( PhpParser\Node\FunctionLike $stmt, bool $fake_method = false, PhpParser\Comment\Doc $doc_comment = null - ) - { + ) { if ($stmt instanceof PhpParser\Node\Expr\Closure || $stmt instanceof PhpParser\Node\Expr\ArrowFunction ) { diff --git a/src/Psalm/Plugin/EventHandler/Event/AddRemoveTaintsEvent.php b/src/Psalm/Plugin/EventHandler/Event/AddRemoveTaintsEvent.php index 97934728826..a70664b581e 100644 --- a/src/Psalm/Plugin/EventHandler/Event/AddRemoveTaintsEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/AddRemoveTaintsEvent.php @@ -2,6 +2,7 @@ namespace Psalm\Plugin\EventHandler\Event; +use PhpParser\Node\ArrayItem; use PhpParser\Node\Expr; use Psalm\Codebase; use Psalm\Context; @@ -9,7 +10,8 @@ final class AddRemoveTaintsEvent { - private Expr $expr; + /** @var ArrayItem|Expr */ + private $expr; private Context $context; private StatementsSource $statements_source; private Codebase $codebase; @@ -17,10 +19,11 @@ final class AddRemoveTaintsEvent /** * Called after an expression has been checked * + * @param ArrayItem|Expr $expr * @internal */ public function __construct( - Expr $expr, + $expr, Context $context, StatementsSource $statements_source, Codebase $codebase @@ -31,7 +34,10 @@ public function __construct( $this->codebase = $codebase; } - public function getExpr(): Expr + /** + * @return ArrayItem|Expr + */ + public function getExpr() { return $this->expr; } diff --git a/tests/fixtures/SuicidalAutoloader/autoloader.php b/tests/fixtures/SuicidalAutoloader/autoloader.php index ac6e8d54a47..d31e8da0747 100644 --- a/tests/fixtures/SuicidalAutoloader/autoloader.php +++ b/tests/fixtures/SuicidalAutoloader/autoloader.php @@ -18,6 +18,7 @@ 'PHPUnit\Framework\DOMElement', 'Stringable', 'AllowDynamicProperties', + 'PhpParser\Node\Stmt\Throw', // BCHelper for nikic/php-parser v4/5 // https://github.com/symfony/symfony/pull/40203 // these are actually functions, referenced as `if (!function_exists(u::class))` From eb61cede5d150feef57629e4de2492a0c1ad4c87 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 9 Aug 2024 13:35:27 +0200 Subject: [PATCH 07/11] Add v4 support --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 310f73a28e6..4a3507f2d4a 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "felixfbecker/language-server-protocol": "^1.5.2", "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "nikic/php-parser": "^5.1", + "nikic/php-parser": "^4.16 || ^5.1", "sebastian/diff": "^4.0 || ^5.0 || ^6.0", "spatie/array-to-xml": "^2.17.0 || ^3.0", "symfony/console": "^4.1.6 || ^5.0 || ^6.0 || ^7.0", From de1618401fcf25353b5b1dace22eb13b06b1b24d Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 9 Aug 2024 19:00:30 +0200 Subject: [PATCH 08/11] Fix --- src/Psalm/CodeLocation/ParseErrorLocation.php | 4 +- .../FunctionLike/ReturnTypeCollector.php | 2 +- .../Statements/Block/SwitchCaseAnalyzer.php | 4 +- .../Call/Method/MissingMethodCallHandler.php | 3 +- .../Statements/Expression/MatchAnalyzer.php | 3 +- .../Analyzer/Statements/ThrowAnalyzer.php | 4 +- .../Internal/Analyzer/StatementsAnalyzer.php | 3 +- src/Psalm/Internal/BCHelper.php | 47 +++++++++++++++++++ .../Reflector/ClassLikeNodeScanner.php | 1 - .../PhpVisitor/SimpleNameResolver.php | 3 ++ .../Provider/ClassLikeStorageProvider.php | 21 +++++++-- .../Internal/Provider/StatementsProvider.php | 5 +- 12 files changed, 82 insertions(+), 18 deletions(-) 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) { From 3d3ae7c54ce8955a0921155c854bd8297498adf0 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 9 Aug 2024 20:08:19 +0200 Subject: [PATCH 09/11] Update baseline --- psalm-baseline.xml | 319 +++++++++++++++++- .../Reflector/ClassLikeNodeScanner.php | 1 - .../Provider/ClassLikeStorageProvider.php | 1 - 3 files changed, 311 insertions(+), 10 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 987aab124ad..2fc61f0a912 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + tags['variablesfrom'][0]]]> @@ -134,6 +134,9 @@ + + + @@ -372,6 +375,15 @@ + + + + + + + + |null]]> + getArgs()[1]]]> leftover_statements[0]]]> @@ -457,8 +469,6 @@ - - @@ -797,6 +807,17 @@ + + value, + null, + false, + $arg->getAttributes(), + )]]> + + + + @@ -825,6 +846,9 @@ + + + getFQCLN()]]> @@ -935,7 +959,6 @@ - @@ -1014,6 +1037,12 @@ + + getAttributes())]]> + + + + @@ -1045,6 +1074,11 @@ type_start]]> + + + + + @@ -1062,6 +1096,16 @@ type_start]]> + + + + + + + + + + @@ -1081,6 +1125,9 @@ + + + expr->getArgs()[0]]]> @@ -1093,6 +1140,24 @@ vars_to_initialize]]> + + + [ + 'comments', 'startLine', 'startFilePos', 'endFilePos', + ], + 'phpVersion' => $major_version . '.' . $minor_version, + ]]]> + + + + + + + + + + @@ -1386,6 +1451,10 @@ + + + + @@ -1397,7 +1466,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1511,6 +1619,13 @@ + + + + + + + stmts[0]]]> @@ -1537,6 +1652,10 @@ + + + + aliases->uses_start]]> aliases->uses_start]]> @@ -1553,6 +1672,9 @@ + + + @@ -1562,6 +1684,16 @@ start_change]]> + + + + + + + + + + @@ -1608,6 +1740,11 @@ + + + + + @@ -1733,6 +1870,30 @@ + + + + + + + + + + + + + + + + suggested_type + ? StubsGenerator::getExpressionFromType($property_storage->suggested_type) + : null + ) + ]]]> + cased_name]]> template_types]]> @@ -1741,6 +1902,9 @@ + + + cased_name]]> cased_name]]> @@ -2002,6 +2166,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + other_references]]> @@ -2351,9 +2644,19 @@ - - - - + + + + + + + + + + + + + + diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php index a12a0ce86eb..ff88705c606 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php @@ -73,7 +73,6 @@ use function array_pop; use function array_shift; use function array_values; -use function assert; use function count; use function get_class; use function implode; diff --git a/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php b/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php index 359f2f65b2a..e7488df50e6 100644 --- a/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php +++ b/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php @@ -52,7 +52,6 @@ public function get(string $fq_classlike_name): ClassLikeStorage /** * @psalm-mutation-free - * * @return lowercase-string */ private function formatClassName(string $class): string From f9c15934a960e05ef53bb2c8e46988c054c09dfa Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 10 Aug 2024 14:18:46 +0200 Subject: [PATCH 10/11] Fix and add lowest run --- .github/workflows/ci.yml | 16 +++++++-- src/Psalm/Internal/BCHelper.php | 2 +- .../Internal/PhpTraverser/CustomTraverser.php | 36 +++++++++++++++++-- .../SuicidalAutoloader/autoloader.php | 3 +- 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d1103373cb..e94a3740cb8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -115,7 +115,7 @@ jobs: echo "chunks=$(php -r 'echo json_encode(range(1, ${{ env.CHUNK_COUNT }} ));')" >> $GITHUB_OUTPUT tests: - name: "Unit Tests - PHP ${{ matrix.php-version }} ${{ matrix.chunk }}/${{ matrix.count }}" + name: "Unit Tests - PHP ${{ matrix.php-version }} ${{ matrix.dependencies }} ${{ matrix.chunk }}/${{ matrix.count }}" runs-on: ubuntu-latest needs: @@ -129,6 +129,9 @@ jobs: - "8.1" - "8.2" - "8.3" + dependencies: + - highest + - lowest count: ${{ fromJson(needs.chunk-matrix.outputs.count) }} chunk: ${{ fromJson(needs.chunk-matrix.outputs.chunks) }} @@ -158,8 +161,17 @@ jobs: echo "files_cache=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT echo "vcs_cache=$(composer config cache-vcs-dir)" >> $GITHUB_OUTPUT - - name: Generate composer.lock + - name: Generate highest composer.lock + if: ${{ matrix.dependencies == 'highest' }} + run: | + composer update --no-install + env: + COMPOSER_ROOT_VERSION: dev-master + + - name: Generate lowest composer.lock + if: ${{ matrix.dependencies == 'lowest' }} run: | + composer require nikic/php-parser ^4.16 composer update --no-install env: COMPOSER_ROOT_VERSION: dev-master diff --git a/src/Psalm/Internal/BCHelper.php b/src/Psalm/Internal/BCHelper.php index 51332ef8408..316148d96e4 100644 --- a/src/Psalm/Internal/BCHelper.php +++ b/src/Psalm/Internal/BCHelper.php @@ -38,7 +38,7 @@ public static function getPHPParserClassName(string $className): string public static function usePHPParserV4(): bool { - return class_exists('\PhpParser\Node\Stmt\Throw'); + return class_exists('\PhpParser\Node\Stmt\Throw_'); } public static function isThrow(Node $stmt): bool diff --git a/src/Psalm/Internal/PhpTraverser/CustomTraverser.php b/src/Psalm/Internal/PhpTraverser/CustomTraverser.php index fbe4429db9d..085302993dd 100644 --- a/src/Psalm/Internal/PhpTraverser/CustomTraverser.php +++ b/src/Psalm/Internal/PhpTraverser/CustomTraverser.php @@ -1,5 +1,9 @@ customTraverseNode($node); + + return $node; + } + } +} else { + /** + * @internal + */ + final class CustomTraverser extends InternalCustomTraverser + { + protected function traverseNode(Node $node): void + { + $this->customTraverseNode($node); + } + } +} + + /** * @internal */ -final class CustomTraverser extends NodeTraverser +abstract class InternalCustomTraverser extends NodeTraverser { public function __construct() { @@ -28,7 +60,7 @@ public function __construct() * * @param Node $node node to traverse */ - protected function traverseNode(Node $node): void + protected function customTraverseNode(Node $node): void { foreach ($node->getSubNodeNames() as $name) { $subNode = &$node->$name; diff --git a/tests/fixtures/SuicidalAutoloader/autoloader.php b/tests/fixtures/SuicidalAutoloader/autoloader.php index d31e8da0747..6e28b6c1b78 100644 --- a/tests/fixtures/SuicidalAutoloader/autoloader.php +++ b/tests/fixtures/SuicidalAutoloader/autoloader.php @@ -18,7 +18,8 @@ 'PHPUnit\Framework\DOMElement', 'Stringable', 'AllowDynamicProperties', - 'PhpParser\Node\Stmt\Throw', // BCHelper for nikic/php-parser v4/5 + 'PhpParser\PhpVersion', // BCHelper for nikic/php-parser v4/5 + 'PhpParser\Node\Stmt\Throw_', // BCHelper for nikic/php-parser v4/5 // https://github.com/symfony/symfony/pull/40203 // these are actually functions, referenced as `if (!function_exists(u::class))` From b6282ae0c6b3c0450bee92b0fbfefbf86dd84b55 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 8 Sep 2024 23:03:58 +0200 Subject: [PATCH 11/11] Fix rebase --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4a3507f2d4a..0d60df0ba87 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "felixfbecker/language-server-protocol": "^1.5.2", "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "nikic/php-parser": "^4.16 || ^5.1", + "nikic/php-parser": "^4.17 || ^5.1", "sebastian/diff": "^4.0 || ^5.0 || ^6.0", "spatie/array-to-xml": "^2.17.0 || ^3.0", "symfony/console": "^4.1.6 || ^5.0 || ^6.0 || ^7.0",