Skip to content

Feature/class name as scalar #256

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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions Zend/tests/class_name_as_scalar.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
--TEST--
class name as scalar from ::class keyword
--FILE--
<?php

namespace Foo\Bar {
class One {
// compile time constants
const A = self::class;
const B = Two::class;
}
class Two extends One {
public static function run() {
var_dump(self::class); // self compile time lookup
var_dump(static::class); // runtime lookup
var_dump(parent::class); // runtime lookup
var_dump(Baz::class); // default compile time lookup
}
}
class Three extends Two {
// compile time static lookups
public static function checkCompileTime(
$one = self::class,
$two = Baz::class,
$three = One::A,
$four = self::B
) {
var_dump($one, $two, $three, $four);
}
}
echo "In NS\n";
var_dump(Moo::CLASS); // resolve in namespace
}

namespace {
use Bee\Bop as Moo,
Foo\Bar\One;
echo "Top\n";
var_dump(One::class); // resolve from use
var_dump(Boo::class); // resolve in global namespace
var_dump(Moo::CLASS); // resolve from use as
var_dump(\Moo::Class); // resolve fully qualified
$class = One::class; // assign class as scalar to var
$x = new $class; // create new class from original scalar assignment
var_dump($x);
Foo\Bar\Two::run(); // resolve runtime lookups
echo "Parent\n";
Foo\Bar\Three::run(); // resolve runtime lookups with inheritance
echo "Compile Check\n";
Foo\Bar\Three::checkCompileTime();
}

?>
--EXPECTF--
In NS
string(11) "Foo\Bar\Moo"
Top
string(11) "Foo\Bar\One"
string(3) "Boo"
string(7) "Bee\Bop"
string(3) "Moo"
object(Foo\Bar\One)#1 (0) {
}
string(11) "Foo\Bar\Two"
string(11) "Foo\Bar\Two"
string(11) "Foo\Bar\One"
string(11) "Foo\Bar\Baz"
Parent
string(11) "Foo\Bar\Two"
string(13) "Foo\Bar\Three"
string(11) "Foo\Bar\One"
string(11) "Foo\Bar\Baz"
Compile Check
string(13) "Foo\Bar\Three"
string(11) "Foo\Bar\Baz"
string(11) "Foo\Bar\One"
string(11) "Foo\Bar\Two"
13 changes: 13 additions & 0 deletions Zend/tests/class_name_as_scalar_error_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
class name as scalar from ::class keyword error using static in class constant
--FILE--
<?php

namespace Foo\Bar {
class One {
const Baz = static::class;
}
}
?>
--EXPECTF--
Fatal error: static::class cannot be used for compile-time class name resolution in %s on line %d
13 changes: 13 additions & 0 deletions Zend/tests/class_name_as_scalar_error_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
class name as scalar from ::class keyword error using parent in class constant
--FILE--
<?php

namespace Foo\Bar {
class One {
const Baz = parent::class;
}
}
?>
--EXPECTF--
Fatal error: parent::class cannot be used for compile-time class name resolution in %s on line %d
13 changes: 13 additions & 0 deletions Zend/tests/class_name_as_scalar_error_003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
class name as scalar from ::class keyword error using static in method signature
--FILE--
<?php

namespace Foo\Bar {
class One {
public function baz($x = static::class) {}
}
}
?>
--EXPECTF--
Fatal error: static::class cannot be used for compile-time class name resolution in %s on line %d
13 changes: 13 additions & 0 deletions Zend/tests/class_name_as_scalar_error_004.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
class name as scalar from ::class keyword error using parent in method signature
--FILE--
<?php

namespace Foo\Bar {
class One {
public function baz($x = parent::class) {}
}
}
?>
--EXPECTF--
Fatal error: parent::class cannot be used for compile-time class name resolution in %s on line %d
10 changes: 10 additions & 0 deletions Zend/tests/class_name_as_scalar_error_005.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
class name as scalar from ::class keyword error using static non class context
--FILE--
<?php

$x = static::class;

?>
--EXPECTF--
Fatal error: Cannot access static::class when no class scope is active in %s on line %d
10 changes: 10 additions & 0 deletions Zend/tests/class_name_as_scalar_error_006.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
class name as scalar from ::class keyword error using parent in non class context
--FILE--
<?php

$x = parent::class;

