rfc:nameof

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
rfc:nameof [2023/05/13 20:15] – apply feedback withinboredomrfc:nameof [2024/01/14 22:49] (current) – reflect discussions withinboredom
Line 7: Line 7:
 ===== Introduction ===== ===== Introduction =====
  
-Currently, it is possible to inspect virtually any aspect of running code, except there is no easy way to inspect the locally executing scopeFor examplea developer can get the class of an object with ''get_class()'' or the name with ''MyClass::class'' or ''$myClass::class''A developer can get the name of a parameter or property via reflection, however, when a developer needs to write a simple error message, that may be overkillthus the variable name is usually hard-coded into the error message. This usually works fine until developer refactors the code and forgets to change the name of the variable in the error message.+During PHP execution, it is possible to reflect on most of the code currently running. For exampleit is easy to get the name of a class and using reflection utilities: names of properties, methods, and so onHowevergetting the name of a callable passed via ''(...)'' is complicated, even though the engine knows itGetting the name of a constant not bound to a class or a simple variable, is virtually impossible. This RFC proposes ''nameof'' operator that makes it relatively easy to get the name of virtually any identifier passed to it.
  
-''nameof()'' allows developers to quickly and easily access the name of a variable in the current scope, in a way that keeps error messages consistent.+This can be used in attributes:
  
