Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

"::class resolution as scalar" Feature #187

Closed
wants to merge 3 commits into from

6 participants

Ralph Schindler Xinchen Hui Lars Strojny Nikita Popov Michael Moravec Sebastian Bergmann
Ralph Schindler

FOR RFC: https://wiki.php.net/rfc/class_name_scalars

Patch addresses:

  • Allows for Name::class, self::class, static::class, parent::class resolution to a scalar based on current use rules and current namespace.
  • Reuses existing keyword "class"
  • Alters zend_compile.c\zend_resolve_class_name() to facilitate compile time class name resolution (and runtime by way of FCALL)
Ralph Schindler "::class resolution as scalar" Feature
* Allows for Name::class, self::class, static::class, parent::class resolution to a scalar based on current use rules and current namespace.
* Reuses existing keyword "class"
* Alters zend_compile.c\zend_resolve_class_name() to facilitate compile time class name resolution (and runtime by way of FCALL)
76703a0
Ralph Schindler "::class" Feature:
* Added class scope checking for self,parent+static
* Fixed tab/spaces in phpt file
26d592a
Xinchen Hui

just a quick test with your patch, got a memleak:

<?php

class b {
  public function dummy() {
      echo self::class;
    }
}

lead to a memleak:

[Sun Sep  9 22:19:52 2012]  Script:  '/tmp/1.php'
Zend/zend_language_scanner.l(1907) :  Freeing 0x2B1498A80288 (5 bytes), script=/tmp/1.php
=== Total 1 memory leaks detected ===
Ralph Schindler

I've located the leak and will work the fix out, thanks- (I forgot to compile with --enable-debug).

Ralph Schindler "::class" Feature
* Fixed memory leak by adding zval_dtor() on class_name in zend_compile.c's zend_resolve_class_name()
41a10e7
Sebastian Bergmann sebastianbergmann commented on the diff September 12, 2012
Zend/tests/class_name_scalar.phpt
... ...
@@ -0,0 +1,56 @@
  1
+--TEST--
  2
+class name as scaler from ::class keyword
2

Nitpicking: scaler -> scalar

Indeed. Thanks for pointing this out.

I am aware that this implementation needs more work (there are additional edge cases). But I wanted to bring it to a vote to ensure that additional work on this feature would not ultimately be all-for-naught, as outside of core/master inclusion, this feature is effectively useless.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Lars Strojny

I am working on a few more tests and some remaining issues (parent::class in type hints and constants e.g.)

Lars Strojny

@ralphschindler would love to get this merged, as the RFC ended positively. Could you drop me a message to discuss the remaining work?

Ralph Schindler

I need to rework the patch as per the conversations that were had on the internals list. My plan is to do it during the holidays this week (American Thanksgiving), so I should be able to get this updated by end of week.

In its current state, it's not good for merge.

Lars Strojny

@ralphschindler, cool, thanks! If you need help with anything, drop me a note.

Lars Strojny

@ralphschindler ping again.

Nikita Popov

@ralphschindler By the way, if you want this in 5.5 you need to hurry. We're close to feature freeze ;)

Ralph Schindler

@nikic I have it mostly working, in two different forms, any chance you could help me bring it to completion?

Nikita Popov

@ralphschindler Depends on how much help you need, but in principle yes :) What are you still missing / what issues are you having with the implementation?

Ralph Schindler

@nikic I will push what I have to my github account, and give you a summary of the decisions I need to make.

Mostly, I need to decide how to handle situations like this:

class Foo {
    public function bar(static::class $foo, parent::class $bar, self::class $baz) {}
}

I believe I had self::class working in that scenario, but the other two were not with the current implementation of the FETCH_CLASS_CONST.

Nikita Popov

@ralphschindler Assuming you mean $foo = static::class etc.

