Skip to content

add nameof operator #11172

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Conversation

withinboredom
Copy link
Contributor

@withinboredom withinboredom commented May 1, 2023

After seeing some discussions about using enum values as keys, I thought of my own implementation of nameof (from C#) that is very hackish: https://github.com/withinboredom/nameof

I'd like to propose making it a real thing (with better behavior than my hack). An operator like this has a few niche uses, mostly for better error messages but also for making strings of things that you normally can't get the string value of. Here are some examples:

nameof(func(...)) === 'func'
nameof($party) === 'party'
nameof($captain->planet) === 'planet'
nameof(Class::Prop) === 'Prop'

I first attempted this as an extension, but this belongs at the parsing/compiling level and not during runtime (this gets turned into a constant zval).

I use my (very hacky) implementation to handle stringifying enums (mostly), as well as better, easier-to-refactor error messages:

throw new InvalidArgumentException(nameof($var) . ' has an unexpected value');

Without it, the name of the variable can be missed in a refactor, making the error message useless or incorrect.

The code to make this possible (without much error checking) is relatively simple

@TimWolla
Copy link
Member

TimWolla commented May 1, 2023

This most certainly requires an RFC. The RFC process is described at: https://wiki.php.net/rfc/howto. Would you be willing to pursue your proposal with the RFC process?

@withinboredom
Copy link
Contributor Author

I sent a discussion email per the RFC at the same time as this PR: https://externals.io/message/120173

@withinboredom
Copy link
Contributor Author

@TimWolla just out of curiosity and a feeling of foreboding after nearly a week of waiting:

  • There was no response to my pre-discussion thread.
  • I was not granted RFC privileges after asking for them.

Did I fall through some cracks, did I do something wrong, or is this a way of saying "We're not interested?"

@TimWolla
Copy link
Member

TimWolla commented May 7, 2023

I was not granted RFC privileges after asking for them.

There is a very limited number of folks that are able to grant the necessary permissions (we should change that) and I am not able to grant the permissions. I've already asked in your behalf in StackOverflow chat, but did not receive a response either.

There was no response to my pre-discussion thread.

This doesn't necessarily surprise me, the proposal is pretty clear and some folks probably will only comment on the real discussion to not unnecessarily split it into several locations.

@withinboredom
Copy link
Contributor Author

Thanks @TimWolla, it's good to know there is something happening somewhere. I'll patiently wait and maybe bug the list again in another day or two.

@derickr
Copy link
Member

derickr commented May 7, 2023

@withinboredom You should have karma now.

zend_ast *var_ast = ast->child[0];
zend_ast *name_ast;

// todo: proper error handling and better error messages

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are your ideas for proper error handling? Happy to thinker with you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MelvinRook, I'm actually still trying to come up with something in the RFC that makes sense (and will probably come up in discussions). For example, should this be an error and what might the error message be:

$x = null;
unset($x);
echo nameof($x);

In this specific implementation, no error/warning message is shown if you use a non-existent variable/method/member/etc, nor is any autoloading triggered. This might be desired for performance/optimization reasons, but the entire point is to make refactoring of messages/strings easier, thus inconsistencies could be missed.

If a message is emitted, what should be emitted? Notices, warnings, errors, exceptions? I personally, would favor a normal message, so the above would emit a warning ("Undefined variable $x in ...") but I can see how other people might want something special to happen.

Lots to think about and I haven't particularly settled on any particular implementation. I'd love to hear any arguments for any of them.

break;
default:
zend_error_noreturn(E_COMPILE_ERROR,
"Cannot use nameof() on the result of an expression or something with an ambiguous name");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can "something" be more specific in this error, or is it as specific as it can be?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes and yes; that's actually a good question. I imagine that given more examples, more helpful error messages can be created, but there are "somethings" that are ambiguous or don't make sense:

echo nameof($a['b']);
echo nameof(empty($x));
echo nameof($x === $y);
echo nameof('literal');

In the grammar, I chose an expression as what can go in the brackets/parenthesis, but it doesn't make sense to accept literally any expression. Perhaps there is something better to use (or construct), but I didn't want to spend too much time on it in case there was a name for these more ambiguous things that I don't have the imagination for.

@sybrew
Copy link

sybrew commented Oct 3, 2023

Instead of a function, wouldn't something like ::class be better?
E.g., ::name:

echo $variable::name; // variable
echo ${$variable}::name; // variable
echo $object->property::name; // property
echo Object::$property::name; // property
echo Enum::Case::name; // Case
echo Object::Const::name; // Const
echo myFunction()::name; // myFunction
echo MY_CONSTANT::name; // MY_CONSTANT

etc.

@withinboredom
Copy link
Contributor Author

withinboredom commented Oct 3, 2023

Thanks for your feedback @sybrew, but there's one issue that precludes that syntax from actually working: ambiguity.

For example, let's start with some code:

class Test
{
    public const name = 2;

    public function __construct()
    {
        echo 'hello world';
    }
}

echo Test::name;

In your proposal, what should the output be? The reason Test::class works is because class is a keyword, thus this is a syntax error:

class Test
{
    public const class = 2; // <---- syntax error

    public function __construct()
    {
        echo 'hello world';
    }
}

echo Test::class;

Second, the nameof() operator is an operator, not a function. The code inside the parenthesis is only compiled so far, just to determine the name of what is inside of it, which turns into a string by the time the engine is actually executing the code.

Thirdly, "better" is purely subjective. I think ::name looks terrible. That being said, other than I think it looks hard to read, there is nothing technically wrong with it, besides the ambiguity.

@rodrigoslayertech
Copy link

rodrigoslayertech commented Apr 7, 2024

Forgive my ignorance, but...
Is it possible to add an update in the future to get the name of the variable when used with variadic parameters?

Something like:

class Template {
    // ...

    public function use (...$vars) {
        $var_names = [];
        foreach ($vars as $var) {
                $var_names[] = nameof($var);
        }
        // $var_names[0] = 'foo'
        // $var_names[1] = 'bar'
    }

    // ...
}

$Template = new Template;
$foo = "bar";
$bar = 'barz';
$Template->use($foo, $bar);

I believe that it currently works by taking the literal name of $var which is 'var', but is it possible, already inside the function, to get the name of the variable that was passed in the function's argument?

@withinboredom
Copy link
Contributor Author

Yes, nameof($var) would be "var", if you want to get the name of a variadic argument:

class Template {
    // ...

    public function use (...$vars) {
        $var_names = [];
        foreach ($vars as $name => $var) {
                $var_names[] = $name;
        }
        // $var_names[0] = 'foo'
        // $var_names[1] = 'bar'
    }
}

@withinboredom
Copy link
Contributor Author

withinboredom commented Apr 8, 2024

An interesting addendum would be to allow this:

$Template = new Template;
$foo = "bar";
$bar = 'barz';
$Template->use(nameof($foo): $foo, nameof($bar): $bar);

Though it would be simpler to just:

$Template = new Template;
$foo = "bar";
$bar = 'barz';
$Template->use(...compact(nameof($foo), nameof($bar)));

It'd be nice to create a better syntax than nameof() but I don't have anything yet. It's the entire holdup here.

@user451421541757324
Copy link

Any updates? This would be a nice feature to have.

@withinboredom
Copy link
Contributor Author

Heh, good timing @user451421541757324 (love the username btw). I plan on putting this to a vote early next month and reintroducing it to the list some time in the next few days -- after rebasing this PR and fixing a few minor bugs.

@user451421541757324
Copy link

Heh, good timing @user451421541757324 (love the username btw). I plan on putting this to a vote early next month and reintroducing it to the list some time in the next few days -- after rebasing this PR and fixing a few minor bugs.

Great news, thanks a lot for your work.

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

Successfully merging this pull request may close these issues.

7 participants