-This RFC proposes a global ''nameof()'' function that allows a developer to get the name of virtually any user-named variable, property, const, or member, quickly and easily:+<code PHP> 
 +#[SerializeWith(nameof(Person->serialize))] 
 +class Person { 
 +  public function serialize() { /* do stuff */ } 
 +
 +</code> 
 + 
 +In error messages:
  
 <code PHP> <code PHP>
-echo nameof($variable); // variable +function divide(float $numerator, float $denominator): float { 
-echo nameof($object->property); // property +  if($denominator === 0) throw new InvalidArgumentException(nameof($denominator) . ' is zero'); 
-echo nameof(Enum::Case); /Case +
-echo nameof(Object::Const); // Const +</code> 
-echo nameof(myFunction(...)); // myFunction + 
-echo nameof(MY_CONSTANT); // MY_CONSTANT+In match statements: 
 + 
 +<code PHP> 
 +match($propertyName) { 
 +  nameof(MyClass->property) => $value, 
 +  nameof(MyClass->otherProperty=> $otherValue, 
 +
 +</code> 
 + 
 +Or anywhere a literal string can be used
 + 
 +<code PHP> 
 +$arr[nameof(myfunc(...))] = true;
 </code> </code>
  
-As named parameters become more prevalent, providing the end user with the name of the parameter is more important, in a way that it is easy to locate when refactoring, especially as part of a deprecation strategy:+As named parameters become more prevalent, providing the end user with the name of the parameter is more important than ever, in a way that it is easy to locate when refactoring, especially as part of a deprecation strategy:
  
 <code PHP> <code PHP>
Line 33: Line 52:
 </code> </code>
  
-Further, using ''nameof()'' would allow simpler usage of attributes, by using the silence operator (see error checking):+Here are some further examples showing how the identifier is transformed:
  
 <code PHP> <code PHP>
-// evaluates to #[EventHandler(callback'handler')] +echo nameof($variable); // variable 
-#[EventHandler(callback@nameof(Operator::handler))] +echo nameof(MyClass->property); // property 
-class Operator { +echo nameof(MyClass::staticProperty); // staticProperty 
-  public function handler() { +echo nameof(Enum::Case); // Case 
-    // implementation +echo nameof(Object::Const); // Const 
-  } +echo nameof(myFunction(...)); // myFunction 
-}+echo nameof(\MyNamespace\myFunction(...)); // \MyNamespace\myFunction 
 +echo nameof(MY_CONSTANT); // MY_CONSTANT
 </code> </code>
  
Line 52: Line 72:
 echo nameof($a($b)); // same as array access echo nameof($a($b)); // same as array access
 echo nameof($$a); // ask for $a or use the value of $a echo nameof($$a); // ask for $a or use the value of $a
 +echo nameof($a->prop); // what is $a?
 </code> </code>
  
-All of these will generate a compile error: "Cannot get the name of an ambiguous expression."+These will generate a compile error: "Cannot get the name of an ambiguous expression." 
 + 
 +Note that only the right-most part of the identifier is translated to a string. This is because this is almost always what you want. For example, in the case of ''nameof(MyClass->property)'', you are most likely not interested in the name of ''MyClass'' (otherwise, you'd ask for the ''MyClass::class''), but you are interested in the name of ''property'' because that is what you asked for. The key difference between writing ''"property"'' and ''nameof(MyClass->property)'' comes down to humans reading and modifying the code. It is much safer to refactor code when you know the pedigree of a string that should also be refactored.  
 + 
 +To continue the ''"property"'' example, it isn't clear what object "property" comes from without digging into the code, perhaps requiring debugging running code. When it is clear that it comes from ''MyClass'', you can know from whence it came. You can also grep the codebase for "MyClass->property" and locate all usages.
  
 ===== Proposal ===== ===== Proposal =====
  
-The ''nameof()'' function is not necessarily function in the traditional sense and can be used anywhere a string literal can be used (such as to define static constants, array keys, and attributes)This means that there is an 'identity' for every valid ''nameof()'':+''nameof'' operator will be introduced into PHP. During compilation, it will validate that all parts of the identifier exist, and if they do notwarning will be emitted. The right-most identifier will be turned into a string literal.
  
-<code PHP> +There is a special case for objects: 
-${nameof($variable)} === $variable; + 
-$object->{nameof($object->property)} === $object->property+1. A ''::'' will denote a static property/method. 
-constant(Enum::class . '::'nameof(Enum::Case)) === Enum::Case; +2. A ''->'' will denote an instanced property/method 
-constant(Object::class . '::' . nameof(Object::Const)) === Object::Const; +3. The left-hand side must be the type nameit can be an abstract type or an interface. 
-(nameof(myFunction(...))() === myFunction(); +4. The left-hand side cannot be a variable. 
-constant(nameof(MY_CONSTANT)) === MY_CONSTANT; + 
-</code>+If the first two rules are broken (e.g., a ''::'' where a ''->'' should be used), a warning will be emitted: "name of a static property/method is asked for, using instanced syntax." If the 3rd rule is broken (e.g., a type cannot be found), then the standard error will be emitted for a missing type, and if the 4th rule is broken, a compilation error will be emitted"cannot get the name of a variables property/method." The exceptions for rule 4 are ''$this'', ''static::'', and ''self::'', which may be used on the left-hand side in appropriate contexts.
  
 There are a limited number of expressions that can resolve to a name using ''nameof()'': There are a limited number of expressions that can resolve to a name using ''nameof()'':
  
   * variables (but not variable-variables): ''nameof($var)'' resolves to 'var'   * variables (but not variable-variables): ''nameof($var)'' resolves to 'var'
-  * properties: ''nameof($this->prop)'', ''nameof($a?->prop)'' resolves to 'prop'+  * properties: ''nameof($this->prop)'', ''nameof(MyClass->prop)'' resolves to 'prop'
   * first-class callables: ''nameof(strlen(...))'' resolves to 'strlen'   * first-class callables: ''nameof(strlen(...))'' resolves to 'strlen'
   * static properties and constants: ''nameof(A::Prop)'' resolves to 'Prop'   * static properties and constants: ''nameof(A::Prop)'' resolves to 'Prop'
   * constants: ''nameof(MY_CONST)'' resolves to 'MY_CONST'   * constants: ''nameof(MY_CONST)'' resolves to 'MY_CONST'
  
-When getting the name of constants and functions, the name will NOT be the full namebut the lexical name. This means that if the name is ''use'''d, it will be that nameHowever, if the full name is used in the ''nameof()'', the full name is returned. It's probably easiest to show this with an example:+The name will always be fully qualified, with a beginning slash, if it is a first-class callable or constant and it refers to a globally accessible identifierFor example:
  
 <code PHP> <code PHP>
-namespace Name { +namespace Example;
-    const MY_CONST = true; +
-    function namedFunction() {} +
-}+
  
-namespace Other +class A 
-    echo nameof(\Name\MY_CONST); // \Name\MY_CONST +  public function doStuff(): void {}
-    echo nameof(\Name\namedFunction(...)); // \Name\namedFunction+
 } }
  
-namespace Using { +function doStuff(): void {}
-    use const Name\MY_CONST as ; +
-    use const Name\MY_CONST as ALIASED_CONST; +
-    use function Name\namedFunction;+
  
-    echo nameof(MY_CONST); // MY_CONST +echo nameof(doStuff(...)); // \Example\doStuff 
-    echo nameof(\Name\MY_CONST); // \Name\MY_CONST +echo nameof(A->doStuff(...)); // doStuff
-    echo nameof(ALIASED_CONST); // ALIASED_CONST +
-    echo nameof(namedFunction(...)); // namedFunction +
-    echo nameof(\Name\namedFunction(...)); // \Name\namedFunction(...) +
-}+
 </code> </code>
- 
-In other language implementations (such as C#), a full name usually isn't returned even if you pass one in. However, a 'full name' is rarely required and can often be constructed from parts, if required. However, as a convenience, in this implementation, if a 'full name' is requested entered, a full name is returned. This allows for the 'least surprises' since what you put in is what you get out. 
  
 Additionally, consider traits and aliasing: Additionally, consider traits and aliasing:
Line 123: Line 135:
 } }
  
-$a = new C(); +echo nameof(C->exampleB(...)); // outputs: "exampleB" 
-echo nameof($a->exampleB(...)); // outputs: "exampleB"+echo nameof(B->example(...)); // outputs: "example"
 </code> </code>
  
-In the example aboveif we were to return the 'full nameof ''$a->exampleB(...)'' it would actually be 'B::example' but that isn't helpful in the context of class ''C''. By returning the lexical namethe developer gets the response they expect. +This adheres to the basic refactoring safety mentioned earliersince refactoring ''B->example()'' will show up in ''C'', which will then be found by looking for ''C->exampleB'', if it needs to be refactored at all. If ''nameof(C->exampleB)'' returned 'example,' other code may accidentally call ''C->example()'', which would not be idealnot to mention counter-intuitive.
- +
-===== Error Handling ===== +
- +
-There are several ways to handle errors, each one has its own merits but ultimately will be left up as a secondary vote. It may not provide the **best** solution, but the author doesn't have a personal preference: +
- +
-1. No error handling +
- +
-In this case, there is no error handling. This would allow using ''nameof'' on things that don't exist. For exampleno error would be output if a developer references a static property on an instanced property: +
- +
-<code PHP> +
-class MyClass { +
-  public string $var; +
-+
-nameof(MyClass::var); // no error, returns 'var' +
-nameof($aVariable); // no error, returns 'aVariable' +
-</code> +
- +
-The downside is that PHP won't natively inform a developer that they've used something that doesn't exist. The main benefit of this approach is that it would allow the community to come up with its own rules via static analysis tools. +
- +
-2. Warnings +
- +
-In this case, PHP would output a warning: "nameof(<name>): does not exist." where <name> is the name the developer passed. +
- +
-Using a class name (or any other method that would normally trigger autoloading) that does not exist, will trigger autoloading and attempt to locate the class. If it is not found, a warning will be issued. If a variable does not exist in the current scope, a warning will be issued. If the referenced property/method/const cannot be located, a warning will be issued. If a function or constant cannot be located in the current scope, a warning will be issued. +
- +
-Warnings can be disabled via the 'silence operator' (''@'') to behave just like the first case. This is the best of both worldsallowing developers to 'break the rulesto specify properties/functions without a reference to the object (''@nameof(Object::method(...))'' vs. ''nameof($object->method)'') but would require assistance via static analysis tooling to catch mistakes. +
- +
-3. Exceptions +
- +
-An exception model was consideredhowever, the author believes that it would break the concept of "using ''nameof'' wherever a string could be used". For exampleif a name cannot be located, an exception would be thrown. This would be true in the case of simply defining an attribute with a non-existent name: +
- +
-#[EventHandler(handler: nameof(I_DO_NOT_EXIST))] +
- +
-which is probably undesirable and nearly impossible to handle in a graceful way.+
  
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
Line 172: Line 150:
 ===== Proposed PHP Version(s) ===== ===== Proposed PHP Version(s) =====
  
-  * 8.3: release +  * 8.4: release 
-  * 8.2.x: ''nameof'' becomes a reserved word, emitting a notice+  * 8.3.x: ''\nameof'' becomes a reserved word, emitting a deprecation warning.
  
 ===== RFC Impact ===== ===== RFC Impact =====
Line 183: Line 161:
  
 ==== To Opcache ==== ==== To Opcache ====
-Depends on error type vote. 
  
-First case: compiled to a string literal. +a new AST node (similar to isset) will need to be handled.
- +
-Second and third case: a new AST node (similar to isset) will need to be handled.+
  
 ==== New Constants ==== ==== New Constants ====
Line 193: Line 168:
  
 ===== Open Issues ===== ===== Open Issues =====
-- Should first-class callables be allowed? It's relatively easy to get the name of a first-class callable and only included for completeness. 
  
 ===== Unaffected PHP Functionality ===== ===== Unaffected PHP Functionality =====
-PHP will largely be unaffected by this change, except that a new global function is introduced.+PHP will largely be unaffected by this change.
  
 ===== Future Scope ===== ===== Future Scope =====
Line 203: Line 177:
 ===== Proposed Voting Choices ===== ===== Proposed Voting Choices =====
 This is a simple yes-or-no vote to include this feature. 2/3 majority required to pass. This is a simple yes-or-no vote to include this feature. 2/3 majority required to pass.
- 
-There is a subvote for the type of error checking to include. Majority wins. 
  
 ===== Patches and Tests ===== ===== Patches and Tests =====
  
-experimental implementation: https://github.com/php/php-src/pull/11172/files+experimental implementation: https://github.com/php/php-src/pull/11172/files (to be updated)
  
 ===== Implementation ===== ===== Implementation =====
Line 218: Line 190:
  
 ===== References ===== ===== References =====
-Links to external references, discussions or RFCs+- internals discussion: https://externals.io/message/120256
  
 ===== Rejected Features ===== ===== Rejected Features =====
-Keep this updated with features that were discussed on the mail lists.+ 
 +Classes need not be supported by ''nameof()'' because there is already an easy way to get the class via ''::class''. This would also be ambiguous when a class name matches a constant name. 
 + 
 +There were some suggestions to use ''::name'' instead of ''nameof''; however, this introduces some ambiguity. Take, for example: 
 + 
 +<code PHP> 
 +class A { 
 + public const name = 'bob'; 
 +
 + 
 +function example(): A { 
 + return new A(); 
 +
 + 
 +echo example()::name; 
 +echo A::name; 
 +</code> 
 + 
 +The author believes ''name'' is a rather popular name for variables and would inevitably confuse things. In the example above, "bob" is output, but it looks like we could be getting the name of ''example'' if we don't look hard enough. Further, it prevents us from getting the names of constants: 
 + 
 +<code PHP> 
 +const A = 'billy'; 
 + 
 +class A { 
 + public const name = 'bob'; 
 +
 + 
 +echo A::name; 
 +</code> 
 + 
 +Are we getting the string "A" or "bob" in this example? As it is currently, we would output "bob," and the author feels rather strongly that this is correct. Thus, something like ''::name'' is rejected.
rfc/nameof.1684008903.txt.gz · Last modified: 2023/05/13 20:15 by withinboredom