В некоторых функциональных языках (Haskell, OCaml) можно явно не декларировать типы. Они реализуют вывод типов на базе алгоритма Хиндли-Милнера.
Простой пример:
fun a b -> a + b
Проанализировав эту функцию, мы можем заключить, что в неё можно передавать только пары типов, для которых определён оператор
+, например, числа. Возвращаемый тип для конкретной пары уже тривиально выводится.В таких языках типы обычно указывают только в верхнеуровневых функциях, чтобы избежать неоднозначности и зафиксировать API.
Однако, Хиндли-Милнер очень плохо дружит с подтипизацией и юнионами, поэтому в условных Scala и TypeScript нельзя опускать типы параметров (интересный тикет для TS, кстати). А вот возвращаемые типы почти всегда можно:
def twss(message: String) = message + " That's what she said!"
twss("The box is too big to fit in the hole") // String
function twss(message: string)
{
return message + " That's what she said!"
}
twss("The box is too big to fit in the hole") // string
Это интуитивно понятно: когда мы знаем типы аргументов и умеем выводить типы выражений и инструкций, мы элементарно можем вывести возвращаемый тип, проанализировав все ветви выполнения.
Теперь к PHP. Наши статанализаторы, грубо говоря, работают в двух режимах:
• рефлектор — читает сигнатуру, но не смотрит внутрь функции — полезен для стабов и вендорного кода,
• чекер — читает сигнатуру и проверяет "кишки" — используется для кода проекта.
Тут важно, что в режиме чекера анализатор не запоминает выведенный из тела функции возвращаемый тип. В общем случае это ок, потому что публичные контракты должны быть задекларированы явно, но для приватных методов было бы здорово запоминать выведенный тип:
final readonly class App
{
// Валентин: PHPStan, выведи и запомни тут тип по-братски!
private function twss(string $message)
{
return $message . " That's what she said!";
}
public function greet(string $message): string
{
// PHPStan: Иди на х*й, Валентин!
// Method App::greet() should return string but returns mixed.
return $this->twss($message);
}
}
Тут мы получили ошибку статанализа, хотя этот код ни при каких условиях не сломается. Очень жаль! Можно было бы в приватных методах писать меньше кода...
Но! Выведенный возвращаемый тип учитывается в анонимках:
PHPStan\dumpType(
array_map(
static fn (int $int) => new BcMath\Number($int),
[1, 2],
),
);
// array{BcMath\Number, BcMath\Number}
И никаких других ошибок! Оно и понятно: анонимные функции — часть процедурного кода, а не сигнатур, поэтому они обязательно анализируются целиком. Ну и кроме того, PHPStan не поддерживает PHPDoc для анонимок (эта фича была оценена Ондрюхой в 1500-2000 EUR, которые ему, видимо, так никто и не дал), поэтому выведенный тип имеет выше приоритет, чем нативный.
Это я всё к чему: не указывайте возвращаемые типы в анонимках — это простой и типобезопасный способ сокращать визуальный долг