?>
--EXPECTF--
Fatal error: Cannot access parent::class when no class scope is active in %s on line %d
47 changes: 47 additions & 0 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -2119,6 +2119,53 @@ void zend_resolve_non_class_name(znode *element_name, zend_bool check_namespace
}
/* }}} */

void zend_do_resolve_class_name(znode *result, znode *class_name, int is_static TSRMLS_DC) /* {{{ */
{
char *lcname;
int lctype;
znode constant_name;

lcname = zend_str_tolower_dup(Z_STRVAL(class_name->u.constant), class_name->u.constant.value.str.len);
lctype = zend_get_class_fetch_type(lcname, strlen(lcname));
switch (lctype) {
case ZEND_FETCH_CLASS_SELF:
if (!CG(active_class_entry)) {
zend_error(E_COMPILE_ERROR, "Cannot access self::class when no class scope is active");
Copy link
Contributor

Choose a reason for hiding this comment

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

This case is not covered by tests

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am adding the error tests tonight.

}
zval_dtor(&class_name->u.constant);
class_name->op_type = IS_CONST;
ZVAL_STRINGL(&class_name->u.constant, CG(active_class_entry)->name, CG(active_class_entry)->name_length, 1);
*result = *class_name;
break;
case ZEND_FETCH_CLASS_STATIC:
case ZEND_FETCH_CLASS_PARENT:
if (is_static) {
zend_error(E_COMPILE_ERROR,
"%s::class cannot be used for compile-time class name resolution",
lctype == ZEND_FETCH_CLASS_STATIC ? "static" : "parent"
);
}
if (!CG(active_class_entry)) {
zend_error(E_COMPILE_ERROR,
"Cannot access %s::class when no class scope is active",
lctype == ZEND_FETCH_CLASS_STATIC ? "static" : "parent"
);
}
constant_name.op_type = IS_CONST;
ZVAL_STRINGL(&constant_name.u.constant, "class", sizeof("class")-1, 1);
zend_do_fetch_constant(result, class_name, &constant_name, ZEND_RT, 1 TSRMLS_CC);
break;
case ZEND_FETCH_CLASS_DEFAULT:
zend_resolve_class_name(class_name, ZEND_FETCH_CLASS_GLOBAL, 1);
*result = *class_name;
break;
}

efree(lcname);

}
/* }}} */

void zend_resolve_class_name(znode *class_name, ulong fetch_type, int check_ns_name TSRMLS_DC) /* {{{ */
{
char *compound;
Expand Down
10 changes: 10 additions & 0 deletions Zend/zend_language_parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,7 @@ common_scalar:

static_scalar: /* compile-time evaluated scalars */
common_scalar { $$ = $1; }
| static_class_name_scalar { $$ = $1; }
| namespace_name { zend_do_fetch_constant(&$$, NULL, &$1, ZEND_CT, 1 TSRMLS_CC); }
| 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); }
| 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); }
Expand All @@ -959,6 +960,7 @@ static_class_constant:

scalar:
T_STRING_VARNAME { $$ = $1; }
| class_name_scalar { $$ = $1; }
| class_constant { $$ = $1; }
| namespace_name { zend_do_fetch_constant(&$$, NULL, &$1, ZEND_RT, 1 TSRMLS_CC); }
| 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); }
Expand Down Expand Up @@ -1200,6 +1202,14 @@ class_constant:
| variable_class_name T_PAAMAYIM_NEKUDOTAYIM T_STRING { zend_do_fetch_constant(&$$, &$1, &$3, ZEND_RT, 0 TSRMLS_CC); }
;

static_class_name_scalar:
class_name T_PAAMAYIM_NEKUDOTAYIM T_CLASS { zend_do_resolve_class_name(&$$, &$1, 1 TSRMLS_CC); }
;

class_name_scalar:
class_name T_PAAMAYIM_NEKUDOTAYIM T_CLASS { zend_do_resolve_class_name(&$$, &$1, 0 TSRMLS_CC); }
;

%%

/* Copy to YYRES the contents of YYSTR after stripping away unnecessary
Expand Down
3 changes: 3 additions & 0 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -3567,6 +3567,9 @@ ZEND_VM_HANDLER(99, ZEND_FETCH_CONSTANT, VAR|CONST|UNUSED, CONST)
}
ZVAL_COPY_VALUE(&EX_T(opline->result.var).tmp_var, *value);
zval_copy_ctor(&EX_T(opline->result.var).tmp_var);
} else if (Z_STRLEN_P(opline->op2.zv) == sizeof("class")-1 && strcmp(Z_STRVAL_P(opline->op2.zv), "class") == 0) {
Copy link
Member

Choose a reason for hiding this comment

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

Tokens in PHP are case-insensitive, so the strcmp here should be done case insensitive too. Otherwise static::CLASS will not work for example.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This particular reference to "class" was provided from inside zend_do_resolve_class_name, so it will always be "class" and never any other variation.

See: https://github.com/ralphschindler/php-src/blob/05c737e117b7aafbf67b9e568352054e39d5c606/Zend/zend_compile.c#L2155

Copy link
Member

Choose a reason for hiding this comment

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

Yup, you're right, didn't notice that :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(I added a comment to that effect in my latest commit).
That said, if there is something in this approach that is not consistent with the way things are done, I can change it. In my mind, this is the most performant way of going about what I'd was trying to go about.

Let me know if there is anything more in this patch that needs to be addressed :)

Thanks again for taking the time.

/* "class" is assigned as a case-sensitive keyword from zend_do_resolve_class_name */
ZVAL_STRINGL(&EX_T(opline->result.var).tmp_var, ce->name, ce->name_length, 1);
} else {
zend_error_noreturn(E_ERROR, "Undefined class constant '%s'", Z_STRVAL_P(opline->op2.zv));
}
Expand Down
9 changes: 9 additions & 0 deletions Zend/zend_vm_execute.h
Original file line number Diff line number Diff line change
Expand Up @@ -3735,6 +3735,9 @@ static int ZEND_FASTCALL ZEND_FETCH_CONSTANT_SPEC_CONST_CONST_HANDLER(ZEND_OPCO
}
ZVAL_COPY_VALUE(&EX_T(opline->result.var).tmp_var, *value);
zval_copy_ctor(&EX_T(opline->result.var).tmp_var);
} else if (Z_STRLEN_P(opline->op2.zv) == sizeof("class")-1 && strcmp(Z_STRVAL_P(opline->op2.zv), "class") == 0) {
/* "class" is assigned as a case-sensitive keyword from zend_do_resolve_class_name */
ZVAL_STRINGL(&EX_T(opline->result.var).tmp_var, ce->name, ce->name_length, 1);
} else {
zend_error_noreturn(E_ERROR, "Undefined class constant '%s'", Z_STRVAL_P(opline->op2.zv));
}
Expand Down Expand Up @@ -15600,6 +15603,9 @@ static int ZEND_FASTCALL ZEND_FETCH_CONSTANT_SPEC_VAR_CONST_HANDLER(ZEND_OPCODE
}
ZVAL_COPY_VALUE(&EX_T(opline->result.var).tmp_var, *value);
zval_copy_ctor(&EX_T(opline->result.var).tmp_var);
} else if (Z_STRLEN_P(opline->op2.zv) == sizeof("class")-1 && strcmp(Z_STRVAL_P(opline->op2.zv), "class") == 0) {
/* "class" is assigned as a case-sensitive keyword from zend_do_resolve_class_name */
ZVAL_STRINGL(&EX_T(opline->result.var).tmp_var, ce->name, ce->name_length, 1);
} else {
zend_error_noreturn(E_ERROR, "Undefined class constant '%s'", Z_STRVAL_P(opline->op2.zv));
}
Expand Down Expand Up @@ -25175,6 +25181,9 @@ static int ZEND_FASTCALL ZEND_FETCH_CONSTANT_SPEC_UNUSED_CONST_HANDLER(ZEND_OPC
}
ZVAL_COPY_VALUE(&EX_T(opline->result.var).tmp_var, *value);
zval_copy_ctor(&EX_T(opline->result.var).tmp_var);
} else if (Z_STRLEN_P(opline->op2.zv) == sizeof("class")-1 && strcmp(Z_STRVAL_P(opline->op2.zv), "class") == 0) {
/* "class" is assigned as a case-sensitive keyword from zend_do_resolve_class_name */
ZVAL_STRINGL(&EX_T(opline->result.var).tmp_var, ce->name, ce->name_length, 1);
} else {
zend_error_noreturn(E_ERROR, "Undefined class constant '%s'", Z_STRVAL_P(opline->op2.zv));
}
Expand Down