Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Call to undefined method PhpParser\Node\Expr\Variable::getParts() #610

Open
simPod opened this issue Jan 14, 2024 · 18 comments
Open

Call to undefined method PhpParser\Node\Expr\Variable::getParts() #610

simPod opened this issue Jan 14, 2024 · 18 comments

Comments

@simPod
Copy link
Contributor

simPod commented Jan 14, 2024

Describe the bug

I have encountered a crash

PHP Fatal error:  Uncaught Error: Call to undefined method PhpParser\Node\Expr\Variable::getParts() in /builds/7/my/app/vendor/composer-unused/symbol-parser/src/Parser/PHP/DefinedSymbolCollector.php:65
Stack trace:
#0 /builds/7/my/app/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(200): ComposerUnused\SymbolParser\Parser\PHP\DefinedSymbolCollector->enterNode()
#1 /builds/7/my/app/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(114): PhpParser\NodeTraverser->traverseArray()
#2 /builds/7/my/app/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(223): PhpParser\NodeTraverser->traverseNode()
#3 /builds/7/my/app/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(114): PhpParser\NodeTraverser->traverseArray()
#4 /builds/7/my/app/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(223): PhpParser\NodeTraverser->traverseNode()
#5 /builds/7/my/app/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(114): PhpParser\NodeTraverser->traverseArray()
#6 /builds/7/my/app/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(223): PhpParser\NodeTraverser->traverseNode()
#7 /builds/7/my/app/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(114): PhpParser\NodeTraverser->traverseArray()
#8 /builds/7/my/app/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(223): PhpParser\NodeTraverser->traverseNode()
#9 /builds/7/my/app/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(114): PhpParser\NodeTraverser->traverseArray()
#10 /builds/7/my/app/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(223): PhpParser\NodeTraverser->traverseNode()
#11 /builds/7/my/app/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(114): PhpParser\NodeTraverser->traverseArray()
#12 /builds/7/my/app/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(223): PhpParser\NodeTraverser->traverseNode()
#13 /builds/7/my/app/vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php(91): PhpParser\NodeTraverser->traverseArray()
#14 /builds/7/my/app/vendor/composer-unused/symbol-parser/src/Parser/PHP/SymbolNameParser.php(55): PhpParser\NodeTraverser->traverse()
#15 /builds/7/my/app/vendor/composer-unused/symbol-parser/src/Symbol/Provider/FileSymbolProvider.php(44): ComposerUnused\SymbolParser\Parser\PHP\SymbolNameParser->parseSymbolNames()
#16 /builds/7/my/app/vendor/composer-unused/symbol-parser/src/Symbol/Loader/FileSymbolLoader.php(70): ComposerUnused\SymbolParser\Symbol\Provider\FileSymbolProvider->provide()
#17 /builds/7/my/app/vendor/composer-unused/symbol-parser/src/Symbol/Loader/CompositeSymbolLoader.php(29): ComposerUnused\SymbolParser\Symbol\Loader\FileSymbolLoader->load()
#18 [internal function]: ComposerUnused\SymbolParser\Symbol\Loader\CompositeSymbolLoader->load()
#19 /builds/7/my/app/vendor/composer-unused/symbol-parser/src/Symbol/SymbolList.php(23): iterator_to_array()
#20 /builds/7/my/app/vendor/icanhazstring/composer-unused/src/Command/Handler/CollectRequiredDependenciesCommandHandler.php(60): ComposerUnused\SymbolParser\Symbol\SymbolList->addAll()
#21 /builds/7/my/app/vendor/icanhazstring/composer-unused/src/Console/Command/UnusedCommand.php(182): ComposerUnused\ComposerUnused\Command\Handler\CollectRequiredDependenciesCommandHandler->collect()
#22 /builds/7/my/app/vendor/symfony/console/Command/Command.php(279): ComposerUnused\ComposerUnused\Console\Command\UnusedCommand->execute()
#23 /builds/7/my/app/vendor/symfony/console/Application.php(1031): Symfony\Component\Console\Command\Command->run()
#24 /builds/7/my/app/vendor/symfony/console/Application.php(318): Symfony\Component\Console\Application->doRunCommand()
#25 /builds/7/my/app/vendor/symfony/console/Application.php(169): Symfony\Component\Console\Application->doRun()
#26 /builds/7/my/app/vendor/icanhazstring/composer-unused/bin/composer-unused(66): Symfony\Component\Console\Application->run()
#27 /builds/7/my/app/vendor/icanhazstring/composer-unused/bin/composer-unused(67): {closure}()
#28 /builds/7/my/app/vendor/bin/composer-unused(119): include('...')
#29 {main}
  thrown in /builds/7/my/app/vendor/composer-unused/symbol-parser/src/Parser/PHP/DefinedSymbolCollector.php on line 65

