Psalm: Add support for function::class, constant::class

Created on 26 Nov 2020  Â·  12Comments  Â·  Source: vimeo/psalm

Most helpful comment

Currently PHP has three distinct symbol tables and two of them support fallback to global namespace (const and function). Two (different two, class and function namely) are case-insensitive, const symbol table is case-sensitive. This makes the following code valid:

namespace A;
class Thing {}

namespace B;
function Thing() {}

namespace C;
const thing = 123;

namespace Z;
use A\thing;
use function B\thing;
use const C\thing;

$_a = thing::class;

As it stands now $_a is unambiguously treated as class-string<\A\thing>.
Allowing ::class on things other than classes/interfaces/traits (all belonging to a single symbol table) would introduce ambiguity, which would lead to false positives (and negatives) so this is no-go from my point of view.

This could be resolved if PHP merged all symbol tables, but it's not going to happen until all of them behave the same way in regards to namespace resolution and case-sensitivity (so, PHP 11 if we're lucky). This has been discussed in some detail here: https://externals.io/message/100535, to add more week-end read :smile:.

All 12 comments

I found these snippets:


https://psalm.dev/r/92cd7736da

<?php

var_dump(array_map(strval::class, [1, 2]));

var_dump(PHP_EOL::class);
Psalm output (using commit 74c07bb):

ERROR: UndefinedClass - 3:20 - Class or interface strval does not exist

INFO: MixedArgument - 3:20 - Argument 1 of array_map cannot be mixed, expecting callable|null

ERROR: ForbiddenCode - 3:1 - Unsafe var_dump

ERROR: UndefinedClass - 5:10 - Class or interface PHP_EOL does not exist

ERROR: ForbiddenCode - 5:1 - Unsafe var_dump

Not sure what the benefit would be. Do you have an example of concrete code using those?

No, it does not work the way you think it does: https://3v4l.org/Z8eMP

But it does this way https://3v4l.org/XTOLH

@weirdan , your example doesn't work, because use is not taken into account when used with ::class. I think, this is a PHP mistake which should be fixed in PHP.

See https://3v4l.org/DA24O

Imho, the issue should be reopened, it's a valid use case.

@orklah , any use case where you use a function name as a callable argument

var_dump(
    array_map(
        htmlspecialchars::class,
        ['<', '>']
    )
);

@orklah , any use case where you use a function name as a callable argument

Yeah, but this is basically a glorified string factory. I mean echo hello::class.World::class; works pretty well too but apart from allowing you to use more chars to represent a string, I still fail to see the point.

@orklah , when you use your own namespaced functions in the project (we often do, because functions are handy in a lot of situations), referencing them with ::class allows for refactoring. And it also could allow for shorter references ('my\namespace\function' > function::class) if it took use func into account.

if it took use func into account.

FYI: the real feature you're asking for has been discussed already on internals mailing list
https://externals.io/message/112201
https://externals.io/message/108459

There are a lof of response as to why it doesn't exists yet.

I don't think it's a good idea to have this in Psalm before it's coherent in core language. However, maybe @muglug would accept a PR with a opt-in config that just takes whatever word you put before ::class and just turn it into a literal string instead of emitting an error (kinda like string to class-string coercion) ? This would probably solve your issues

Okay, I realized what this phrase in the documentation means: "The class name resolution using ::class is a compile time transformation". So ::class just finds the used class and outputs its name regardless of existence. That's why it doesn't work with functions — it just thinks that it's a class in the current namespace.

@orklah , thank you for the link!

Currently PHP has three distinct symbol tables and two of them support fallback to global namespace (const and function). Two (different two, class and function namely) are case-insensitive, const symbol table is case-sensitive. This makes the following code valid:

namespace A;
class Thing {}

namespace B;
function Thing() {}

namespace C;
const thing = 123;

namespace Z;
use A\thing;
use function B\thing;
use const C\thing;

$_a = thing::class;

As it stands now $_a is unambiguously treated as class-string<\A\thing>.
Allowing ::class on things other than classes/interfaces/traits (all belonging to a single symbol table) would introduce ambiguity, which would lead to false positives (and negatives) so this is no-go from my point of view.

This could be resolved if PHP merged all symbol tables, but it's not going to happen until all of them behave the same way in regards to namespace resolution and case-sensitivity (so, PHP 11 if we're lucky). This has been discussed in some detail here: https://externals.io/message/100535, to add more week-end read :smile:.

Thank you very much for your explanations!

Was this page helpful?
0 / 5 - 0 ratings