-
Notifications
You must be signed in to change notification settings - Fork 8k
Closed as not planned
Closed as not planned
Copy link
Labels
Description
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
- Returning anything in the "new" context does not fail. Which is very strange. This is only in combination with the "new" operator. The "new" operator should emit TypeError for all return values.
- return $this; also does not fail. But if any return value is discarded by "new", as shown with testcase 4, why allow any return value from the "new" context? return $this should also error.
- When calling __construct() as a regular function a return value might have some purpose. But why not deprecate calling __construct() outside the new operator?
P.S. If it should be a TypeError or some other error is debatable. But at least it should error.
Reproduce
The following code:
<?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";
}
}Resulted in this output (php 8.5.2):
--------------[ 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
PHP Version
PHP 8.5.2 via 3v4l
Operating System
3v4l