rfc:nameof

Differences

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

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rfc:nameof [2023/05/13 07:14] – I think it is ready 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 51: Line 71:
 echo nameof($a === $b); // ?? echo nameof($a === $b); // ??
 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->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."
  
-===== Proposal =====+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. 
  
-The ''nameof()'' function is not necessarily a function in the traditional sense and can be used anywhere a string literal can be used (such as to define static constantsarray keys, and attributes)This means that there is an 'identityfor every valid ''nameof()'':+To continue the ''"property"'' example, it isn't clear what object "property" comes from without digging into the codeperhaps requiring debugging running codeWhen 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.
  
-<code PHP> +===== Proposal =====
-${nameof($variable)} === $variable; +
-$object->{nameof($object->property)} === $object->property; +
-constant(Enum::class . '::' . nameof(Enum::Case)) === Enum::Case; +
-constant(Object::class . '::' . nameof(Object::Const)) === Object::Const; +
-(nameof(myFunction(...))() === myFunction(); +
-constant(nameof(MY_CONSTANT)) === MY_CONSTANT; +
-</code>+
  
-When getting the name of constants and functions, the name will NOT be the full name, but the lexical name. This means that if the name is ''use'''d, it will be that name. However, if the full name is used in the ''nameof()''the full name is returnedIt's probably easiest to show this with an example:+''nameof'' operator will be introduced into PHP. During compilation, it will validate that all parts of the identifier existand if they do nota warning will be emitted. The right-most identifier will be turned into a string literal.
  
-<code PHP> +There is a special case for objects:
-namespace Name { +
-    const MY_CONST = true; +
-    function namedFunction() {} +
-}+
  
-namespace Other { +1. A ''::'' will denote a static property/method. 
-    echo nameof(\Name\MY_CONST); // \Name\MY_CONST +2. A ''->'' will denote an instanced property/method 
-    echo nameof(\Name\namedFunction(...))// \Name\namedFunction +3The left-hand side must be the type nameit can be an abstract type or an interface. 
-}+4. The left-hand side cannot be a variable.
  
-namespace Using { +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.
-    use const Name\MY_CONST as ; +
-    use const Name\MY_CONST as ALIASED_CONST; +
-    use function Name\namedFunction;+
  
-    echo nameof(MY_CONST); // MY_CONST +There are a limited number of expressions that can resolve to a name using ''nameof()'':
-    echo nameof(\Name\MY_CONST); // \Name\MY_CONST +
-    echo nameof(ALIASED_CONST); // ALIASED_CONST +
-    echo nameof(namedFunction(...)); // namedFunction +
-    echo nameof(\Name\namedFunction(...)); // \Name\namedFunction(...) +
-+
-</code>+
  
-===== Error Handling =====+  * variables (but not variable-variables): ''nameof($var)'' resolves to 'var' 
 +  * properties: ''nameof($this->prop)'', ''nameof(MyClass->prop)'' resolves to 'prop' 
 +  * first-class callables: ''nameof(strlen(...))'' resolves to 'strlen' 
 +  * static properties and constants: ''nameof(A::Prop)'' resolves to 'Prop' 
 +  * constants: ''nameof(MY_CONST)'' resolves to 'MY_CONST'
  
-There are several ways to handle errors, each one has its own merits but ultimately will be left up as secondary vote:+The name will always be fully qualified, with a beginning slash, if it is a first-class callable or constant and it refers to globally accessible identifier. For example:
  
-1. No error handling+<code PHP> 
 +namespace Example;
  
-In this case, there is no error handling. This would allow using ''nameof'' on things that don't exist. For example, no error would be output if a developer references a static property on an instanced property: +class 
- +  public function doStuff(): void {}
-<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.+function doStuff(): void {}
  
-2Warnings as usual+echo nameof(doStuff(...)); // \Example\doStuff 
 +echo nameof(A->doStuff(...)); // doStuff 
 +</code>
  
-In this casePHP would output warnings and errors a developer would expect to see if they used something that doesn't exist:+Additionallyconsider traits and aliasing:
  
 <code PHP> <code PHP>
-class MyClass +trait A 
-  public string $var;+  public function example() {}
 } }
-nameof(MyClass::var); // returns 'var' 
-// Uncaught Error: Undefined constant MyClass::var 
-nameof($aVariable); // returns 'aVariable' 
-// Warning: Undefined variable $aVariable 
-</code> 
  
-Warnings/errors can be disabled via the 'silence operator' (''@''to behave just like the first case. This is the best of both worlds, allowing developers to 'break the rules' to 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.+trait B { 
 +  public function example() {} 
 +}
  
-3Exceptions+class C { 
 +  use A, B { 
 +    A::example insteadof B; 
 +    B::example as exampleB; 
 +  } 
 +
 + 
 +echo nameof(C->exampleB(...)); // outputs: "exampleB" 
 +echo nameof(B->example(...)); // outputs: "example" 
 +</code>
  
-An exception model was consideredhoweverthe author believes that it would break the concept of "using ''nameof'' wherever a string could be used" and wouldn'be practical for the engine to handle all of those cases.+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 ideal, not to mention counter-intuitive.
  
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
Line 141: 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 152: 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 162: 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 172: 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 187: 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.1683962081.txt.gz · Last modified: 2023/05/13 07:14 by withinboredom