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

Argument 1 of SimpleXMLElement::attributes cannot be null - Irreproduceable in sandbox #8603

Open
jnvsor opened this issue Oct 19, 2022 · 3 comments
Labels

Comments

@jnvsor
Copy link
Contributor

jnvsor commented Oct 19, 2022

I'm getting an error in real code that I can't reproduce in the sandbox:

ERROR: PossiblyNullArgument - [...] - Argument 1 of SimpleXMLElement::attributes cannot be null, possibly null value provided (see https://psalm.dev/078)
ERROR: PossiblyNullArgument - [...] - Argument 1 of SimpleXMLElement::children cannot be null, possibly null value provided (see https://psalm.dev/078)

In the sandbox I pass in null and it works just fine, and the sandbox and my local psalm are on the same commit...

On top of that:

  • This error occurs even when I set psalm to different php version targets
  • After clearing the cache
  • It even happens in a GHA run

So I have no idea what the cause could be.

I assume there's some strange edge case in my code causing this error, but since SimpleXMLElement::attributes absolutely can take null on the first argument, the error still seems wrong.

The line in question:

https://github.com/jnvsor/kint/blob/5bf5527e2b51ff3edfe438c46216d18be3fab1a4/src/Parser/SimpleXMLElementPlugin.php#L99

The closest potential hint is that psalm (and the PHP docs) incorrectly say the result of SimpleXMLElement::getDocNamespaces is array|false (Probably because libxml2's xmlDocGetRootElement can return null?) but according to https://3v4l.org/Qenk7 there's no version where false is returned. Maybe there is an edge case but I can't think of one.

Anyway, even with that hint it would be complaining about false, not null, since null is a valid input.

So I'm stumped. Any ideas?

Is there a verbose debug mode or some annotation that makes psalm print its types at that point of execution I could use to debug this further?

@psalm-github-bot
Copy link

Hey @jnvsor, can you reproduce the issue on https://psalm.dev ?

@danog
Copy link
Collaborator

danog commented Oct 19, 2022

Are you running the latest version of dev-master?

You can use /** @psalm-trace $something */; to debug psalm types.

@jnvsor
Copy link
Contributor Author

jnvsor commented Oct 19, 2022

Version says Psalm dev-master@212281dbcd17700bd6a577a61cbd088b7e0c8d13

Thanks for the info on psalm-trace, but it's made things even more confusing:

INFO: Trace - src/Parser/SimpleXMLElementPlugin.php:99:9 - $namespaces: array{0: null|string}<array-key, string> (see https://psalm.dev/224)
        /** @psalm-trace $namespaces */
        foreach ($namespaces as $nsAlias => $nsUrl) {
            /**
             * @psalm-trace $nsAlias
             * @psalm-trace $nsUrl
             */
            if ($nsAttribs = $var->attributes($nsUrl)) {
                $cleanAttribs = [];
                foreach ($nsAttribs as $name => $attrib) {
                    $cleanAttribs[(string) $name] = $attrib;
                }

                if (null === $nsUrl) {
                    $obj = clone $base_obj;
                    if ($obj->access_path) {
                        $obj->access_path .= '->attributes()';
                    }

                    $a->contents = $this->parser->parse($cleanAttribs, $obj)->value->contents;
                } else {
                    $obj = clone $base_obj;
                    if ($obj->access_path) {
                        $obj->access_path .= '->attributes('.\var_export($nsAlias, true).', true)';
                    }

                    $cleanAttribs = $this->parser->parse($cleanAttribs, $obj)->value->contents;

                    foreach ($cleanAttribs as $attribute) {
                        $attribute->name = $nsAlias.':'.$attribute->name;
                        $a->contents[] = $attribute;
                    }
                }
            }
        }


INFO: Trace - src/Parser/SimpleXMLElementPlugin.php:104:13 - $nsAlias: array-key (see https://psalm.dev/224)
            /**
             * @psalm-trace $nsAlias
             * @psalm-trace $nsUrl
             */
            if ($nsAttribs = $var->attributes($nsUrl)) {
                $cleanAttribs = [];
                foreach ($nsAttribs as $name => $attrib) {
                    $cleanAttribs[(string) $name] = $attrib;
                }

                if (null === $nsUrl) {
                    $obj = clone $base_obj;
                    if ($obj->access_path) {
                        $obj->access_path .= '->attributes()';
                    }

                    $a->contents = $this->parser->parse($cleanAttribs, $obj)->value->contents;
                } else {
                    $obj = clone $base_obj;
                    if ($obj->access_path) {
                        $obj->access_path .= '->attributes('.\var_export($nsAlias, true).', true)';
                    }

                    $cleanAttribs = $this->parser->parse($cleanAttribs, $obj)->value->contents;

                    foreach ($cleanAttribs as $attribute) {
                        $attribute->name = $nsAlias.':'.$attribute->name;
                        $a->contents[] = $attribute;
                    }
                }
            }


INFO: Trace - src/Parser/SimpleXMLElementPlugin.php:104:13 - $nsUrl: string (see https://psalm.dev/224)
            /**
             * @psalm-trace $nsAlias
             * @psalm-trace $nsUrl
             */
            if ($nsAttribs = $var->attributes($nsUrl)) {
                $cleanAttribs = [];
                foreach ($nsAttribs as $name => $attrib) {
                    $cleanAttribs[(string) $name] = $attrib;
                }

                if (null === $nsUrl) {
                    $obj = clone $base_obj;
                    if ($obj->access_path) {
                        $obj->access_path .= '->attributes()';
                    }

                    $a->contents = $this->parser->parse($cleanAttribs, $obj)->value->contents;
                } else {
                    $obj = clone $base_obj;
                    if ($obj->access_path) {
                        $obj->access_path .= '->attributes('.\var_export($nsAlias, true).', true)';
                    }

                    $cleanAttribs = $this->parser->parse($cleanAttribs, $obj)->value->contents;

                    foreach ($cleanAttribs as $attribute) {
                        $attribute->name = $nsAlias.':'.$attribute->name;
                        $a->contents[] = $attribute;
                    }
                }
            }


ERROR: PossiblyNullArgument - src/Parser/SimpleXMLElementPlugin.php:104:47 - Argument 1 of SimpleXMLElement::attributes cannot be null, possibly null value provided (see https://psalm.dev/078)
            if ($nsAttribs = $var->attributes($nsUrl)) {

So it correctly identifies $namespaces as an array of strings where the first element can be null, but when looping over it the element type is supposedly type string (It should be ?string) and it's immediately thereafter complaining that it's a "Possibly null value" in an argument where that should be fine

Lots of contradictions in there

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

No branches or pull requests

3 participants