Почему php допускает недопустимые сокращения возвращаемого типа, которые он знает, что не может разрешить? - PullRequest
4 голосов
/ 11 февраля 2020

Насколько я могу судить, php имеет возможность предотвратить объявление типа возвращаемого значения, когда он знает, что это проблематично c.

class Foo {
    public function __clone(): Baz {
        return new Baz;
    }
}

class Baz {

}

$foo = new Foo;
$newFoo = clone $foo;

Это приводит к Fatal error: Clone method Foo::__clone() cannot declare a return type, что совершенно разумно.

Но тогда почему php допускает такие вещи:

class Foo {
    public function __toString(): float {
        return "WAT!?!";
    }
}

echo new Foo;

Это приводит к

Неустранимая ошибка: Uncaught TypeError: Return значение Foo :: __ toString () должно иметь тип float, возвращаемая строка

Что не имеет смысла, потому что если вы попытаетесь вернуть float:

Неустранимая ошибка: необработанная ошибка: метод Foo :: __ toString () должен возвращать строковое значение

Не имеет ли смысла для php предотвращать объявленный тип возврата этих типов методы, а не давать эти сомнительные ошибки? Если нет, что является основной причиной этого внутри? Есть ли какая-то механическая баррикада, которая мешает php делать это там, где она может это делать в таких случаях, как клон ?

1 Ответ

4 голосов
/ 19 февраля 2020

TL; DR: поддержка выводов типа для методов magi c нарушает обратную совместимость.

Пример: что выводит этот код?

$foo = new Foo;
$bar = $foo->__construct();
echo get_class($bar);

Если вы сказали Foo, вы не правы: это Bar.


PHP имеет долгую и сложную эволюцию обработки возвращаемого типа.

  • До PHP 7.0 подсказки типа возврата были ошибкой разбора.
  • В PHP 7.0 мы получили объявления типа возврата с очень простыми правилами ( RF C ), и после, пожалуй, самых спорных внутренних дебатов в истории, мы получили строгие типы ( RF C).
  • PHP хромали вместе с некоторыми странностями в ко- и противоречивости до PHP 7.4, где мы получили многие из этих отсортированных ( RF C).

Поведение сегодняшнего дня отражает рост этого организма, бородавки и все остальное. ,

Вы указываете, что поведение __clone() является разумным, затем сравниваете это с явно бессмысленным поведением __toString(). Я оспариваю, что ни из них не являются разумными, при любом разумном ожидании вывода типа.

Вот код двигателя __clone:

6642     if (ce->clone) {                                                          
6643         if (ce->clone->common.fn_flags & ZEND_ACC_STATIC) {                   
6644             zend_error_noreturn(E_COMPILE_ERROR, "Clone method %s::%s() cannot be static",
6645                 ZSTR_VAL(ce->name), ZSTR_VAL(ce->clone->common.function_name));
6646         } else if (ce->clone->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {   
6647             zend_error_noreturn(E_COMPILE_ERROR,                              
6648                 "Clone method %s::%s() cannot declare a return type",         
6649                 ZSTR_VAL(ce->name), ZSTR_VAL(ce->clone->common.function_name));
6650         }                                                                     
6651     }                                                                         

Обратите особое внимание на эту формулировку (выделено мной):

Метод клонирования ... не может объявить тип возврата

__clone() дал вам ошибку не потому, что типы были разные , а потому что вы вообще указали тип ! Это также ошибка компиляции :

class Foo {
    public function __clone(): Foo {
        return new Foo;
    }
}

"Зачем ?!", вы кричите.

Я считаю, что есть две причины:

  1. Internals обязана поддерживать высокую планку обратной совместимости.
  2. Постепенное улучшение происходит медленно, каждое улучшение опирается на более ранние.

Давайте поговорим о # 1. Рассмотрим этот код , который действителен вплоть до PHP 4.0:

<?php
class Amount {
    var $amount;
}
class TaxedAmount extends Amount {
    var $rate;
    function __toString() {
        return $this->amount * $this->rate;
    }
}
$item = new TaxedAmount;
$item->amount = 242.0;
$item->rate = 1.13;
echo "You owe me $" . $item->__toString() . " for this answer.";

Некоторые бедняги использовали __toString в качестве своего собственного метода совершенно разумным способом. Теперь сохранение его поведения является главным приоритетом, поэтому мы не можем вносить изменения в движок, который нарушает этот код. Это мотивация для объявления strict_types: разрешить изменения в поведении анализатора, чтобы сохранить прежнее поведение при добавлении нового поведения.

Вы можете спросить: почему бы нам просто не исправить это, когда declare(strict_types=1) включен? Ну, потому что этот код отлично подходит и для режима строгих типов! Это даже имеет смысл :

<?php declare(strict_types=1);

class Amount {
    var $amount;
}
class TaxedAmount extends Amount {
    var $rate;
    function __toString(): float {
        return $this->amount * $this->rate;
    }
}
$item = new TaxedAmount;
$item->amount = 242.0;
$item->rate = 1.13;
echo "You owe me $" . $item->__toString() . " for this answer.";

Ничего в этом коде не пахнет. Это действительный код PHP. Если бы метод назывался getTotalAmount вместо __toString, никто бы не бросил глаз. Единственная странная часть: имя метода «зарезервировано».

Таким образом, движок не может (а) принудительно __toString вернуть тип строки или (б) помешать вам установить собственный тип возвращаемого значения. Потому что выполнение любого из них нарушило бы обратную совместимость.

Однако мы могли бы реализовать новый положительный выбор, который говорит, что эти методы не могут быть вызваны напрямую. Как только мы это сделаем, мы можем добавить к ним вывод типа. Гипотетически:

<?php declare(strict_magic=1);

class Person {
    function __construct(): Person {
    }
    function __toString(): string {
    }
    // ... other magic
}

(new Person)->__construct(); // Fatal Error: cannot call magic method on strict_magic object

И это пункт № 2: когда у нас есть способ защиты обратной совместимости, мы можем добавить способ принудительного применения типов к методам magi c.

В резюме, __construct, __destruct, __clone, __toString, et c. обе (а) функции, которые вызывает движок в определенных обстоятельствах, для которых он может разумно вывести типы, и (б) функции, которые - исторически - могут вызываться напрямую способами, которые нарушают разумный вывод типа из (1).

Это причина PR 4117 , чтобы исправить Ошибка # 69718 заблокирована.

Единственный способ сломать этого пат: разработчик соглашается с обещанием, что эти методы не могут быть вызваны напрямую. Это освобождает движок для применения строгих правил вывода типов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...