I run the unused with 112d25c

what other information should I acquire?

@icanhazstring
Copy link
Member

Can you narrow it down to a certain vendor package?

Because it's hard to detect which code structure causes this.

@simPod
Copy link
Contributor Author

simPod commented Jan 14, 2024

<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

declare(strict_types=1);

namespace Nette;

use Nette\Utils\ObjectHelpers;


/**
 * Strict class for better experience.
 * - 'did you mean' hints
 * - access to undeclared members throws exceptions
 * - support for @property annotations
 * - support for calling event handlers stored in $onEvent via onEvent()
 */
trait SmartObject
{
	/**
	 * @return mixed
	 * @throws MemberAccessException
	 */
	public function __call(string $name, array $args)
	{
		$class = static::class;

		if (ObjectHelpers::hasProperty($class, $name) === 'event') { // calling event handlers
			$handlers = $this->$name ?? null;
			if (is_iterable($handlers)) {
				foreach ($handlers as $handler) {
					$handler(...$args);
				}
			} elseif ($handlers !== null) {
				throw new UnexpectedValueException("Property $class::$$name must be iterable or null, " . get_debug_type($handlers) . ' given.');
			}

			return null;
		}

		ObjectHelpers::strictCall($class, $name);
	}


	/**
	 * @throws MemberAccessException
	 */
	public static function __callStatic(string $name, array $args)
	{
		ObjectHelpers::strictStaticCall(static::class, $name);
	}


	/**
	 * @return mixed
	 * @throws MemberAccessException if the property is not defined.
	 */
	public function &__get(string $name)
	{
		$class = static::class;

		if ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property getter
			if (!($prop & 0b0001)) {
				throw new MemberAccessException("Cannot read a write-only property $class::\$$name.");
			}

			$m = ($prop & 0b0010 ? 'get' : 'is') . ucfirst($name);
			if ($prop & 0b10000) {
				$trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call()
				$loc = isset($trace['file'], $trace['line'])
					? " in $trace[file] on line $trace[line]"
					: '';
				trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED);
			}

			if ($prop & 0b0100) { // return by reference
				return $this->$m();
			} else {
				$val = $this->$m();
				return $val;
			}
		} else {
			ObjectHelpers::strictGet($class, $name);
		}
	}


	/**
	 * @throws MemberAccessException if the property is not defined or is read-only
	 */
	public function __set(string $name, mixed $value): void
	{
		$class = static::class;

		if (ObjectHelpers::hasProperty($class, $name)) { // unsetted property
			$this->$name = $value;

		} elseif ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property setter
			if (!($prop & 0b1000)) {
				throw new MemberAccessException("Cannot write to a read-only property $class::\$$name.");
			}

			$m = 'set' . ucfirst($name);
			if ($prop & 0b10000) {
				$trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call()
				$loc = isset($trace['file'], $trace['line'])
					? " in $trace[file] on line $trace[line]"
					: '';
				trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED);
			}

			$this->$m($value);

		} else {
			ObjectHelpers::strictSet($class, $name);
		}
	}


	/**
	 * @throws MemberAccessException
	 */
	public function __unset(string $name): void
	{
		$class = static::class;
		if (!ObjectHelpers::hasProperty($class, $name)) {
			throw new MemberAccessException("Cannot unset the property $class::\$$name.");
		}
	}


	public function __isset(string $name): bool
	{
		return isset(ObjectHelpers::getMagicProperties(static::class)[$name]);
	}
}

