From 89fc38d1ea7e309aa7c53782cf1ffcb2dfefaf30 Mon Sep 17 00:00:00 2001 From: Kevin Gessner Date: Mon, 22 Feb 2016 20:07:49 +0000 Subject: [PATCH] Allow traits to implement interfaces Traits can now declare one or more interfaces. The trait must implement each of the methods from all the interfaces it implements with compatible declarations. Failure to do so is a fatal error. The interfaces declared by a trait do not affect classes that use the trait. Implements Proposal 1 of https://wiki.php.net/rfc/traits-with-interfaces --- Zend/tests/traits/bugs/interfaces.phpt | 19 ---------- Zend/tests/traits/interface_004.phpt | 52 ++++++++++++++++++++++++++ Zend/tests/traits/interface_005.phpt | 15 ++++++++ Zend/tests/traits/interface_006.phpt | 27 +++++++++++++ Zend/tests/traits/interface_007.phpt | 17 +++++++++ Zend/zend_execute_API.c | 7 ++-- Zend/zend_language_parser.y | 4 +- 7 files changed, 117 insertions(+), 24 deletions(-) delete mode 100644 Zend/tests/traits/bugs/interfaces.phpt create mode 100644 Zend/tests/traits/interface_004.phpt create mode 100644 Zend/tests/traits/interface_005.phpt create mode 100644 Zend/tests/traits/interface_006.phpt create mode 100644 Zend/tests/traits/interface_007.phpt diff --git a/Zend/tests/traits/bugs/interfaces.phpt b/Zend/tests/traits/bugs/interfaces.phpt deleted file mode 100644 index b632b73be29df..0000000000000 --- a/Zend/tests/traits/bugs/interfaces.phpt +++ /dev/null @@ -1,19 +0,0 @@ ---TEST-- -Make sure trait does not implement an interface. ---FILE-- - ---EXPECTF-- -Parse error: syntax error, unexpected 'implements' (T_IMPLEMENTS), expecting '{' in %s on line %d diff --git a/Zend/tests/traits/interface_004.phpt b/Zend/tests/traits/interface_004.phpt new file mode 100644 index 0000000000000..eb8030307a02b --- /dev/null +++ b/Zend/tests/traits/interface_004.phpt @@ -0,0 +1,52 @@ +--TEST-- +Using trait that implements an interface +--FILE-- +getInterfaceNames()); +var_dump((new ReflectionClass(foo::class))->implementsInterface(baz::class)); + +print "OK\n"; + +?> +--EXPECT-- +Array +( + [baz] => baz +) +Array +( +) +Array +( + [baz] => baz +) +Array +( + [0] => baz +) +bool(true) +OK diff --git a/Zend/tests/traits/interface_005.phpt b/Zend/tests/traits/interface_005.phpt new file mode 100644 index 0000000000000..14fc5b04ac507 --- /dev/null +++ b/Zend/tests/traits/interface_005.phpt @@ -0,0 +1,15 @@ +--TEST-- +Checking error message when the trait doesn't implements the interface +--FILE-- + +--EXPECTF-- +Fatal error: Trait foo contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (baz::abc) in %s on line %d diff --git a/Zend/tests/traits/interface_006.phpt b/Zend/tests/traits/interface_006.phpt new file mode 100644 index 0000000000000..8fa46669cc454 --- /dev/null +++ b/Zend/tests/traits/interface_006.phpt @@ -0,0 +1,27 @@ +--TEST-- +Defining trait that implements an interface with abstract methods and via another trait +--FILE-- + +--EXPECT-- +OK diff --git a/Zend/tests/traits/interface_007.phpt b/Zend/tests/traits/interface_007.phpt new file mode 100644 index 0000000000000..cbcfa8531cbfd --- /dev/null +++ b/Zend/tests/traits/interface_007.phpt @@ -0,0 +1,17 @@ +--TEST-- +Checking error message when a trait's interface implementation doesn't match the interface +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of bar::abc() must be compatible with baz::abc($param) in %s on line %d diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 0c6f0377dab34..3d77c9430fafc 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1416,7 +1416,7 @@ typedef struct _zend_abstract_info { static void zend_verify_abstract_class_function(zend_function *fn, zend_abstract_info *ai) /* {{{ */ { - if (fn->common.fn_flags & ZEND_ACC_ABSTRACT) { + if (fn->common.fn_flags & ZEND_ACC_ABSTRACT && !(fn->common.scope->ce_flags & ZEND_ACC_TRAIT)) { if (ai->cnt < MAX_ABSTRACT_INFO_CNT) { ai->afn[ai->cnt] = fn; } @@ -1439,7 +1439,7 @@ void zend_verify_abstract_class(zend_class_entry *ce) /* {{{ */ zend_function *func; zend_abstract_info ai; - if ((ce->ce_flags & ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) && !(ce->ce_flags & (ZEND_ACC_TRAIT | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS))) { + if ((ce->ce_flags & ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) && !(ce->ce_flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) { memset(&ai, 0, sizeof(ai)); ZEND_HASH_FOREACH_PTR(&ce->function_table, func) { @@ -1447,7 +1447,8 @@ void zend_verify_abstract_class(zend_class_entry *ce) /* {{{ */ } ZEND_HASH_FOREACH_END(); if (ai.cnt) { - zend_error_noreturn(E_ERROR, "Class %s contains %d abstract method%s and must therefore be declared abstract or implement the remaining methods (" MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT ")", + zend_error_noreturn(E_ERROR, "%s %s contains %d abstract method%s and must therefore be declared abstract or implement the remaining methods (" MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT ")", + (ce->ce_flags & ZEND_ACC_TRAIT) ? "Trait" : "Class", ZSTR_VAL(ce->name), ai.cnt, ai.cnt > 1 ? "s" : "", DISPLAY_ABSTRACT_FN(0), diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 49027b787e7f7..8da0a58459807 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -510,8 +510,8 @@ class_modifier: trait_declaration_statement: T_TRAIT { $$ = CG(zend_lineno); } - T_STRING backup_doc_comment '{' class_statement_list '}' - { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_TRAIT, $2, $4, zend_ast_get_str($3), NULL, NULL, $6, NULL); } + T_STRING implements_list backup_doc_comment '{' class_statement_list '}' + { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_TRAIT, $2, $5, zend_ast_get_str($3), NULL, $4, $7, NULL); } ; interface_declaration_statement: