gron                                     Sat, 05 Nov 2011 01:46:40 +0000

Revision: http://svn.php.net/viewvc?view=revision&revision=318793

Log:
Fixed Bug #60217 (Requiring the same method from different traits)
- also added test to check for inconsistent abstract method definitions, they 
need to be compatible

Bug: https://bugs.php.net/60217 (Assigned) imposing requirements in traits
      
Changed paths:
    A   php/php-src/branches/PHP_5_4/Zend/tests/traits/bug60217a.phpt
    A   php/php-src/branches/PHP_5_4/Zend/tests/traits/bug60217b.phpt
    A   php/php-src/branches/PHP_5_4/Zend/tests/traits/bug60217c.phpt
    U   php/php-src/branches/PHP_5_4/Zend/zend_compile.c
    A   php/php-src/trunk/Zend/tests/traits/bug60217a.phpt
    A   php/php-src/trunk/Zend/tests/traits/bug60217b.phpt
    A   php/php-src/trunk/Zend/tests/traits/bug60217c.phpt
    U   php/php-src/trunk/Zend/zend_compile.c

Added: php/php-src/branches/PHP_5_4/Zend/tests/traits/bug60217a.phpt
===================================================================
--- php/php-src/branches/PHP_5_4/Zend/tests/traits/bug60217a.phpt	                        (rev 0)
+++ php/php-src/branches/PHP_5_4/Zend/tests/traits/bug60217a.phpt	2011-11-05 01:46:40 UTC (rev 318793)
@@ -0,0 +1,26 @@
+--TEST--
+Bug #60217 (Requiring the same method from different traits.)
+--FILE--
+<?php
+
+trait T1 {
+    public abstract function foo();
+}
+
+trait T2 {
+    public abstract function foo();
+}
+
+class C {
+    use T1, T2;
+
+    public function foo() {
+        echo "C::foo() works.\n";
+    }
+}
+
+$o = new C;
+$o->foo();
+
+--EXPECTF--
+C::foo() works.

Added: php/php-src/branches/PHP_5_4/Zend/tests/traits/bug60217b.phpt
===================================================================
--- php/php-src/branches/PHP_5_4/Zend/tests/traits/bug60217b.phpt	                        (rev 0)
+++ php/php-src/branches/PHP_5_4/Zend/tests/traits/bug60217b.phpt	2011-11-05 01:46:40 UTC (rev 318793)
@@ -0,0 +1,26 @@
+--TEST--
+Bug #60217 (Requiring the same method from different traits and abstract methods have to be compatible)
+--FILE--
+<?php
+
+trait TBroken1 {
+    public abstract function foo($a);
+}
+
+trait TBroken2 {
+    public abstract function foo($a, $b = 0);
+}
+
+class CBroken {
+    use TBroken1, TBroken2;
+
+    public function foo($a) {
+        echo 'FOO';
+    }
+}
+
+$o = new CBroken;
+$o->foo(1);
+
+--EXPECTF--
+Fatal error: Declaration of TBroken1::foo($a) must be compatible with TBroken2::foo($a, $b = 0) in %s on line %d

Added: php/php-src/branches/PHP_5_4/Zend/tests/traits/bug60217c.phpt
===================================================================
--- php/php-src/branches/PHP_5_4/Zend/tests/traits/bug60217c.phpt	                        (rev 0)
+++ php/php-src/branches/PHP_5_4/Zend/tests/traits/bug60217c.phpt	2011-11-05 01:46:40 UTC (rev 318793)
@@ -0,0 +1,26 @@
+--TEST--
+Bug #60217 (Requiring the same method from different traits and abstract methods have to be compatible, in both directions.)
+--FILE--
+<?php
+
+trait TBroken1 {
+    public abstract function foo($a, $b = 0);
+}
+
+trait TBroken2 {
+    public abstract function foo($a);
+}
+
+class CBroken {
+    use TBroken1, TBroken2;
+
+    public function foo($a) {
+        echo 'FOO';
+    }
+}
+
+$o = new CBroken;
+$o->foo(1);
+
+--EXPECTF--
+Fatal error: Declaration of TBroken1::foo($a, $b = 0) must be compatible with TBroken2::foo($a) in %s on line %d

Modified: php/php-src/branches/PHP_5_4/Zend/zend_compile.c
===================================================================
--- php/php-src/branches/PHP_5_4/Zend/zend_compile.c	2011-11-04 22:58:44 UTC (rev 318792)
+++ php/php-src/branches/PHP_5_4/Zend/zend_compile.c	2011-11-05 01:46:40 UTC (rev 318793)
@@ -3617,7 +3617,19 @@
 			/* if it is an abstract method, there is no collision */
 			if (other_trait_fn->common.fn_flags & ZEND_ACC_ABSTRACT) {
 				/* Make sure they are compatible */
-				do_inheritance_check_on_method(fn, other_trait_fn TSRMLS_CC);
+        if (fn->common.fn_flags & ZEND_ACC_ABSTRACT) {
+          /* In case both are abstract, just check prototype, but need to do that in both directions */
+          if (   !zend_do_perform_implementation_check(fn, other_trait_fn TSRMLS_CC)
+              || !zend_do_perform_implementation_check(other_trait_fn, fn TSRMLS_CC)) {
+            zend_error(E_COMPILE_ERROR, "Declaration of %s must be compatible with %s", //ZEND_FN_SCOPE_NAME(fn), fn->common.function_name, //::%s()
+              zend_get_function_declaration(fn TSRMLS_CC),
+              zend_get_function_declaration(other_trait_fn TSRMLS_CC));
+          }
+        }
+        else {
+          /* otherwise, do the full check */
+          do_inheritance_check_on_method(fn, other_trait_fn TSRMLS_CC);
+        }

 				/* we can savely free and remove it from other table */
 				zend_function_dtor(other_trait_fn);
@@ -3626,7 +3638,8 @@
 				/* if it is not an abstract method, there is still no collision */
 				/* if fn is an abstract method */
 				if (fn->common.fn_flags & ZEND_ACC_ABSTRACT) {
-					/* Make sure they are compatible */
+					/* Make sure they are compatible.
+             Here, we already know other_trait_fn cannot be abstract, full check ok. */
 					do_inheritance_check_on_method(other_trait_fn, fn TSRMLS_CC);

 					/* just mark as solved, will be added if its own trait is processed */
@@ -3843,39 +3856,39 @@
 	zend_function* existing_fn = NULL;
 	zend_function fn_copy, *fn_copy_p;
 	zend_function* prototype = NULL;  /* is used to determine the prototype according to the inheritance chain */
-
+
 	if (zend_hash_quick_find(&ce->function_table, hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void**) &existing_fn) == FAILURE) {
 		add = 1; /* not found */
 	} else if (existing_fn->common.scope != ce) {
 		add = 1; /* or inherited from other class or interface */
 	}
-
+
 	if (add) {
 		zend_function* parent_function;
 		if (ce->parent && zend_hash_quick_find(&ce->parent->function_table, hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void**) &parent_function) != FAILURE) {
 			prototype = parent_function; /* ->common.fn_flags |= ZEND_ACC_ABSTRACT; */

 			/* we got that method in the parent class, and are going to override it,
-			   except, if the trait is just asking to have an abstract method implemented. */
+       except, if the trait is just asking to have an abstract method implemented. */
 			if (fn->common.fn_flags & ZEND_ACC_ABSTRACT) {
 				/* then we clean up an skip this method */
 				zend_function_dtor(fn);
 				return ZEND_HASH_APPLY_REMOVE;
 			}
 		}
-
+
 		fn->common.scope = ce;
 		fn->common.prototype = prototype;
-
+
 		if (prototype
-			&& (prototype->common.fn_flags & ZEND_ACC_IMPLEMENTED_ABSTRACT
-				|| prototype->common.fn_flags & ZEND_ACC_ABSTRACT)) {
-			fn->common.fn_flags |= ZEND_ACC_IMPLEMENTED_ABSTRACT;
-		} else if (fn->common.fn_flags & ZEND_ACC_IMPLEMENTED_ABSTRACT) {
-			/* remove ZEND_ACC_IMPLEMENTED_ABSTRACT flag, think it shouldn't be copied to class */
-			fn->common.fn_flags = fn->common.fn_flags - ZEND_ACC_IMPLEMENTED_ABSTRACT;
-		}
-
+        && (prototype->common.fn_flags & ZEND_ACC_IMPLEMENTED_ABSTRACT
+            || prototype->common.fn_flags & ZEND_ACC_ABSTRACT)) {
+          fn->common.fn_flags |= ZEND_ACC_IMPLEMENTED_ABSTRACT;
+        } else if (fn->common.fn_flags & ZEND_ACC_IMPLEMENTED_ABSTRACT) {
+          /* remove ZEND_ACC_IMPLEMENTED_ABSTRACT flag, think it shouldn't be copied to class */
+          fn->common.fn_flags = fn->common.fn_flags - ZEND_ACC_IMPLEMENTED_ABSTRACT;
+        }
+
 		/* check whether the trait method fullfills the inheritance requirements */
 		if (prototype) {
 			do_inheritance_check_on_method(fn, prototype TSRMLS_CC);
@@ -3906,18 +3919,18 @@
 		}
 		fn_copy = *fn;
 		zend_traits_duplicate_function(&fn_copy, ce, estrdup(fn->common.function_name) TSRMLS_CC);
-
+
 		if (zend_hash_quick_update(&ce->function_table, hash_key->arKey, hash_key->nKeyLength, hash_key->h, &fn_copy, sizeof(zend_function), (void**)&fn_copy_p)==FAILURE) {
 			zend_error(E_COMPILE_ERROR, "Trait method %s has not been applied, because failure occured during updating class method table", hash_key->arKey);
 		}
-
+
 		zend_add_magic_methods(ce, hash_key->arKey, hash_key->nKeyLength, fn_copy_p TSRMLS_CC);
-
+
 		zend_function_dtor(fn);
 	} else {
 		zend_function_dtor(fn);
 	}
-
+
 	return ZEND_HASH_APPLY_REMOVE;
 }
 /* }}} */

Added: php/php-src/trunk/Zend/tests/traits/bug60217a.phpt
===================================================================
--- php/php-src/trunk/Zend/tests/traits/bug60217a.phpt	                        (rev 0)
+++ php/php-src/trunk/Zend/tests/traits/bug60217a.phpt	2011-11-05 01:46:40 UTC (rev 318793)
@@ -0,0 +1,26 @@
+--TEST--
+Bug #60217 (Requiring the same method from different traits.)
+--FILE--
+<?php
+
+trait T1 {
+    public abstract function foo();
+}
+
+trait T2 {
+    public abstract function foo();
+}
+
+class C {
+    use T1, T2;
+
+    public function foo() {
+        echo "C::foo() works.\n";
+    }
+}
+
+$o = new C;
+$o->foo();
+
+--EXPECTF--
+C::foo() works.

Added: php/php-src/trunk/Zend/tests/traits/bug60217b.phpt
===================================================================
--- php/php-src/trunk/Zend/tests/traits/bug60217b.phpt	                        (rev 0)
+++ php/php-src/trunk/Zend/tests/traits/bug60217b.phpt	2011-11-05 01:46:40 UTC (rev 318793)
@@ -0,0 +1,26 @@
+--TEST--
+Bug #60217 (Requiring the same method from different traits and abstract methods have to be compatible)
+--FILE--
+<?php
+
+trait TBroken1 {
+    public abstract function foo($a);
+}
+
+trait TBroken2 {
+    public abstract function foo($a, $b = 0);
+}
+
+class CBroken {
+    use TBroken1, TBroken2;
+
+    public function foo($a) {
+        echo 'FOO';
+    }
+}
+
+$o = new CBroken;
+$o->foo(1);
+
+--EXPECTF--
+Fatal error: Declaration of TBroken1::foo($a) must be compatible with TBroken2::foo($a, $b = 0) in %s on line %d

Added: php/php-src/trunk/Zend/tests/traits/bug60217c.phpt
===================================================================
--- php/php-src/trunk/Zend/tests/traits/bug60217c.phpt	                        (rev 0)
+++ php/php-src/trunk/Zend/tests/traits/bug60217c.phpt	2011-11-05 01:46:40 UTC (rev 318793)
@@ -0,0 +1,26 @@
+--TEST--
+Bug #60217 (Requiring the same method from different traits and abstract methods have to be compatible, in both directions.)
+--FILE--
+<?php
+
+trait TBroken1 {
+    public abstract function foo($a, $b = 0);
+}
+
+trait TBroken2 {
+    public abstract function foo($a);
+}
+
+class CBroken {
+    use TBroken1, TBroken2;
+
+    public function foo($a) {
+        echo 'FOO';
+    }
+}
+
+$o = new CBroken;
+$o->foo(1);
+
+--EXPECTF--
+Fatal error: Declaration of TBroken1::foo($a, $b = 0) must be compatible with TBroken2::foo($a) in %s on line %d

Modified: php/php-src/trunk/Zend/zend_compile.c
===================================================================
--- php/php-src/trunk/Zend/zend_compile.c	2011-11-04 22:58:44 UTC (rev 318792)
+++ php/php-src/trunk/Zend/zend_compile.c	2011-11-05 01:46:40 UTC (rev 318793)
@@ -3617,7 +3617,19 @@
 			/* if it is an abstract method, there is no collision */
 			if (other_trait_fn->common.fn_flags & ZEND_ACC_ABSTRACT) {
 				/* Make sure they are compatible */
-				do_inheritance_check_on_method(fn, other_trait_fn TSRMLS_CC);
+        if (fn->common.fn_flags & ZEND_ACC_ABSTRACT) {
+          /* In case both are abstract, just check prototype, but need to do that in both directions */
+          if (   !zend_do_perform_implementation_check(fn, other_trait_fn TSRMLS_CC)
+              || !zend_do_perform_implementation_check(other_trait_fn, fn TSRMLS_CC)) {
+            zend_error(E_COMPILE_ERROR, "Declaration of %s must be compatible with %s", //ZEND_FN_SCOPE_NAME(fn), fn->common.function_name, //::%s()
+              zend_get_function_declaration(fn TSRMLS_CC),
+              zend_get_function_declaration(other_trait_fn TSRMLS_CC));
+          }
+        }
+        else {
+          /* otherwise, do the full check */
+          do_inheritance_check_on_method(fn, other_trait_fn TSRMLS_CC);
+        }

 				/* we can savely free and remove it from other table */
 				zend_function_dtor(other_trait_fn);
@@ -3626,7 +3638,8 @@
 				/* if it is not an abstract method, there is still no collision */
 				/* if fn is an abstract method */
 				if (fn->common.fn_flags & ZEND_ACC_ABSTRACT) {
-					/* Make sure they are compatible */
+					/* Make sure they are compatible.
+             Here, we already know other_trait_fn cannot be abstract, full check ok. */
 					do_inheritance_check_on_method(other_trait_fn, fn TSRMLS_CC);

 					/* just mark as solved, will be added if its own trait is processed */
@@ -3843,39 +3856,39 @@
 	zend_function* existing_fn = NULL;
 	zend_function fn_copy, *fn_copy_p;
 	zend_function* prototype = NULL;  /* is used to determine the prototype according to the inheritance chain */
-
+
 	if (zend_hash_quick_find(&ce->function_table, hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void**) &existing_fn) == FAILURE) {
 		add = 1; /* not found */
 	} else if (existing_fn->common.scope != ce) {
 		add = 1; /* or inherited from other class or interface */
 	}
-
+
 	if (add) {
 		zend_function* parent_function;
 		if (ce->parent && zend_hash_quick_find(&ce->parent->function_table, hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void**) &parent_function) != FAILURE) {
 			prototype = parent_function; /* ->common.fn_flags |= ZEND_ACC_ABSTRACT; */

 			/* we got that method in the parent class, and are going to override it,
-			   except, if the trait is just asking to have an abstract method implemented. */
+       except, if the trait is just asking to have an abstract method implemented. */
 			if (fn->common.fn_flags & ZEND_ACC_ABSTRACT) {
 				/* then we clean up an skip this method */
 				zend_function_dtor(fn);
 				return ZEND_HASH_APPLY_REMOVE;
 			}
 		}
-
+
 		fn->common.scope = ce;
 		fn->common.prototype = prototype;
-
+
 		if (prototype
-			&& (prototype->common.fn_flags & ZEND_ACC_IMPLEMENTED_ABSTRACT
-				|| prototype->common.fn_flags & ZEND_ACC_ABSTRACT)) {
-			fn->common.fn_flags |= ZEND_ACC_IMPLEMENTED_ABSTRACT;
-		} else if (fn->common.fn_flags & ZEND_ACC_IMPLEMENTED_ABSTRACT) {
-			/* remove ZEND_ACC_IMPLEMENTED_ABSTRACT flag, think it shouldn't be copied to class */
-			fn->common.fn_flags = fn->common.fn_flags - ZEND_ACC_IMPLEMENTED_ABSTRACT;
-		}
-
+        && (prototype->common.fn_flags & ZEND_ACC_IMPLEMENTED_ABSTRACT
+            || prototype->common.fn_flags & ZEND_ACC_ABSTRACT)) {
+          fn->common.fn_flags |= ZEND_ACC_IMPLEMENTED_ABSTRACT;
+        } else if (fn->common.fn_flags & ZEND_ACC_IMPLEMENTED_ABSTRACT) {
+          /* remove ZEND_ACC_IMPLEMENTED_ABSTRACT flag, think it shouldn't be copied to class */
+          fn->common.fn_flags = fn->common.fn_flags - ZEND_ACC_IMPLEMENTED_ABSTRACT;
+        }
+
 		/* check whether the trait method fullfills the inheritance requirements */
 		if (prototype) {
 			do_inheritance_check_on_method(fn, prototype TSRMLS_CC);
@@ -3906,18 +3919,18 @@
 		}
 		fn_copy = *fn;
 		zend_traits_duplicate_function(&fn_copy, ce, estrdup(fn->common.function_name) TSRMLS_CC);
-
+
 		if (zend_hash_quick_update(&ce->function_table, hash_key->arKey, hash_key->nKeyLength, hash_key->h, &fn_copy, sizeof(zend_function), (void**)&fn_copy_p)==FAILURE) {
 			zend_error(E_COMPILE_ERROR, "Trait method %s has not been applied, because failure occured during updating class method table", hash_key->arKey);
 		}
-
+
 		zend_add_magic_methods(ce, hash_key->arKey, hash_key->nKeyLength, fn_copy_p TSRMLS_CC);
-
+
 		zend_function_dtor(fn);
 	} else {
 		zend_function_dtor(fn);
 	}
-
+
 	return ZEND_HASH_APPLY_REMOVE;
 }
 /* }}} */
-- 
PHP CVS Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to