L36 $handler(...$args);

image

@icanhazstring
Copy link
Member

Thanks. I can check.
But something to fix in symbol-parser most probably.

@llaville
Copy link
Contributor

llaville commented Mar 2, 2024

But something to fix in symbol-parser most probably.

👍 My explain and how to fix was already replyed at composer-unused/symbol-parser#122 (comment)

@TomasVotruba
Copy link
Contributor

@llaville I didn't understand how to fix it in my projects from the comment.

But I've found a way 😃

composer require "symfony/finder:^6.4"
composer require "composer-unused/symbol-parser:0.2.1"

It would help to make composer-unused a scoped prefixed package, to drop these version conflicts compleetely. Like PHPStan, Rector and ECS do.

@llaville
Copy link
Contributor

llaville commented Mar 7, 2024

@TomasVotruba Did you read my RFC at #630 ?

@TomasVotruba
Copy link
Contributor

@llaville Indeed, but didn't find solution there.

@llaville
Copy link
Contributor

llaville commented Mar 7, 2024

@TomasVotruba Could you share your project (where you used composer-unused), if public, to see what's happens

@TomasVotruba
Copy link
Contributor

TomasVotruba commented Mar 7, 2024

@llaville
Copy link
Contributor

llaville commented Mar 7, 2024

@TomasVotruba Thanks for share

This is what I've tried to explain by my comment (composer-unused/symbol-parser#122 (comment))

DefinedSymbolCollector need a fix on version 0.2.2

We should check that $node->expr->name instanceof Node\Name is valid before to try to get it name $functionName = $expressionName->getParts()[0] ?? null;

I've already applied this change on the new DefineStrategy (see my contribution initialized by composer-unused/symbol-parser#137)
But no official version that embed it was still published !

Goal was to unified SymbolCollector(s) and have only one : the ConsumedSymbolCollector (that use list of strategy)
and remove useless DefinedSymbolCollector.

BTW, DefinedSymbolCollector was not patched, and valid code are only present into DefineStrategy.

A quick fix to solve the situation, with composer-unused/symbol-parser 0.2.2 is to apply this patch :

diff --git a/src/Parser/PHP/DefinedSymbolCollector.php b/src/Parser/PHP/DefinedSymbolCollector.php
index 703840c..77449ac 100644
--- a/src/Parser/PHP/DefinedSymbolCollector.php
+++ b/src/Parser/PHP/DefinedSymbolCollector.php
@@ -59,7 +59,11 @@ final class DefinedSymbolCollector extends AbstractCollector
             return NodeTraverser::DONT_TRAVERSE_CHILDREN;
         }

