]> granicus.if.org Git - php/commitdiff
Automatically implement Stringable interface
authorNikita Popov <nikita.ppv@gmail.com>
Thu, 6 Feb 2020 09:27:30 +0000 (10:27 +0100)
committerNicolas Grekas <nicolas.grekas@gmail.com>
Mon, 2 Mar 2020 14:25:33 +0000 (15:25 +0100)
UPGRADING
Zend/tests/stringable_automatic_implementation.phpt [new file with mode: 0644]
Zend/tests/type_declarations/variance/stringable.phpt [new file with mode: 0644]
Zend/zend_compile.c
ext/spl/tests/class_implements_variation1.phpt

index 07d7f86f7c4a94b77f1773c9bb86d9323b21826e..fcfb715622339aa866570bb0cda9785fbd43bf39 100644 (file)
--- 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 (file)
index 0000000..5c45c03
--- /dev/null
@@ -0,0 +1,35 @@
+--TEST--
+Stringable is automatically implemented
+--FILE--
+<?php
+
+class Test {
+    public function __toString() {
+        return "foo";
+    }
+}
+
+var_dump(new Test instanceof Stringable);
+var_dump((new ReflectionClass(Test::class))->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 (file)
index 0000000..a132080
--- /dev/null
@@ -0,0 +1,17 @@
+--TEST--
+Automatic Stringable implementation participates in variance
+--FILE--
+<?php
+
+class Foo {
+    public function test(): Stringable {}
+}
+class Bar extends Foo {
+    public function test(): Bar {}
+    public function __toString() {}
+}
+
+?>
+===DONE===
+--EXPECT--
+===DONE===
index cbf5ebfc0244598d3ccc25f93e4b073b3dd0be9a..0abf274dbd25ef55db76928909aac924df1752dd 100644 (file)
@@ -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);
        }
index 65fbe1a58a60b316b54af135a5b38e382f16e2e4..5c998c74944c8285d5b8ee48807240df1810b6c0 100644 (file)
@@ -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--