Parameter initializers (and all other initializers) in PHP only accept compile-time scalars. As static::class and parent::class are not resolvable at compile time they should just throw an error there. parent::class is theoretically resolvable at compile-time (because it only needs the name and not the actual CE) and you can obtain it by checking the op array for the FETCH_CLASS op of the class declaration. But I don't think that it's worth adding such a hack for something nobody will do anyway (what's supposed to be the use for initializing a parameter to the name of the parent class?)

Lars Strojny

I agree with @nikic here. If parent and static throw an error, that’s more than fine.

Ralph Schindler

Since I decided to go in a different direction with the implementation and wanted to keep a clean commit history, I re-branched from master and applied my fixes there and will open up a new PR. see #256

Ralph Schindler ralphschindler closed this January 09, 2013
Michael Moravec

@ralphschindler: You don't need a new pull request, just force-push to this branch, the history will update here.

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

Showing 3 unique commits by 1 author.

Sep 07, 2012
Ralph Schindler "::class resolution as scalar" Feature
* Allows for Name::class, self::class, static::class, parent::class resolution to a scalar based on current use rules and current namespace.
* Reuses existing keyword "class"
* Alters zend_compile.c\zend_resolve_class_name() to facilitate compile time class name resolution (and runtime by way of FCALL)
76703a0
Sep 08, 2012
Ralph Schindler "::class" Feature:
* Added class scope checking for self,parent+static
* Fixed tab/spaces in phpt file
26d592a
Sep 10, 2012
Ralph Schindler "::class" Feature
* Fixed memory leak by adding zval_dtor() on class_name in zend_compile.c's zend_resolve_class_name()
41a10e7
This page is out of date. Refresh to see the latest.
56  Zend/tests/class_name_scalar.phpt
... ...
@@ -0,0 +1,56 @@
  1
+--TEST--
  2
+class name as scaler from ::class keyword
  3
+--FILE--
  4
+<?php
  5
+
  6
+namespace Foo\Bar {
  7
+    class One {}
  8
+    class Two extends One {
  9
+        public static function run() {
  10
+            var_dump(self::class);
  11
+            var_dump(static::class);
  12
+            var_dump(parent::class);
  13
+            var_dump(Baz::class);
  14
+        }
  15
+    }
  16
+    class Three extends Two {}
  17
+    echo "In NS\n";
  18
+    var_dump(Moo::CLASS); // resolve in namespace
  19
+}
  20
+
  21
+namespace {
  22
+    use Bee\Bop as Moo,
  23
+        Foo\Bar\One;
  24
+    echo "Top\n";
  25
+    var_dump(One::class); // resolve from use
  26
+    var_dump(Boo::class); // resolve in global namespace
  27
+    var_dump(Moo::CLASS); // resolve from use as
  28
+    var_dump(\Moo::Class); // resolve fully qualified
  29
+    $class = One::class; // assign class as scalar to var
  30
+    $x = new $class; // create new class from original scalar assignment
  31
+    var_dump($x);
  32
+    Foo\Bar\Two::run(); // resolve runtime lookups
  33
+    echo "Parent\n";
  34
+    Foo\Bar\Three::run(); // resolve runtime lookups with inheritance
  35
+}
  36
+
  37
+?>
  38
+--EXPECTF--
  39
+In NS
  40
+string(11) "Foo\Bar\Moo"
  41
+Top
  42
+string(11) "Foo\Bar\One"
  43
+string(3) "Boo"
  44
+string(7) "Bee\Bop"
  45
+string(3) "Moo"
  46
+object(Foo\Bar\One)#1 (0) {
  47
+}
  48
+string(11) "Foo\Bar\Two"
  49
+string(11) "Foo\Bar\Two"
  50
+string(11) "Foo\Bar\One"
  51
+string(11) "Foo\Bar\Baz"
  52
+Parent
  53
+string(11) "Foo\Bar\Two"
  54
+string(13) "Foo\Bar\Three"
  55
+string(11) "Foo\Bar\One"
  56
