TGViewer
Пых Пых @phpyh · 7.83K subscribers
Post #506 6.64K
Пых 💙 Typhoon 0.4.0 https://github.com/typhoon-php/typhoon/releases/tag/0.4.0 Вчера ночью, ровно через 5 месяцев после 0.3.0, поставил следующий минорный тег. По сути, он, конечно, мажорный, так как 0.y релизы могут ломать обратную совместимость, чем мы не преминули…
💙 Typhoon 0.4. Тип not

Как вы наверняка знаете, в Psalm и PHPStan есть типы non-empty-string, non-empty-list<TValue> и non-empty-array<TKey, TValue>. В далёкой 0.2 версии тайфуна они обрабатывались индивидуально — у каждого был свой метод в TypeVisitor. Затем в 0.3 мы попытались сократить избыточность, внедрив конструктор non-empty<T>, но не покидало ощущение, что это костыль. И, наконец, месяц назад пришло осознание, что нужен конструктор не для непустоты, а для отрицания.

В самом деле, выведем статически типы внутри вот такой функции:


/**
* @template T
* @param T $bar
*/
function foo(mixed $bar): void
{
if (is_string($bar)) {
// тут у $bar тип T & string

return;
}

// а тут у $bar тип T & !string

trim($bar); // ошибка: ожидается string
}


Очевидно, что для статического анализатора отрицание может быть очень полезно. В TypeScript был такой PR, но почему-то заглох. Psalm и PHPStan частично поддерживают ! в аннотациях @assert:


/**
* @psalm-assert !null $value
* @phpstan-assert !null $value
*/
function assertNotNull($value): void
{
if ($value === null) {
throw new InvalidArgumentException();
}
}


Ну а в typhoon/type 0.4 not теперь first-class тип. Семейство non-empty-*, а заодно и non-falsy-string выражаются так:


non-empty-string = string & !''
non-falsy-string = truthy-string = non-empty-string & !'0'
non-empty-list<TValue> = list<TValue> & !array{}
non-empty-array<TKey, TValue> = array<TKey, TValue> & !array{}

Здесь array{} — это запечатанный array-shape без элементов, то есть []. Его ещё часто пишут как array<never, never>.


Чтобы обойти types::nonEmptyString через TypeVisitor, достаточно смаршрутизировать его как пересечение с соответствующими аргументами:


enum types implements Type
{
// ...
case nonEmptyString;

public function accept(TypeVisitor $visitor): mixed
{
return match ($this) {
// ...
self::nonEmptyString => $visitor->intersection($this, [
self::string,
self::not(self::string('')),
]),
}
}
}
  • 👍 22
  • 🔥 16
  • ❤ 1
  • 👎 1
  • 🤯 1
More from @phpyh
  1. Sep 30, 2026🍺 BeerPHP AI Moscow Кажется, «неделя PHP» потихоньку превращается в месяц PHP. 😅 Будет 2…
  2. Sep 28, 2026Запустили Пыхник и неделю PHP! Залетайте на первый открытый доклад от Дмитрия Dantes про к…
  3. Sep 15, 2026Сейчас один человек с AI может выполнять работу целого IT-отдела. Только хуёво
  4. Sep 12, 2026Спасибо за заявки! Всем напишу в понедельник. Осталось 1 место.
  5. Sep 11, 2026Хардкорный курс асинхронного PHP https://hardcourse.ru/php/async
  6. Sep 11, 2026Ещё полчасика надо — сайт готов, расписываю программу! 🙏
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →