P.S. If it should be a TypeError or some other error is debatable. But at least it should error.
<?php
enum SpecialReturnValue
{
case NoReturn;
case ReturnWithoutValue;
case ReturnDollarThis;
case ReturnVoid;
case ReturnNever;
}
class ShouldTypeError
{
public $uniqueId;
public function __construct($returnValue)
{
static $uniqueId = 0;
$this->uniqueId = $uniqueId;
$uniqueId++;
if ($returnValue !== SpecialReturnValue::NoReturn) {
if ($returnValue === SpecialReturnValue::ReturnWithoutValue) {
return;
} elseif ($returnValue === SpecialReturnValue::ReturnDollarThis) {
return $this;
} elseif ($returnValue === SpecialReturnValue::ReturnVoid) {
return void;
} elseif ($returnValue === SpecialReturnValue::ReturnNever) {
return never;
}
return $returnValue;
}
}
}
const SOMECONST = 'Const hello';
enum SomeEnum
{
case Hearts;
case Diamonds;
case Clubs;
case Spades;
}
function SomeFunction()
{
}
$SomeFunctionClosure = function()
{
};
$SomeFunctionFirstClassCallable = SomeFunction(...);
$someFunctionClosureFromCallabe = Closure::fromCallable('SomeFunction');
$arrowFunction = fn($a) => $a;
$testcases = [
SpecialReturnValue::NoReturn,
SpecialReturnValue::ReturnWithoutValue,
SpecialReturnValue::ReturnDollarThis,
new ShouldTypeError(SpecialReturnValue::NoReturn),
SOMECONST,
'abc',
1,
1.23,
null,
true,
false,
['a', 'b', 'c'],
[6 => 'six', 7 => 'seven', 67 => 'six seven'],
['a' => 'a', 'b' => 'b', 'c' => 'c', 0 => 'Zero'],
SpecialReturnValue::ReturnVoid,
SpecialReturnValue::ReturnNever,
SomeEnum::Spades,
function() {},
$SomeFunctionClosure,
$SomeFunctionFirstClassCallable,
$someFunctionClosureFromCallabe,
$arrowFunction,
new DateTimeImmutable(),
fopen('php://memory', 'w+'),
];
foreach($testcases as $testcase) {
echo "--------------[ TESTCASE ]--------------\n";
var_dump($testcase);
echo "\n";
try {
$didReturn = new ShouldTypeError($testcase);
if ($testcase === SpecialReturnValue::NoReturn) {
echo "Success: without a return statement is always valid.\n";
} elseif ($testcase === SpecialReturnValue::ReturnWithoutValue) {
echo "Success: a return statement without a value is always valid.\n";
} elseif ($testcase === SpecialReturnValue::ReturnDollarThis) {
echo "Dubious: return \$this is dubious.\n";
echo "- it fullfills the return type, so it could be allowed.\n";
echo "- but returning anything from a constructor is nonsense, because it is discarded by \"new\".\n";
echo " As shown by the fourth testcase new SomeTypeError(SpecialReturnValue::NoReturn).\n";
} else {
echo "Error: why is it not a return TypeError?\n";
}
if ($didReturn instanceof ShouldTypeError) {
echo "Created uniqueId ".$didReturn->uniqueId."\n";
} else {
echo "Failed to new a ShouldTypeError.\n";
}
} catch (Throwable $ex) {
echo "Success: throwable: ".$ex->getMessage()."\n";
}
}
--------------[ TESTCASE ]--------------
enum(SpecialReturnValue::NoReturn)
Success: without a return statement is always valid.
Created uniqueId 1
--------------[ TESTCASE ]--------------
enum(SpecialReturnValue::ReturnWithoutValue)
Success: a return statement without a value is always valid.
Created uniqueId 2
--------------[ TESTCASE ]--------------
enum(SpecialReturnValue::ReturnDollarThis)
Dubious: return $this is dubious.
- it fullfills the return type, so it could be allowed.
- but returning anything from a constructor is nonsense, because it is discarded by "new".
As shown by the fourth testcase new SomeTypeError(SpecialReturnValue::NoReturn).
Created uniqueId 3
--------------[ TESTCASE ]--------------
object(ShouldTypeError)#8 (1) {
["uniqueId"]=>
int(0)
}
Error: why is it not a return TypeError?
Created uniqueId 4
--------------[ TESTCASE ]--------------
string(11) "Const hello"
Error: why is it not a return TypeError?
Created uniqueId 5
--------------[ TESTCASE ]--------------
string(3) "abc"
Error: why is it not a return TypeError?
Created uniqueId 6
--------------[ TESTCASE ]--------------
int(1)
Error: why is it not a return TypeError?
Created uniqueId 7
--------------[ TESTCASE ]--------------
float(1.23)
Error: why is it not a return TypeError?
Created uniqueId 8
--------------[ TESTCASE ]--------------
NULL
Error: why is it not a return TypeError?
Created uniqueId 9
--------------[ TESTCASE ]--------------
bool(true)
Error: why is it not a return TypeError?
Created uniqueId 10
--------------[ TESTCASE ]--------------
bool(false)
Error: why is it not a return TypeError?
Created uniqueId 11
--------------[ TESTCASE ]--------------
array(3) {
[0]=>
string(1) "a"
[1]=>
string(1) "b"
[2]=>
string(1) "c"
}
Error: why is it not a return TypeError?
Created uniqueId 12
--------------[ TESTCASE ]--------------
array(3) {
[6]=>
string(3) "six"
[7]=>
string(5) "seven"
[67]=>
string(9) "six seven"
}
Error: why is it not a return TypeError?
Created uniqueId 13
--------------[ TESTCASE ]--------------
array(4) {
["a"]=>
string(1) "a"
["b"]=>
string(1) "b"
["c"]=>
string(1) "c"
[0]=>
string(4) "Zero"
}
Error: why is it not a return TypeError?
Created uniqueId 14
--------------[ TESTCASE ]--------------
enum(SpecialReturnValue::ReturnVoid)
Success: throwable: Undefined constant "void"
--------------[ TESTCASE ]--------------
enum(SpecialReturnValue::ReturnNever)
Success: throwable: Undefined constant "never"
--------------[ TESTCASE ]--------------
enum(SomeEnum::Spades)
Error: why is it not a return TypeError?
Created uniqueId 17
--------------[ TESTCASE ]--------------
object(Closure)#12 (3) {
["name"]=>
string(22) "{closure:/in/svfeO:84}"
["file"]=>
string(9) "/in/svfeO"
["line"]=>
int(84)
}
Error: why is it not a return TypeError?
Created uniqueId 18
--------------[ TESTCASE ]--------------
object(Closure)#1 (3) {
["name"]=>
string(22) "{closure:/in/svfeO:51}"
["file"]=>
string(9) "/in/svfeO"
["line"]=>
int(51)
}
Error: why is it not a return TypeError?
Created uniqueId 19
--------------[ TESTCASE ]--------------
object(Closure)#2 (1) {
["function"]=>
string(12) "SomeFunction"
}
Error: why is it not a return TypeError?
Created uniqueId 20
--------------[ TESTCASE ]--------------
object(Closure)#3 (1) {
["function"]=>
string(12) "SomeFunction"
}
Error: why is it not a return TypeError?
Created uniqueId 21
--------------[ TESTCASE ]--------------
object(Closure)#4 (4) {
["name"]=>
string(22) "{closure:/in/svfeO:59}"
["file"]=>
string(9) "/in/svfeO"
["line"]=>
int(59)
["parameter"]=>
array(1) {
["$a"]=>
string(10) "<required>"
}
}
Error: why is it not a return TypeError?
Created uniqueId 22
--------------[ TESTCASE ]--------------
object(DateTimeImmutable)#13 (3) {
["date"]=>
string(26) "2026-01-31 12:46:56.009085"
["timezone_type"]=>
int(3)
["timezone"]=>
string(16) "Europe/Amsterdam"
}
Error: why is it not a return TypeError?
Created uniqueId 23
--------------[ TESTCASE ]--------------
resource(5) of type (stream)
Error: why is it not a return TypeError?
Created uniqueId 24
Edited because the original example code contained an error, as pointed out by @iluuu1994 .
2nd edit because the "true" testcase was not handled correctly. I guess "third time's a charm".
Description
The "new" operator and __construct() return values
P.S. If it should be a TypeError or some other error is debatable. But at least it should error.
Reproduce
The following code:
https://3v4l.org/svfeO#v8.5.2
Resulted in this output (php 8.5.2):
PHP Version
Operating System
3v4l