+string(11) "Foo\Bar\Baz"
58  Zend/zend_compile.c
@@ -2109,6 +2109,8 @@ void zend_resolve_class_name(znode *class_name, ulong fetch_type, int check_ns_n
2109 2109
 	zval **ns;
2110 2110
 	znode tmp;
2111 2111
 	int len;
  2112
+	int lctype;
  2113
+	zend_op *opline;
2112 2114
 
2113 2115
 	compound = memchr(Z_STRVAL(class_name->u.constant), '\\', Z_STRLEN(class_name->u.constant));
2114 2116
 	if (compound) {
@@ -2153,7 +2155,7 @@ void zend_resolve_class_name(znode *class_name, ulong fetch_type, int check_ns_n
2153 2155
 				*class_name = tmp;
2154 2156
 			}
2155 2157
 		}
2156  
-	} else if (CG(current_import) || CG(current_namespace)) {
  2158
+	} else {
2157 2159
 		/* this is a plain name (without \) */
2158 2160
 		lcname = zend_str_tolower_dup(Z_STRVAL(class_name->u.constant), Z_STRLEN(class_name->u.constant));
2159 2161
 
@@ -2163,13 +2165,53 @@ void zend_resolve_class_name(znode *class_name, ulong fetch_type, int check_ns_n
2163 2165
 			zval_dtor(&class_name->u.constant);
2164 2166
 			class_name->u.constant = **ns;
2165 2167
 			zval_copy_ctor(&class_name->u.constant);
2166  
-		} else if (CG(current_namespace)) {
2167  
-			/* plain name, no import - prepend current namespace to it */
2168  
-			tmp.op_type = IS_CONST;
2169  
-			tmp.u.constant = *CG(current_namespace);
2170  
-			zval_copy_ctor(&tmp.u.constant);
2171  
-			zend_do_build_namespace_name(&tmp, &tmp, class_name TSRMLS_CC);
2172  
-			*class_name = tmp;
  2168
+		} else {
  2169
+			lctype = zend_get_class_fetch_type(lcname, strlen(lcname));
  2170
+			switch (lctype) {
  2171
+				case ZEND_FETCH_CLASS_SELF:
  2172
+					if (!CG(active_class_entry)) {
  2173
+						zend_error(E_COMPILE_ERROR, "Cannot access self::class when no class scope is active");
  2174
+					}
  2175
+					zval_dtor(&class_name->u.constant);
  2176
+					class_name->op_type = IS_CONST;
  2177
+					ZVAL_STRINGL(&class_name->u.constant, CG(active_class_entry)->name, CG(active_class_entry)->name_length, 1);
  2178
+					break;
  2179
+				case ZEND_FETCH_CLASS_STATIC:
  2180
+					if (!CG(active_class_entry)) {
  2181
+						zend_error(E_COMPILE_ERROR, "Cannot access static::class when no class scope is active");
  2182
+					}
  2183
+				case ZEND_FETCH_CLASS_PARENT:
  2184
+					if (!CG(active_class_entry)) {
  2185
+						zend_error(E_COMPILE_ERROR, "Cannot access parent::class when no class scope is active");
  2186
+					}
  2187
+					zval_dtor(&class_name->u.constant);
  2188
+					opline = get_next_op(CG(active_op_array) TSRMLS_CC);
  2189
+					opline->opcode = ZEND_DO_FCALL;
  2190
+					opline->result.var = get_temporary_variable(CG(active_op_array));
  2191
+					opline->result_type = IS_VAR;
  2192
+					if (lctype == ZEND_FETCH_CLASS_STATIC) {
  2193
+						LITERAL_STRINGL(opline->op1, estrndup("get_called_class", sizeof("get_called_class")-1), sizeof("get_called_class")-1, 0);
  2194
+					} else {
  2195
+						LITERAL_STRINGL(opline->op1, estrndup("get_parent_class", sizeof("get_parent_class")-1), sizeof("get_parent_class")-1, 0);
  2196
+					}
  2197
+					CALCULATE_LITERAL_HASH(opline->op1.constant);
  2198
+					opline->op1_type = IS_CONST;
  2199
+					GET_CACHE_SLOT(opline->op1.constant);
  2200
+					opline->extended_value = 0;
  2201
+					SET_UNUSED(opline->op2);
  2202
+					GET_NODE(class_name, opline->result);
  2203
+					break;
  2204
+				case ZEND_FETCH_CLASS_DEFAULT:
  2205
+					if (CG(current_namespace)) {
  2206
+						/* plain name, no import - prepend current namespace to it */
  2207
+						tmp.op_type = IS_CONST;
  2208
+						tmp.u.constant = *CG(current_namespace);
  2209
+						zval_copy_ctor(&tmp.u.constant);
  2210
+						zend_do_build_namespace_name(&tmp, &tmp, class_name TSRMLS_CC);
  2211
+						*class_name = tmp;
  2212
+					}
  2213
+					break;
  2214
+			}
2173 2215
 		}
2174 2216
 		efree(lcname);
2175 2217
 	}
6  Zend/zend_language_parser.y
@@ -942,6 +942,7 @@ common_scalar:
942 942
 
943 943
 static_scalar: /* compile-time evaluated scalars */
944 944
 		common_scalar		{ $$ = $1; }
  945
+	|	class_name_scalar	{ $$ = $1; }
945 946
 	|	namespace_name 		{ zend_do_fetch_constant(&$$, NULL, &$1, ZEND_CT, 1 TSRMLS_CC); }
946 947
 	|	T_NAMESPACE T_NS_SEPARATOR namespace_name { $$.op_type = IS_CONST; ZVAL_EMPTY_STRING(&$$.u.constant);  zend_do_build_namespace_name(&$$, &$$, &$3 TSRMLS_CC); $3 = $$; zend_do_fetch_constant(&$$, NULL, &$3, ZEND_CT, 0 TSRMLS_CC); }
947 948
 	|	T_NS_SEPARATOR namespace_name { char *tmp = estrndup(Z_STRVAL($2.u.constant), Z_STRLEN($2.u.constant)+1); memcpy(&(tmp[1]), Z_STRVAL($2.u.constant), Z_STRLEN($2.u.constant)+1); tmp[0] = '\\'; efree(Z_STRVAL($2.u.constant)); Z_STRVAL($2.u.constant) = tmp; ++Z_STRLEN($2.u.constant); zend_do_fetch_constant(&$$, NULL, &$2, ZEND_CT, 0 TSRMLS_CC); }
@@ -959,6 +960,7 @@ static_class_constant:
959 960
 
960 961
 scalar:
961 962
 		T_STRING_VARNAME		{ $$ = $1; }
  963
+	|	class_name_scalar	{ $$ = $1; }
962 964
 	|	class_constant		{ $$ = $1; }
963 965
 	|	namespace_name	{ zend_do_fetch_constant(&$$, NULL, &$1, ZEND_RT, 1 TSRMLS_CC); }
964 966
 	|	T_NAMESPACE T_NS_SEPARATOR namespace_name { $$.op_type = IS_CONST; ZVAL_EMPTY_STRING(&$$.u.constant);  zend_do_build_namespace_name(&$$, &$$, &$3 TSRMLS_CC); $3 = $$; zend_do_fetch_constant(&$$, NULL, &$3, ZEND_RT, 0 TSRMLS_CC); }
@@ -1200,6 +1202,10 @@ class_constant:
1200 1202
 	|	variable_class_name T_PAAMAYIM_NEKUDOTAYIM T_STRING { zend_do_fetch_constant(&$$, &$1, &$3, ZEND_RT, 0 TSRMLS_CC); }
1201 1203
 ;
1202 1204
 
  1205
+class_name_scalar:
  1206
+	class_name T_PAAMAYIM_NEKUDOTAYIM T_CLASS { zend_resolve_class_name(&$1, ZEND_FETCH_CLASS_GLOBAL, 1 TSRMLS_CC); $$ = $1; }
  1207
+;
  1208
+
1203 1209
 %%
1204 1210
 
1205 1211
 /* Copy to YYRES the contents of YYSTR after stripping away unnecessary
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.