Skip to content

The "new" operator should emit TypeErrors on __construct() return values. #21090

@MircoBabin

Description

@MircoBabin

Description

The "new" operator and __construct() return values

  • To my surprise return $this; in the __construct() function gives the error Using $this when not in object context. If this is the corrrect errortext is debatable, but this is correct behavior, it is not allowed.
  • But returning anything but $this 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.
  • 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:

https://3v4l.org/sXXe8#v8.5.2

<?php

const SOMECONST = 'Const hello';

enum SomeEnum 
{
    case Hearts;
    case Diamonds;
    case Clubs;
    case Spades;
}

function SomeFunction()
{
}

$SomeFunctionClosure = function()
{
};

$SomeFunctionFirstClassCallable = SomeFunction(...);

$someFunctionClosureFromCallabe = Closure::fromCallable('SomeFunction');

class ShouldTypeError
{
    public function __construct($returnValue)
    {
        if ($returnValue !== 'no-return') {
            if ($returnValue === '$this') {
                return $this;
            } elseif ($returnValue === 'no-return-value') {
                return;
            } elseif ($returnValue === 'void') {
                return void;
            } elseif ($returnValue === 'never') {
                return never;
            }
            
            return $returnValue;
        }
    }
}

$testcases = [
    'no-return',
    'no-return-value',
    '$this',
    new ShouldTypeError('no-return'),

    SOMECONST,    
    'abc',
    1,
    1.23,
    null,
    true,
    false,
    
    ['a', 'b', 'c'],
    ['a' => 'a', 'b' => 'b', 'c' => 'c', 0 => 'Zero'],
    
    'never',
    'void',
    
    SomeEnum::Spades,
    
    function() {
       echo 'Hi'.PHP_EOL;
    },
    $SomeFunctionClosure,
    $SomeFunctionFirstClassCallable,
    $someFunctionClosureFromCallabe,
    
    new DateTimeImmutable(),
];

foreach($testcases as $testcase) {
    echo "--------------[ TESTCASE ]--------------\n";
    var_dump($testcase);
    echo "\n";
    
    try {
        $didReturn = new ShouldTypeError($testcase);
        
        switch($testcase) {
            case 'no-return':
                echo "Success: without a return statement is always valid.\n";
                break;

            case 'no-return-value':
                echo "Success: a return statement without a value is always valid.\n";
                break;

            case '$this':
                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.\n";
                echo "  As shown by the third testcase new SomeTypeError('no-return').\n";
                break;
                
            default:
                echo "Error: why is it not a return TypeError?\n";
                break;
        }
        
        if (!($didReturn instanceof ShouldTypeError)) {
            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 ]--------------
string(9) "no-return"

Success: without a return statement is always valid.
--------------[ TESTCASE ]--------------
string(15) "no-return-value"

Success: a return statement without a value is always valid.
--------------[ TESTCASE ]--------------
string(5) "$this"

Success: throwable: Using $this when not in object context
--------------[ TESTCASE ]--------------
object(ShouldTypeError)#4 (0) {
}

Error: why is it not a return TypeError?
--------------[ TESTCASE ]--------------
string(11) "Const hello"

Error: why is it not a return TypeError?
--------------[ TESTCASE ]--------------
string(3) "abc"

Error: why is it not a return TypeError?
--------------[ TESTCASE ]--------------
int(1)

Error: why is it not a return TypeError?
--------------[ TESTCASE ]--------------
float(1.23)

Error: why is it not a return TypeError?
--------------[ TESTCASE ]--------------
NULL

Error: why is it not a return TypeError?
--------------[ TESTCASE ]--------------
bool(true)

Success: without a return statement is always valid.
--------------[ TESTCASE ]--------------
bool(false)

Error: why is it not a return TypeError?
--------------[ TESTCASE ]--------------
array(3) {
  [0]=>
  string(1) "a"
  [1]=>
  string(1) "b"
  [2]=>
  string(1) "c"
}

Error: why is it not a return TypeError?
--------------[ 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?
--------------[ TESTCASE ]--------------
string(5) "never"

Success: throwable: Undefined constant "never"
--------------[ TESTCASE ]--------------
string(4) "void"

Success: throwable: Undefined constant "void"
--------------[ TESTCASE ]--------------
enum(SomeEnum::Spades)

Error: why is it not a return TypeError?
--------------[ TESTCASE ]--------------
object(Closure)#6 (3) {
  ["name"]=>
  string(22) "{closure:/in/sXXe8:67}"
  ["file"]=>
  string(9) "/in/sXXe8"
  ["line"]=>
  int(67)
}

Error: why is it not a return TypeError?
--------------[ TESTCASE ]--------------
object(Closure)#1 (3) {
  ["name"]=>
  string(22) "{closure:/in/sXXe8:17}"
  ["file"]=>
  string(9) "/in/sXXe8"
  ["line"]=>
  int(17)
}

Error: why is it not a return TypeError?
--------------[ TESTCASE ]--------------
object(Closure)#2 (1) {
  ["function"]=>
  string(12) "SomeFunction"
}

Error: why is it not a return TypeError?
--------------[ TESTCASE ]--------------
object(Closure)#3 (1) {
  ["function"]=>
  string(12) "SomeFunction"
}

Error: why is it not a return TypeError?
--------------[ TESTCASE ]--------------
object(DateTimeImmutable)#7 (3) {
  ["date"]=>
  string(26) "2026-01-30 13:31:14.025598"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(16) "Europe/Amsterdam"
}

Error: why is it not a return TypeError?

PHP Version

PHP 8.5.2 via 3v4l

Operating System

3v4l

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions