diff --git a/UPGRADING b/UPGRADING index 07d7f86f7c4..fcfb7156223 100644 --- a/UPGRADING +++ b/UPGRADING @@ -410,6 +410,8 @@ PHP 8.0 UPGRADE NOTES . Some consistency fixes to variable syntax have been applied, for example writing `Foo::BAR::$baz` is now allowed. RFC: https://wiki.php.net/rfc/variable_syntax_tweaks + . Added Stringable. + RFC: https://wiki.php.net/rfc/stringable - Date: . Added DateTime::createFromInterface() and diff --git a/Zend/tests/stringable_automatic_implementation.phpt b/Zend/tests/stringable_automatic_implementation.phpt new file mode 100644 index 00000000000..5c45c03acbf --- /dev/null +++ b/Zend/tests/stringable_automatic_implementation.phpt @@ -0,0 +1,35 @@ +--TEST-- +Stringable is automatically implemented +--FILE-- +getInterfaceNames()); + +class Test2 extends Test { + public function __toString() { + return "bar"; + } +} + +var_dump(new Test2 instanceof Stringable); +var_dump((new ReflectionClass(Test2::class))->getInterfaceNames()); + +?> +--EXPECT-- +bool(true) +array(1) { + [0]=> + string(10) "Stringable" +} +bool(true) +array(1) { + [0]=> + string(10) "Stringable" +} diff --git a/Zend/tests/type_declarations/variance/stringable.phpt b/Zend/tests/type_declarations/variance/stringable.phpt new file mode 100644 index 00000000000..a132080106f --- /dev/null +++ b/Zend/tests/type_declarations/variance/stringable.phpt @@ -0,0 +1,17 @@ +--TEST-- +Automatic Stringable implementation participates in variance +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index cbf5ebfc024..0abf274dbd2 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6066,6 +6066,24 @@ static void zend_check_magic_method_attr(uint32_t attr, const char* method, zend } /* }}} */ +static void add_stringable_interface(zend_class_entry *ce) { + for (uint32_t i = 0; i < ce->num_interfaces; i++) { + if (zend_string_equals_literal(ce->interface_names[i].lc_name, "stringable")) { + /* Interface already explicitly implemented */ + return; + } + } + + ce->num_interfaces++; + ce->interface_names = + erealloc(ce->interface_names, sizeof(zend_class_name) * ce->num_interfaces); + // TODO: Add known interned strings instead? + ce->interface_names[ce->num_interfaces - 1].name = + zend_string_init("Stringable", sizeof("Stringable") - 1, 0); + ce->interface_names[ce->num_interfaces - 1].lc_name = + zend_string_init("stringable", sizeof("stringable") - 1, 0); +} + void zend_begin_method_decl(zend_op_array *op_array, zend_string *name, zend_bool has_body) /* {{{ */ { zend_class_entry *ce = CG(active_class_entry); @@ -6147,6 +6165,7 @@ void zend_begin_method_decl(zend_op_array *op_array, zend_string *name, zend_boo } else if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_NAME)) { zend_check_magic_method_attr(fn_flags, "__toString", 0); ce->__tostring = (zend_function *) op_array; + add_stringable_interface(ce); } else if (zend_string_equals_literal(lcname, ZEND_INVOKE_FUNC_NAME)) { zend_check_magic_method_attr(fn_flags, "__invoke", 0); } else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_NAME)) { @@ -6680,6 +6699,10 @@ zend_op *zend_compile_class_decl(zend_ast *ast, zend_bool toplevel) /* {{{ */ CG(active_class_entry) = ce; + if (implements_ast) { + zend_compile_implements(implements_ast); + } + zend_compile_stmt(stmt_ast); /* Reset lineno for final opcodes and errors */ @@ -6719,10 +6742,6 @@ zend_op *zend_compile_class_decl(zend_ast *ast, zend_bool toplevel) /* {{{ */ } } - if (implements_ast) { - zend_compile_implements(implements_ast); - } - if ((ce->ce_flags & (ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) == ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) { zend_verify_abstract_class(ce); } diff --git a/ext/spl/tests/class_implements_variation1.phpt b/ext/spl/tests/class_implements_variation1.phpt index 65fbe1a58a6..5c998c74944 100644 --- a/ext/spl/tests/class_implements_variation1.phpt +++ b/ext/spl/tests/class_implements_variation1.phpt @@ -184,7 +184,9 @@ Error: 2 - class_implements(): Class does not exist and could not be loaded, %s bool(false) --instance of classWithToString-- -array(0) { +array(1) { + ["Stringable"]=> + string(10) "Stringable" } --instance of classWithoutToString--