-        if ($node instanceof Node\Stmt\Expression && $node->expr instanceof Node\Expr\FuncCall) {
+        if (
+            $node instanceof Node\Stmt\Expression &&
+            $node->expr instanceof Node\Expr\FuncCall &&
+            $node->expr->name instanceof Node\Name
+        ) {
             /** @var Node\Name $expressionName */
             $expressionName = $node->expr->name;
             $functionName = $expressionName->getParts()[0] ?? null;

@llaville
Copy link
Contributor

llaville commented Mar 7, 2024

If you remove the https://github.com/rectorphp/swiss-knife/blob/main/composer.json#L20 constraint to use the latest version 0.2.2, you 'll get of course error (as previously explained)

/shared/backups/github/swiss-knife $ vendor/bin/composer-unused
 3/8 [==========>-----------------]  37%
Fatal error: Uncaught Error: Call to undefined method PhpParser\Node\Expr\Variable::getParts() in /shared/backups/github/swiss-knife/vendor/composer-unused/symbol-parser/src/Parser/PHP/DefinedSymbolCollector.php on line 65

Error: Call to undefined method PhpParser\Node\Expr\Variable::getParts() in /shared/backups/github/swiss-knife/vendor/composer-unused/symbol-parser/src/Parser/PHP/DefinedSymbolCollector.php on line 65

Call Stack:

And with the patch/fix (I've provided), you'll get



Results
-------

Found 8 used, 0 unused, 0 ignored and 0 zombie packages

 Used packages
 ✓ php
 ✓ illuminate/container (https://github.com/illuminate/container)
 ✓ nette/robot-loader (https://github.com/nette/robot-loader)
 ✓ nette/utils (https://github.com/nette/utils)
 ✓ nikic/php-parser (https://github.com/nikic/PHP-Parser)
 ✓ symfony/console (https://github.com/symfony/console)
 ✓ symfony/finder (https://github.com/symfony/finder)
 ✓ webmozart/assert (https://github.com/webmozarts/assert)

 Unused packages

 Ignored packages

 Zombies exclusions (did not match any package)

@llaville
Copy link
Contributor

llaville commented Mar 7, 2024

@icanhazstring composer-unused/symbol-parser need a hotfix and a new release 0.2.3

@llaville
Copy link
Contributor

llaville commented Mar 8, 2024

@icanhazstring Thanks for new version https://github.com/composer-unused/symbol-parser/releases/tag/0.2.3, but the most important is missing to solve this issue.
See my patch suggest #610 (comment)

PS: this is the same issue as composer-unused/symbol-parser#122

@icanhazstring
Copy link
Member

Got it. If you would be so kind to just run a PR for that patch? Would gladly just release a new version 😉

@icanhazstring
Copy link
Member

Added to version 0.2.4

@llaville
Copy link
Contributor

llaville commented Mar 9, 2024

With this adventure, I've noticed that is was a bit difficult to know where the issue come from.

I propose to add the package name processing on right side of the progress bar (when verbose mode is set to level 1 : -v)

That means for example, when trying to analyse https://github.com/rectorphp/swiss-knife/, with vendor/bin/composer-unused command, rather than got

 3/8 [==========>-----------------]  37%
Fatal error: Uncaught Error: Call to undefined method PhpParser\Node\Expr\Variable::getParts() in /shared/backups/github/swiss-knife/vendor/composer-unused/symbol-parser/src/Parser/PHP/DefinedSymbolCollector.php on line 65

Error: Call to undefined method PhpParser\Node\Expr\Variable::getParts() in /shared/backups/github/swiss-knife/vendor/composer-unused/symbol-parser/src/Parser/PHP/DefinedSymbolCollector.php on line 65

Call Stack:

we can have this one :

 3/8 [==========>-----------------]  37% Processing nette/robot-loader package
Fatal error: Uncaught Error: Call to undefined method PhpParser\Node\Expr\Variable::getParts() in /shared/backups/github/swiss-knife/vendor/composer-unused/symbol-parser/src/Parser/PHP/DefinedSymbolCollector.php on line 65

Error: Call to undefined method PhpParser\Node\Expr\Variable::getParts() in /shared/backups/github/swiss-knife/vendor/composer-unused/symbol-parser/src/Parser/PHP/DefinedSymbolCollector.php on line 65

Call Stack:

Agree for a PR ?

@icanhazstring
Copy link
Member

Yes sounds like a good addition 👍

@llaville
Copy link
Contributor

@TomasVotruba You can removed symbol-parser constraint into https://github.com/rectorphp/swiss-knife/blob/main/composer.json#L20 to have benefits of new bugfixes version 0.2.5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants