Skip to content

[DISCUSSION] PHP RFC: Objects can be declared falsifiable #9868

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Zend/tests/enum/__toBool.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
Enum __toBool
--FILE--
<?php

enum Foo {
case Bar;

public function __toBool(): bool {
return true;
}
}

?>
--EXPECTF--
Fatal error: Enum Foo cannot include magic method __toBool in %s on line %d
35 changes: 35 additions & 0 deletions Zend/tests/falsifiable_automatic_implementation.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
--TEST--
Falsifiable is automatically implemented
--FILE--
<?php

class Test {
public function __toBool() {
return true;
}
}

var_dump(new Test instanceof Falsifiable);
var_dump((new ReflectionClass(Test::class))->getInterfaceNames());

class Test2 extends Test {
public function __toBool() {
return false;
}
}

var_dump(new Test2 instanceof Falsifiable);
var_dump((new ReflectionClass(Test2::class))->getInterfaceNames());

?>
--EXPECT--
bool(true)
array(1) {
[0]=>
string(10) "Falsifiable"
}
bool(false)
array(1) {
[0]=>
string(10) "Falsifiable"
}
14 changes: 14 additions & 0 deletions Zend/tests/falsifiable_internal_class.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
Falsifiable should be automatically implemented for internal classes
--EXTENSIONS--
zend_test
--FILE--
<?php

// _ZendTestClass defines __toBool() but does not explicitly implement Falsifiable.
$obj = new _ZendTestClass;
var_dump($obj instanceof Falsifiable);

?>
--EXPECT--
bool(true)
38 changes: 38 additions & 0 deletions Zend/tests/falsifiable_trait.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--TEST--
Bug #81582: Falsifiable not implicitly declared if __toBool() came from a trait
--FILE--
<?php

trait T {
public function __toBool(): bool {
return true;
}
}
trait T2 {
use T;
}

class C {
use T;
}
class C2 {
use T2;
}

var_dump(new C instanceof Falsifiable);
var_dump(new C2 instanceof Falsifiable);

// The traits themselves should not implement Falsifiable -- traits cannot implement interfaces.
$rc = new ReflectionClass(T::class);
var_dump($rc->getInterfaceNames());
$rc = new ReflectionClass(T2::class);
var_dump($rc->getInterfaceNames());

?>
--EXPECT--
bool(true)
bool(true)
array(0) {
}
array(0) {
}
14 changes: 14 additions & 0 deletions Zend/tests/falsifiable_trait_invalid.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
__toBool() from trait with invalid return type
--FILE--
<?php

trait T {
public function __toBool(): int {
return true;
}
}

?>
--EXPECTF--
Fatal error: T::__toBool(): Return type must be bool when declared in %s on line %d
11 changes: 11 additions & 0 deletions Zend/tests/return_types/045.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
__toBool can only declare bool return type
--FILE--
<?php
class Foo {
public function __toBool(): string {
}
}
?>
--EXPECTF--
Fatal error: Foo::__toBool(): Return type must be bool when declared in %s on line %d
28 changes: 28 additions & 0 deletions Zend/tests/return_types/never_tobool.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
--TEST--
never type of __toString method
--FILE--
<?php

class A implements Stringable {
public function __toString(): string {
return "hello";
}
}

class B extends A {
public function __toString(): never {
throw new \Exception('not supported');
}
}

try {
echo (string) (new B());
} catch (Exception $e) {
// do nothing
}

echo "done";

?>
--EXPECT--
done
12 changes: 6 additions & 6 deletions Zend/tests/return_types/never_tostring.phpt
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
--TEST--
never type of __toString method
never type of __toBool method
--FILE--
<?php

class A implements Stringable {
public function __toString(): string {
return "hello";
class A implements Falsifiable {
public function __toBool(): bool {
return true;
}
}

class B extends A {
public function __toString(): never {
public function __toBool(): never {
throw new \Exception('not supported');
}
}

try {
echo (string) (new B());
echo (bool) (new B());
} catch (Exception $e) {
// do nothing
}
Expand Down
1 change: 1 addition & 0 deletions Zend/zend.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ struct _zend_class_entry {
zend_function *__call;
zend_function *__callstatic;
zend_function *__tostring;
zend_function *__tobool;
zend_function *__debugInfo;
zend_function *__serialize;
zend_function *__unserialize;
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_API.h
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ typedef struct _zend_fcall_info_cache {
class_container.__call = NULL; \
class_container.__callstatic = NULL; \
class_container.__tostring = NULL; \
class_container.__tobool = NULL; \
class_container.__get = NULL; \
class_container.__set = NULL; \
class_container.__unset = NULL; \
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1848,6 +1848,7 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, bool nullify_hand
ce->__call = NULL;
ce->__callstatic = NULL;
ce->__tostring = NULL;
ce->__tobool = NULL;
ce->__serialize = NULL;
ce->__unserialize = NULL;
ce->__debugInfo = NULL;
Expand Down
6 changes: 6 additions & 0 deletions Zend/zend_inheritance.c
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ static void do_inherit_parent_constructor(zend_class_entry *ce) /* {{{ */
if (EXPECTED(!ce->__tostring)) {
ce->__tostring = parent->__tostring;
}
if (EXPECTED(!ce->__tobool)) {
ce->__tobool = parent->__tobool;
}
if (EXPECTED(!ce->clone)) {
ce->clone = parent->clone;
}
Expand Down Expand Up @@ -2753,6 +2756,7 @@ static zend_class_entry *zend_lazy_class_load(zend_class_entry *pce)
zend_update_inherited_handler(__isset);
zend_update_inherited_handler(__unset);
zend_update_inherited_handler(__tostring);
zend_update_inherited_handler(__tobool);
zend_update_inherited_handler(__callstatic);
zend_update_inherited_handler(__debugInfo);
zend_update_inherited_handler(__serialize);
Expand Down Expand Up @@ -3016,6 +3020,8 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string
do_interface_implementation(ce, zend_ce_stringable);
}

/* Believe the above (3010+) would hold tru for Falsifiable as well. */

zend_build_properties_info_table(ce);
} zend_catch {
/* Do not leak recorded errors to the next linked class. */
Expand Down
5 changes: 5 additions & 0 deletions Zend/zend_interfaces.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ interface Stringable
public function __toString(): string;
}

interface Falsifiable
{
public function __toBool(): bool;
}

/**
* @not-serializable
*/
Expand Down