Skip to content
Create a gist now

Instantly share code, notes, and snippets.

@nikic /th.diff
Created

Patch for property typehinting
diff --git a/Zend/tests/accessors/automatic_setter_with_typehint.phpt b/Zend/tests/accessors/automatic_setter_with_typehint.phpt
index 4915c40..1ec4e1b 100644
--- a/Zend/tests/accessors/automatic_setter_with_typehint.phpt
+++ b/Zend/tests/accessors/automatic_setter_with_typehint.phpt
@@ -4,8 +4,8 @@ Automatic setters can have typehints
<?php
class Test {
- public $test {
- get; set(stdClass $obj);
+ public stdClass $test {
+ get; set;
}
}
diff --git a/Zend/tests/accessors/std_set_typehint_error.phpt b/Zend/tests/accessors/std_set_typehint_error.phpt
index 5dd63b5..68954be 100644
--- a/Zend/tests/accessors/std_set_typehint_error.phpt
+++ b/Zend/tests/accessors/std_set_typehint_error.phpt
@@ -10,8 +10,8 @@ class TestClass {
}
class TimePeriod {
- public $Object {
- set(TestClass2 $y) { $this->_Seconds = $y; }
+ public TestClass2 $Object {
+ set($y) { $this->_Seconds = $y; }
}
}
diff --git a/Zend/tests/accessors/std_set_with_paren_optional.phpt b/Zend/tests/accessors/std_set_with_paren_optional.phpt
index c59f3b5..71ef37f 100644
--- a/Zend/tests/accessors/std_set_with_paren_optional.phpt
+++ b/Zend/tests/accessors/std_set_with_paren_optional.phpt
@@ -21,8 +21,8 @@ class TimePeriod {
public $Seconds3 {
set($x) { $this->_Seconds = $x; }
}
- public $Object {
- set(TestClass $y) { $this->_Seconds = $y; }
+ public TestClass $Object {
+ set($y) { $this->_Seconds = $y; }
}
}
@@ -47,4 +47,4 @@ echo "Done\n";
4000
5000
Object [TestClass]
-Done
\ No newline at end of file
+Done
diff --git a/Zend/tests/accessors/typehinted_property_with_default_value.phpt b/Zend/tests/accessors/typehinted_property_with_default_value.phpt
new file mode 100644
index 0000000..89efee7
--- /dev/null
+++ b/Zend/tests/accessors/typehinted_property_with_default_value.phpt
@@ -0,0 +1,54 @@
+--TEST--
+Typehinted properties (without accessors) can have default values
+--FILE--
+<?php
+
+class Test {
+ public array $array = [1, 2, 3];
+ public stdClass $object;
+ public stdClass $objectNullable = null;
+ public callable $callable;
+ public callable $callableNullable = null;
+
+ public stdClass $objectAcc {
+ get; set { echo __METHOD__."($value)\n"; $this->objectAcc = $value; }
+ }
+ public stdClass $objectAccNullable = NULL {
+ get; set { echo __METHOD__."($value)\n"; $this->objectAccNullable = $value; }
+ }
+}
+
+set_error_handler(function($errNo, $errStr) { echo $errStr, "\n"; });
+
+$test = new Test;
+var_dump(
+ $test->array, $test->object, $test->objectNullable, $test->callable, $test->callableNullable
+);
+
+$test->object = null;
+$test->objectNullable = null;
+$test->callable = null;
+$test->callableNullable = null;
+
+$test->objectAcc = null;
+$test->objectAccNullable = null;
+
+?>
+--EXPECT--
+array(3) {
+ [0]=>
+ int(1)
+ [1]=>
+ int(2)
+ [2]=>
+ int(3)
+}
+NULL
+NULL
+NULL
+NULL
+Argument 1 passed to Test::$object->set() must be an instance of stdClass, null given
+Argument 1 passed to Test::$callable->set() must be callable, null given
+Argument 1 passed to Test::$objectAcc->set() must be an instance of stdClass, null given
+Test::$objectAcc->set()
+Test::$objectAccNullable->set()
diff --git a/Zend/tests/accessors/typehinted_property_with_invalid_default.phpt b/Zend/tests/accessors/typehinted_property_with_invalid_default.phpt
new file mode 100644
index 0000000..4aed5fd
--- /dev/null
+++ b/Zend/tests/accessors/typehinted_property_with_invalid_default.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Only null is allowed as default value for properties with accessors
+--FILE--
+<?php
+
+class Test {
+ public $foo = 42 { }
+}
+
+?>
+--EXPECTF--
+Fatal error: Only null is allowed as a default value for properties with accessors in %s on line %d
diff --git a/Zend/tests/accessors/typehinted_property_without_accessors.phpt b/Zend/tests/accessors/typehinted_property_without_accessors.phpt
new file mode 100644
index 0000000..08ad56e
--- /dev/null
+++ b/Zend/tests/accessors/typehinted_property_without_accessors.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Properties can be typehinted without defining accessors
+--FILE--
+<?php
+
+class Test {
+ public array $array;
+ public stdClass $stdClass;
+ public callable $callable;
+}
+
+$test = new Test;
+
+$test->array = [];
+$test->stdClass = new stdClass;
+$test->callable = 'strlen';
+
+var_dump($test->array, $test->stdClass, $test->callable);
+
+set_error_handler(function($errNo, $errStr) { echo $errStr, "\n"; });
+
+$test->array = 41;
+$test->stdClass = 42;
+$test->callable = 43;
+
+?>
+--EXPECT--
+array(0) {
+}
+object(stdClass)#2 (0) {
+}
+string(6) "strlen"
+Argument 1 passed to Test::$array->set() must be of the type array, integer given
+Argument 1 passed to Test::$stdClass->set() must be an instance of stdClass, integer given
+Argument 1 passed to Test::$callable->set() must be callable, integer given
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index b1f7521..69fe7c5 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -1570,34 +1570,46 @@ int zend_do_verify_access_types(const znode *current_access_type, const znode *n
}
/* }}} */
-void zend_declare_accessor(znode *var_name TSRMLS_DC) { /* {{{ */
+void zend_do_check_accessor_default_value(const znode *value TSRMLS_DC) /* {{{ */
+{
+ if (Z_TYPE(value->u.constant) != IS_NULL && (Z_TYPE(value->u.constant) != IS_CONSTANT || strcasecmp(Z_STRVAL(value->u.constant), "NULL") != 0)) {
+ zend_error(E_COMPILE_ERROR, "Only null is allowed as a default value for properties with accessors");
+ }
+}
+/* }}} */
+
+void zend_do_declare_accessor(const znode *var_name, const znode *value TSRMLS_DC) { /* {{{ */
zend_property_info *property_info;
- char *property_name;
- int property_name_len;
- zend_uint orig_ce_flags = CG(active_class_entry)->ce_flags;
+ zval *default_value;
if (CG(access_type) & ZEND_ACC_STATIC) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot define static accessor %s::$%s, not supported at this time", CG(active_class_entry)->name, Z_STRVAL(var_name->u.constant));
}
- /* zend_do_declare_property free's the string, so copy it */
- property_name_len = Z_STRLEN(var_name->u.constant);
- property_name = estrndup(Z_STRVAL(var_name->u.constant), property_name_len);
+ if (zend_hash_exists(&CG(active_class_entry)->properties_info, Z_STRVAL(var_name->u.constant), Z_STRLEN(var_name->u.constant) + 1)) {
+ zend_error(E_COMPILE_ERROR, "Cannot redeclare %s::$%s", CG(active_class_entry)->name, Z_STRVAL(var_name->u.constant));
+ }
+
+ ALLOC_INIT_ZVAL(default_value);
+ if (value && value->op_type != IS_UNUSED) {
+ *default_value = value->u.constant;
+ CG(typehint_node)->EA = 1;
+ } else {
+ CG(typehint_node)->EA = 0;
+ }
+
+ zend_declare_property_ex(CG(active_class_entry), zend_new_interned_string(Z_STRVAL(var_name->u.constant), Z_STRLEN(var_name->u.constant) + 1, 0 TSRMLS_CC), Z_STRLEN(var_name->u.constant), default_value, CG(access_type), CG(doc_comment), CG(doc_comment_len) TSRMLS_CC);
- /* Hide that we're working with an interface during property accessor declaration */
- CG(active_class_entry)->ce_flags &= ~ZEND_ACC_INTERFACE;
- zend_do_declare_property(var_name, NULL, CG(access_type) & ~(ZEND_ACC_FINAL|ZEND_ACC_ABSTRACT) TSRMLS_CC);
- CG(active_class_entry)->ce_flags = orig_ce_flags;
+ CG(doc_comment) = NULL;
+ CG(doc_comment_len) = 0;
- if (zend_hash_find(&CG(active_class_entry)->properties_info, property_name, property_name_len + 1, (void **) &property_info)==SUCCESS) {
- /* Add back final/abstract flags that were skipped previously */
- property_info->flags |= CG(access_type);
+ if (zend_hash_find(&CG(active_class_entry)->properties_info, Z_STRVAL(var_name->u.constant), Z_STRLEN(var_name->u.constant) + 1, (void **) &property_info) == SUCCESS) {
property_info->accs = ecalloc(ZEND_NUM_ACCESSORS, sizeof(zend_function *));
CG(current_property_info) = property_info;
- efree(property_name);
+ efree(Z_STRVAL(var_name->u.constant));
} else {
- zend_error_noreturn(E_COMPILE_ERROR, "Property_info for %s::$%s is not present, should not happen in zend_declare_accessor()", CG(active_class_entry)->name, property_name);
+ zend_error_noreturn(E_COMPILE_ERROR, "Property_info for %s::$%s is not present, should not happen in zend_declare_accessor()", CG(active_class_entry)->name, Z_STRVAL(var_name->u.constant));
}
}
/* }}} */
@@ -1612,7 +1624,7 @@ static inline char *create_accessor_function_name(const char *property_name, cha
}
/* }}} */
-void zend_do_begin_accessor_declaration(znode *function_token, znode *modifiers, int return_reference, int has_params TSRMLS_DC) /* {{{ */
+void zend_do_begin_accessor_declaration(znode *function_token, znode *modifiers, znode *param, int return_reference TSRMLS_DC) /* {{{ */
{
zend_property_info *property_info = CG(current_property_info);
const char *property_name = zend_get_property_name(property_info);
@@ -1635,7 +1647,7 @@ void zend_do_begin_accessor_declaration(znode *function_token, znode *modifiers,
efree(Z_STRVAL(function_token->u.constant));
ZVAL_STRING(&function_token->u.constant, create_accessor_function_name(property_name, "get"), 0);
- if (has_params) {
+ if (param) {
zend_error(E_COMPILE_ERROR, "Getters do not accept parameters for variable %s::$%s", CG(active_class_entry)->name, property_name);
}
@@ -1643,8 +1655,6 @@ void zend_do_begin_accessor_declaration(znode *function_token, znode *modifiers,
zend_do_begin_function_declaration(function_token, function_token, 1, return_reference, modifiers TSRMLS_CC);
property_info->accs[ZEND_ACCESSOR_GET] = (zend_function *) CG(active_op_array);
} else if (Z_TYPE(function_token->u.constant) == IS_STRING && strcasecmp("set", Z_STRVAL(function_token->u.constant)) == 0) {
- znode unused_node, unused_node2, value_node;
-
efree(Z_STRVAL(function_token->u.constant));
ZVAL_STRING(&function_token->u.constant, create_accessor_function_name(property_name, "set"), 0);
@@ -1655,19 +1665,37 @@ void zend_do_begin_accessor_declaration(znode *function_token, znode *modifiers,
zend_do_begin_function_declaration(function_token, function_token, 1, ZEND_RETURN_VAL, modifiers TSRMLS_CC);
property_info->accs[ZEND_ACCESSOR_SET] = (zend_function *) CG(active_op_array);
- if (!has_params) {
- /* Add $value parameter to __setHours() */
- unused_node.op_type = unused_node2.op_type = IS_UNUSED;
- unused_node.u.op.num = unused_node2.u.op.num = 1;
+ {
+ znode offset_node, *varname_node, varname_node_local;
+ offset_node.op_type = IS_UNUSED;
+ offset_node.u.op.num = 1;
- ZVAL_STRINGL(&value_node.u.constant, "value", 5, 1);
+ if (param) {
+ varname_node = param;
+ } else {
+ ZVAL_STRINGL(&varname_node_local.u.constant, "value", 5, 1);
+ varname_node = &varname_node_local;
+ }
- zend_do_receive_arg(ZEND_RECV, &value_node, &unused_node, NULL, &unused_node2, 0 TSRMLS_CC);
+ /* The EA specifies whether an explicit default value was given or if it's just
+ * implicit NULL */
+ if (CG(typehint_node)->EA) {
+ znode init_node;
+ INIT_ZNODE(init_node);
+ MAKE_COPY_ZVAL(
+ &CG(active_class_entry)->default_properties_table[property_info->offset],
+ &init_node.u.constant
+ );
+
+ zend_do_receive_arg(ZEND_RECV_INIT, varname_node, &offset_node, &init_node, CG(typehint_node), 0 TSRMLS_CC);
+ } else {
+ zend_do_receive_arg(ZEND_RECV, varname_node, &offset_node, NULL, CG(typehint_node), 0 TSRMLS_CC);
+ }
}
} else if (Z_TYPE(function_token->u.constant) == IS_LONG && Z_LVAL(function_token->u.constant) == T_ISSET) {
ZVAL_STRING(&function_token->u.constant, create_accessor_function_name(property_name, "isset"), 0);
- if (has_params) {
+ if (param) {
zend_error(E_COMPILE_ERROR, "Issetters do not accept parameters for variable %s::$%s", CG(active_class_entry)->name, property_name);
}
@@ -1677,7 +1705,7 @@ void zend_do_begin_accessor_declaration(znode *function_token, znode *modifiers,
} else if (Z_TYPE(function_token->u.constant) == IS_LONG && Z_LVAL(function_token->u.constant) == T_UNSET) {
ZVAL_STRING(&function_token->u.constant, create_accessor_function_name(property_name, "unset"), 0);
- if (has_params) {
+ if (param) {
zend_error(E_COMPILE_ERROR, "Unsetters do not accept parameters for variable %s::$%s", CG(active_class_entry)->name, property_name);
}
@@ -1769,7 +1797,23 @@ void zend_do_end_accessor_declaration(znode *function_token, const znode *body T
}
/* }}} */
-void zend_finalize_accessor(TSRMLS_D) { /* {{{ */
+static void zend_declare_accessor_method_helper(znode *function_token, long additional_modifiers TSRMLS_DC) /* {{{ */
+{
+ znode zn_modifiers, zn_body;
+
+ INIT_ZNODE(zn_modifiers);
+ Z_LVAL(zn_modifiers.u.constant) = additional_modifiers;
+
+ INIT_ZNODE(zn_body);
+ Z_LVAL(zn_body.u.constant) = ZEND_ACC_ABSTRACT;
+
+ zend_do_begin_accessor_declaration(function_token, &zn_modifiers, NULL, 0 TSRMLS_CC);
+ zend_do_end_accessor_declaration(function_token, &zn_body TSRMLS_CC);
+}
+/* }}} */
+
+void zend_do_finalize_accessor(TSRMLS_D) /* {{{ */
+{
zend_property_info *property_info = CG(current_property_info);
zend_uint keep_flags = 0;
@@ -1778,34 +1822,18 @@ void zend_finalize_accessor(TSRMLS_D) { /* {{{ */
}
if (!property_info->accs[ZEND_ACCESSOR_ISSET] && property_info->accs[ZEND_ACCESSOR_GET]) {
- znode zn_fntoken, zn_modifiers, zn_body;
+ znode zn_fntoken;
INIT_ZNODE(zn_fntoken);
ZVAL_LONG(&zn_fntoken.u.constant, T_ISSET);
- INIT_ZNODE(zn_modifiers);
- Z_LVAL(zn_modifiers.u.constant)
- = property_info->accs[ZEND_ACCESSOR_GET]->common.fn_flags & keep_flags;
-
- INIT_ZNODE(zn_body);
- Z_LVAL(zn_body.u.constant) = ZEND_ACC_ABSTRACT;
-
- zend_do_begin_accessor_declaration(&zn_fntoken, &zn_modifiers, 0, 0 TSRMLS_CC);
- zend_do_end_accessor_declaration(&zn_fntoken, &zn_body TSRMLS_CC);
+ zend_declare_accessor_method_helper(&zn_fntoken, property_info->accs[ZEND_ACCESSOR_GET]->common.fn_flags & keep_flags TSRMLS_CC);
}
if (!property_info->accs[ZEND_ACCESSOR_UNSET] && property_info->accs[ZEND_ACCESSOR_SET]) {
- znode zn_fntoken, zn_modifiers, zn_body;
+ znode zn_fntoken;
INIT_ZNODE(zn_fntoken);
ZVAL_LONG(&zn_fntoken.u.constant, T_UNSET);
- INIT_ZNODE(zn_modifiers);
- Z_LVAL(zn_modifiers.u.constant)
- = property_info->accs[ZEND_ACCESSOR_SET]->common.fn_flags & keep_flags;
-
- INIT_ZNODE(zn_body);
- Z_LVAL(zn_body.u.constant) = ZEND_ACC_ABSTRACT;
-
- zend_do_begin_accessor_declaration(&zn_fntoken, &zn_modifiers, 0, 0 TSRMLS_CC);
- zend_do_end_accessor_declaration(&zn_fntoken, &zn_body TSRMLS_CC);
+ zend_declare_accessor_method_helper(&zn_fntoken, property_info->accs[ZEND_ACCESSOR_SET]->common.fn_flags & keep_flags TSRMLS_CC);
}
CG(current_property_info) = NULL;
@@ -5598,6 +5626,19 @@ void zend_do_declare_property(const znode *var_name, const znode *value, zend_ui
CG(active_class_entry)->name, var_name->u.constant.value.str.val);
}
+ if (CG(typehint_node)->op_type != IS_UNUSED) {
+ znode function_token;
+
+ zend_do_declare_accessor(var_name, value TSRMLS_CC);
+ MAKE_ZNODE(function_token, "get");
+ zend_declare_accessor_method_helper(&function_token, 0 TSRMLS_CC);
+ MAKE_ZNODE(function_token, "set");
+ zend_declare_accessor_method_helper(&function_token, 0 TSRMLS_CC);
+ zend_do_finalize_accessor(TSRMLS_C);
+
+ return;
+ }
+
if (zend_hash_find(&CG(active_class_entry)->properties_info, var_name->u.constant.value.str.val, var_name->u.constant.value.str.len+1, (void **) &existing_property_info)==SUCCESS) {
zend_error(E_COMPILE_ERROR, "Cannot redeclare %s::$%s", CG(active_class_entry)->name, var_name->u.constant.value.str.val);
}
diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h
index 235e3ca..7f5f291 100644
--- a/Zend/zend_compile.h
+++ b/Zend/zend_compile.h
@@ -546,10 +546,11 @@ void zend_do_return(znode *expr, int do_end_vparse TSRMLS_DC);
void zend_do_yield(znode *result, znode *value, const znode *key, zend_bool is_variable TSRMLS_DC);
void zend_do_handle_exception(TSRMLS_D);
-void zend_declare_accessor(znode *var_name TSRMLS_DC);
-void zend_do_begin_accessor_declaration(znode *function_token, znode *modifiers, int return_reference, int has_params TSRMLS_DC);
+void zend_do_check_accessor_default_value(const znode *value TSRMLS_DC);
+void zend_do_declare_accessor(const znode *var_name, const znode *value TSRMLS_DC);
+void zend_do_begin_accessor_declaration(znode *function_token, znode *modifiers, znode *param, int return_reference TSRMLS_DC);
void zend_do_end_accessor_declaration(znode *function_token, const znode *body TSRMLS_DC);
-void zend_finalize_accessor(TSRMLS_D);
+void zend_do_finalize_accessor(TSRMLS_D);
void zend_do_begin_lambda_function_declaration(znode *result, znode *function_token, int return_reference, int is_static TSRMLS_DC);
void zend_do_fetch_lexical_variable(znode *varname, zend_bool is_ref TSRMLS_DC);
diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h
index 73cd8c6d8..d417104 100644
--- a/Zend/zend_globals.h
+++ b/Zend/zend_globals.h
@@ -122,6 +122,7 @@ struct _zend_compiler_globals {
znode implementing_class;
zend_property_info *current_property_info;
+ znode *typehint_node;
zend_uint access_type;
diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y
index 59a9726..c673f15 100644
--- a/Zend/zend_language_parser.y
+++ b/Zend/zend_language_parser.y
@@ -587,7 +587,9 @@ class_statement_list:
class_statement:
- variable_modifiers { CG(access_type) = Z_LVAL($1.u.constant); } class_variable_accessor_declarations
+ variable_modifiers { CG(access_type) = Z_LVAL($1.u.constant); }
+ optional_class_type { CG(typehint_node) = &$3; }
+ class_variable_accessor_declarations
| class_constant_declaration ';'
| trait_use_statement
| method_modifiers function is_reference T_STRING { zend_do_begin_function_declaration(&$2, &$4, 1, $3.op_type, &$1 TSRMLS_CC); }
@@ -708,29 +710,33 @@ accessor_optional_parens:
accessor_function:
accessor_modifiers T_ISSET accessor_optional_parens
{ Z_LVAL($2.u.constant) = T_ISSET;
- zend_do_begin_accessor_declaration(&$2, &$1, 0, 0 TSRMLS_CC); }
+ zend_do_begin_accessor_declaration(&$2, &$1, NULL, 0 TSRMLS_CC); }
method_body
{ zend_do_end_accessor_declaration(&$2, &$5 TSRMLS_CC); }
| accessor_modifiers T_UNSET accessor_optional_parens
{ Z_LVAL($2.u.constant) = T_UNSET;
- zend_do_begin_accessor_declaration(&$2, &$1, 0, 0 TSRMLS_CC); }
+ zend_do_begin_accessor_declaration(&$2, &$1, NULL, 0 TSRMLS_CC); }
method_body
{ zend_do_end_accessor_declaration(&$2, &$5 TSRMLS_CC); }
| accessor_modifiers is_reference T_STRING accessor_optional_parens
- { zend_do_begin_accessor_declaration(&$3, &$1, $2.op_type, 0 TSRMLS_CC); }
+ { zend_do_begin_accessor_declaration(&$3, &$1, NULL, $2.op_type TSRMLS_CC); }
method_body
{ zend_do_end_accessor_declaration(&$3, &$6 TSRMLS_CC); }
- | accessor_modifiers is_reference T_STRING '('
- { zend_do_begin_accessor_declaration(&$3, &$1, $2.op_type, 1 TSRMLS_CC); }
- non_empty_parameter_list ')' method_body
+ | accessor_modifiers is_reference T_STRING '(' T_VARIABLE ')'
+ { zend_do_begin_accessor_declaration(&$3, &$1, &$5, $2.op_type TSRMLS_CC); }
+ method_body
{ zend_do_end_accessor_declaration(&$3, &$8 TSRMLS_CC); }
;
+optional_null:
+ /* empty */ { $$.op_type = IS_UNUSED; }
+ | '=' static_scalar { $$ = $2; zend_do_check_accessor_default_value(&$2 TSRMLS_CC); }
+
class_variable_accessor_declarations:
- T_VARIABLE '{'
- { zend_declare_accessor(&$1 TSRMLS_CC); }
+ T_VARIABLE optional_null '{'
+ { zend_do_declare_accessor(&$1, &$2 TSRMLS_CC); }
accessors
- { zend_finalize_accessor(TSRMLS_C); }
+ { zend_do_finalize_accessor(TSRMLS_C); }
'}'
| class_variable_declaration ';'
;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.