← All results

unset as a possibly-undefined pseudo-type

Group: Cross-analyzer regressions · Category: regression · File: regressions_unset_pseudo_type.php

In a top-level script, a @var union carrying unset — the Blade-view idiom — states that the variable is either the given type or not defined at all. Reading unset as the undefined state, or as a nullable reading of the union, are both honest interpretations; resolving it as a class name is the defect. PHPantom reports "Class 'unset' not found" for it (issue #366), and most tools measured here resolve the spelling the same way — as an unknown class rather than as the possibly-undefined state. Each probe gets its own variable so that one read cannot narrow the next. A method call and a function argument are both unsafe on the undefined path, so a tool that models the state flags them. Inside an isset() guard the undefined path is gone: the guarded reads must stay silent, and the guard itself must not be reported as redundant — a variable that may be undefined makes isset() meaningful. The // V controls repeat both reads under a plain \DateTime @var. A diagnostic there means the reactions below are about a top-level variable with no assignment, not about the unset member of the union. mir looked like the exception and is not one. Its source resolves unset the same way as the rest: the spelling is absent from the docblock keyword table, falls through to the catch-all named-object atom, and is flagged because it is not in the is_pseudo_type exemption list. That diagnostic is info severity, which this adapter used to ask for only on debug_* files — mir's apparent silence here was the harness's, and MirChecker now reads info everywhere. mir does carry a possibly-undefined concept, but it is set from control flow alone and no docblock type can reach it, so exempting the spelling would quiet the complaint without implementing any of the semantics above. Source lead: PHPantom-dev/phpantom_lsp#366. Tracked here in #7.

Analyzer results

AnalyzerVersionResultDiagnostics
phan 6.0.7Phan 6.0.7 php-ast version 1.1.3 PHP version used to run Phan: 8.5.9 Unrecognized
regressions_unset_pseudo_type.php:44: Global variable $defined is undeclared [PhanUndeclaredGlobalVariable]
regressions_unset_pseudo_type.php:45: Global variable $defined is undeclared [PhanUndeclaredGlobalVariable]
regressions_unset_pseudo_type.php:48: Global variable $read is undeclared [PhanUndeclaredGlobalVariable]
regressions_unset_pseudo_type.php:51: Global variable $passed is undeclared [PhanUndeclaredGlobalVariable]
  • Recognition: spelling not resolved — reported on declaration line(s) 48, 51
  • Enforcement: 5/5 of the expected violations reported — incidental, since the spelling was not resolved
Expectation diff
Line 44: Expected valid value to be accepted, got ["Global variable $defined is undeclared [PhanUndeclaredGlobalVariable]"]
Line 45: Expected valid value to be accepted, got ["Global variable $defined is undeclared [PhanUndeclaredGlobalVariable]"]
phpstan 2.2.11PHPStan - PHP Static Analysis Tool 2.2.11 Unrecognized
regressions_unset_pseudo_type.php:48: Call to method format() on an unknown class Conformance\Tests\RegressionsUnsetPseudoType\unset. [reported-from-level=2]
regressions_unset_pseudo_type.php:48: PHPDoc tag @var for variable $read contains unknown class Conformance\Tests\RegressionsUnsetPseudoType\unset. [reported-from-level=2]
regressions_unset_pseudo_type.php:51: PHPDoc tag @var for variable $passed contains unknown class Conformance\Tests\RegressionsUnsetPseudoType\unset. [reported-from-level=2]
regressions_unset_pseudo_type.php:51: Parameter #1 $object of function date_format expects DateTimeInterface, Conformance\Tests\RegressionsUnsetPseudoType\unset|DateTime given. [reported-from-level=7]
regressions_unset_pseudo_type.php:54: PHPDoc tag @var for variable $guarded contains unknown class Conformance\Tests\RegressionsUnsetPseudoType\unset. [reported-from-level=2]
regressions_unset_pseudo_type.php:54: Variable $guarded in isset() always exists and is not nullable. [reported-from-level=1]
regressions_unset_pseudo_type.php:55: Call to method format() on an unknown class Conformance\Tests\RegressionsUnsetPseudoType\unset. [reported-from-level=2]
regressions_unset_pseudo_type.php:56: Parameter #1 $object of function date_format expects DateTimeInterface, Conformance\Tests\RegressionsUnsetPseudoType\unset|DateTime given. [reported-from-level=7]
With strict-rules
regressions_unset_pseudo_type.php:48: Call to method format() on an unknown class Conformance\Tests\RegressionsUnsetPseudoType\unset.
regressions_unset_pseudo_type.php:48: PHPDoc tag @var for variable $read contains unknown class Conformance\Tests\RegressionsUnsetPseudoType\unset.
regressions_unset_pseudo_type.php:51: PHPDoc tag @var for variable $passed contains unknown class Conformance\Tests\RegressionsUnsetPseudoType\unset.
regressions_unset_pseudo_type.php:51: Parameter #1 $object of function date_format expects DateTimeInterface, Conformance\Tests\RegressionsUnsetPseudoType\unset|DateTime given.
regressions_unset_pseudo_type.php:54: PHPDoc tag @var for variable $guarded contains unknown class Conformance\Tests\RegressionsUnsetPseudoType\unset.
regressions_unset_pseudo_type.php:54: Variable $guarded in isset() always exists and is not nullable.
regressions_unset_pseudo_type.php:55: Call to method format() on an unknown class Conformance\Tests\RegressionsUnsetPseudoType\unset.
regressions_unset_pseudo_type.php:56: Parameter #1 $object of function date_format expects DateTimeInterface, Conformance\Tests\RegressionsUnsetPseudoType\unset|DateTime given.
  • Recognition: spelling not resolved — reported on declaration line(s) 48, 51, 54
  • Enforcement: 2/5 of the expected violations reported — incidental, since the spelling was not resolved
Expectation diff
Line 54: Expected silence (quiet probe), got ["PHPDoc tag @var for variable $guarded contains unknown class Conformance\\Tests\\RegressionsUnsetPseudoType\\unset. [reported-from-level=2]","Variable $guarded in isset() always exists and is not nullable. [reported-from-level=1]"]
Line 55: Expected silence (quiet probe), got ["Call to method format() on an unknown class Conformance\\Tests\\RegressionsUnsetPseudoType\\unset. [reported-from-level=2]"]
Line 56: Expected silence (quiet probe), got ["Parameter #1 $object of function date_format expects DateTimeInterface, Conformance\\Tests\\RegressionsUnsetPseudoType\\unset|DateTime given. [reported-from-level=7]"]
psalm 6.16.1Psalm 6.16.1@f1f5de594dc76faf8784e02d3dc4716c91c6f6ac
next: 7.0.0-beta19
Unrecognized
regressions_unset_pseudo_type.php:48: Docblock-defined class, interface or enum named Conformance\Tests\RegressionsUnsetPseudoType\unset does not exist [UndefinedDocblockClass]
regressions_unset_pseudo_type.php:48: Docblock-defined class, interface or enum named Conformance\Tests\RegressionsUnsetPseudoType\unset does not exist [UndefinedDocblockClass]
regressions_unset_pseudo_type.php:51: Docblock-defined class, interface or enum named Conformance\Tests\RegressionsUnsetPseudoType\unset does not exist [UndefinedDocblockClass]
regressions_unset_pseudo_type.php:51: Argument 1 of date_format expects DateTimeInterface, but possibly different type Conformance\Tests\RegressionsUnsetPseudoType\unset|DateTime provided [PossiblyInvalidArgument]
regressions_unset_pseudo_type.php:54: Docblock-defined class, interface or enum named Conformance\Tests\RegressionsUnsetPseudoType\unset does not exist [UndefinedDocblockClass]
regressions_unset_pseudo_type.php:54: Docblock-defined type Conformance\Tests\RegressionsUnsetPseudoType\unset|DateTime for $guarded is never null [RedundantConditionGivenDocblockType]
regressions_unset_pseudo_type.php:55: Docblock-defined class, interface or enum named Conformance\Tests\RegressionsUnsetPseudoType\unset does not exist [UndefinedDocblockClass]
regressions_unset_pseudo_type.php:56: Argument 1 of date_format expects DateTimeInterface, but possibly different type Conformance\Tests\RegressionsUnsetPseudoType\unset|DateTime provided [PossiblyInvalidArgument]
  • Recognition: spelling not resolved — reported on declaration line(s) 48, 51, 54
  • Enforcement: 2/5 of the expected violations reported — incidental, since the spelling was not resolved
Expectation diff
Line 54: Expected silence (quiet probe), got ["Docblock-defined class, interface or enum named Conformance\\Tests\\RegressionsUnsetPseudoType\\unset does not exist [UndefinedDocblockClass]","Docblock-defined type Conformance\\Tests\\RegressionsUnsetPseudoType\\unset|DateTime for $guarded is never null [RedundantConditionGivenDocblockType]"]
Line 55: Expected silence (quiet probe), got ["Docblock-defined class, interface or enum named Conformance\\Tests\\RegressionsUnsetPseudoType\\unset does not exist [UndefinedDocblockClass]"]
Line 56: Expected silence (quiet probe), got ["Argument 1 of date_format expects DateTimeInterface, but possibly different type Conformance\\Tests\\RegressionsUnsetPseudoType\\unset|DateTime provided [PossiblyInvalidArgument]"]
mago 1.47.4mago 1.47.4 Unrecognized
regressions_unset_pseudo_type.php:47: Cannot find class, interface, enum, or type alias `Conformance\Tests\RegressionsUnsetPseudoType\unset`. [non-existent-class-like]
regressions_unset_pseudo_type.php:48: Attempting to access a method on a non-object type (`unknown-ref(Conformance\Tests\RegressionsUnsetPseudoType\unset)`). [invalid-method-access]
regressions_unset_pseudo_type.php:48: The first value for `echo` is too general. [mixed-argument]
regressions_unset_pseudo_type.php:50: Cannot find class, interface, enum, or type alias `Conformance\Tests\RegressionsUnsetPseudoType\unset`. [non-existent-class-like]
regressions_unset_pseudo_type.php:51: Possible argument type mismatch for argument #1 of `date_format`: expected `DateTimeInterface`, but possibly received `DateTime|unknown-ref(Conformance\Tests\RegressionsUnsetPseudoType\unset)`. [possibly-invalid-argument]
regressions_unset_pseudo_type.php:53: Cannot find class, interface, enum, or type alias `Conformance\Tests\RegressionsUnsetPseudoType\unset`. [non-existent-class-like]
regressions_unset_pseudo_type.php:54: This condition (type `true`) will always evaluate to true. [redundant-condition]
regressions_unset_pseudo_type.php:55: Attempting to access a method on a non-object type (`unknown-ref(Conformance\Tests\RegressionsUnsetPseudoType\unset)`). [invalid-method-access]
regressions_unset_pseudo_type.php:55: The first value for `echo` is too general. [mixed-argument]
regressions_unset_pseudo_type.php:56: Possible argument type mismatch for argument #1 of `date_format`: expected `DateTimeInterface`, but possibly received `DateTime|unknown-ref(Conformance\Tests\RegressionsUnsetPseudoType\unset)`. [possibly-invalid-argument]
  • Recognition: spelling not resolved — reported on declaration line(s) 47, 48, 50, 51, 53, 54
  • Enforcement: 2/5 of the expected violations reported — incidental, since the spelling was not resolved
Expectation diff
Line 54: Expected silence (quiet probe), got ["This condition (type `true`) will always evaluate to true. [redundant-condition]"]
Line 55: Expected silence (quiet probe), got ["Attempting to access a method on a non-object type (`unknown-ref(Conformance\\Tests\\RegressionsUnsetPseudoType\\unset)`). [invalid-method-access]","The first value for `echo` is too general. [mixed-argument]"]
Line 56: Expected silence (quiet probe), got ["Possible argument type mismatch for argument #1 of `date_format`: expected `DateTimeInterface`, but possibly received `DateTime|unknown-ref(Conformance\\Tests\\RegressionsUnsetPseudoType\\unset)`. [possibly-invalid-argument]"]
mir 0.72.1mir 0.72.1 Unrecognized
regressions_unset_pseudo_type.php:48: UndefinedDocblockClass: Docblock type 'Conformance\Tests\RegressionsUnsetPseudoType\unset' does not exist [MIR1505]
regressions_unset_pseudo_type.php:51: UndefinedDocblockClass: Docblock type 'Conformance\Tests\RegressionsUnsetPseudoType\unset' does not exist [MIR1505]
regressions_unset_pseudo_type.php:54: UndefinedDocblockClass: Docblock type 'Conformance\Tests\RegressionsUnsetPseudoType\unset' does not exist [MIR1505]
regressions_unset_pseudo_type.php:54: RedundantCondition: Condition is always true/false for type 'bool' [MIR0400]
  • Recognition: spelling not resolved — reported on declaration line(s) 48, 51, 54
  • Enforcement: 4/5 of the expected violations reported — incidental, since the spelling was not resolved
Expectation diff
Line 54: Expected silence (quiet probe), got ["UndefinedDocblockClass: Docblock type 'Conformance\\Tests\\RegressionsUnsetPseudoType\\unset' does not exist [MIR1505]","RedundantCondition: Condition is always true/false for type 'bool' [MIR0400]"]
phpantom 0.10.0phpantom_lsp 0.10.0 Unrecognized
regressions_unset_pseudo_type.php:47: Class 'Conformance\Tests\RegressionsUnsetPseudoType\unset' not found [unknown_class]
regressions_unset_pseudo_type.php:50: Class 'Conformance\Tests\RegressionsUnsetPseudoType\unset' not found [unknown_class]
regressions_unset_pseudo_type.php:53: Class 'Conformance\Tests\RegressionsUnsetPseudoType\unset' not found [unknown_class]
  • Recognition: spelling not resolved — reported on declaration line(s) 47, 50, 53
  • Enforcement: 3/5 of the expected violations reported — incidental, since the spelling was not resolved
intelephense 1.18.5intelephense 1.18.5 Unrecognized
regressions_unset_pseudo_type.php:48: Expected type 'object'. Found 'DateTime|unset'. [P1006]
regressions_unset_pseudo_type.php:51: Expected type 'DateTimeInterface'. Found 'DateTime|unset'. [P1006]
  • Recognition: spelling not resolved — reported on declaration line(s) 48, 51
  • Enforcement: 5/5 of the expected violations reported — incidental, since the spelling was not resolved
phpactor 2026.07.22.0Phpactor 2026.07.22.0 Partly enforced (3/5)
regressions_unset_pseudo_type.php:44: Undefined variable "$defined"
regressions_unset_pseudo_type.php:45: Undefined variable "$defined"
  • Recognition: spelling resolved
  • Enforcement: 3/5 of the expected violations reported
Expectation diff
Line 44: Expected valid value to be accepted, got ["Undefined variable \"$defined\""]
Line 45: Expected valid value to be accepted, got ["Undefined variable \"$defined\""]
phpy 0.2.0phpy 0.2.0 Unrecognized
regressions_unset_pseudo_type.php:47: Use of unknown class: 'Conformance\Tests\RegressionsUnsetPseudoType\unset'
regressions_unset_pseudo_type.php:50: Use of unknown class: 'Conformance\Tests\RegressionsUnsetPseudoType\unset'
regressions_unset_pseudo_type.php:53: Use of unknown class: 'Conformance\Tests\RegressionsUnsetPseudoType\unset'
  • Recognition: spelling not resolved — reported on declaration line(s) 47, 50, 53
  • Enforcement: 3/5 of the expected violations reported — incidental, since the spelling was not resolved
qodana 262.9437.196Qodana 262.9437.196 Unrecognized
regressions_unset_pseudo_type.php:47: Undefined class 'unset' [PhpUndefinedClassInspection]
regressions_unset_pseudo_type.php:50: Undefined class 'unset' [PhpUndefinedClassInspection]
regressions_unset_pseudo_type.php:53: Undefined class 'unset' [PhpUndefinedClassInspection]
  • Recognition: spelling not resolved — reported on declaration line(s) 47, 50, 53
  • Enforcement: 3/5 of the expected violations reported — incidental, since the spelling was not resolved
noverify 0.5.5NoVerify, version 0.5.5: built on: 2025.04.22 14:36:46 OS: r-kuchinskas arm64 Commit: 4774b82f8adc53fbef38f95e97af4b953b4d1529 Unrecognized
regressions_unset_pseudo_type.php:47: Class or interface named \Conformance\Tests\RegressionsUnsetPseudoType\unset does not exist [undefinedClass]
regressions_unset_pseudo_type.php:50: Class or interface named \Conformance\Tests\RegressionsUnsetPseudoType\unset does not exist [undefinedClass]
regressions_unset_pseudo_type.php:51: potentially not safe call in function date_format signature of param object [notSafeCall]
regressions_unset_pseudo_type.php:53: Class or interface named \Conformance\Tests\RegressionsUnsetPseudoType\unset does not exist [undefinedClass]
regressions_unset_pseudo_type.php:56: potentially not safe call in function date_format signature of param object [notSafeCall]
  • Recognition: spelling not resolved — reported on declaration line(s) 47, 50, 51, 53
  • Enforcement: 3/5 of the expected violations reported — incidental, since the spelling was not resolved
Expectation diff
Line 56: Expected silence (quiet probe), got ["potentially not safe call in function date_format signature of param object [notSafeCall]"]
steins 0.1.6steins 0.1.6 (2026-08-25 revision c13b4c7) Unrecognized
regressions_unset_pseudo_type.php:48: $read is declared \DateTime|unset and may be undefined at this read — guard it with isset($read) or give it a default [phpdoc.maybe-undefined]
regressions_unset_pseudo_type.php:51: $passed is declared \DateTime|unset and may be undefined at this read — guard it with isset($passed) or give it a default [phpdoc.maybe-undefined]
  • Recognition: spelling not resolved — reported on declaration line(s) 48, 51
  • Enforcement: 5/5 of the expected violations reported — incidental, since the spelling was not resolved
phpstan-strict 2.2.11PHPStan - PHP Static Analysis Tool 2.2.11 Unrecognized
regressions_unset_pseudo_type.php:48: Call to method format() on an unknown class Conformance\Tests\RegressionsUnsetPseudoType\unset.
regressions_unset_pseudo_type.php:48: PHPDoc tag @var for variable $read contains unknown class Conformance\Tests\RegressionsUnsetPseudoType\unset.
regressions_unset_pseudo_type.php:51: PHPDoc tag @var for variable $passed contains unknown class Conformance\Tests\RegressionsUnsetPseudoType\unset.
regressions_unset_pseudo_type.php:51: Parameter #1 $object of function date_format expects DateTimeInterface, Conformance\Tests\RegressionsUnsetPseudoType\unset|DateTime given.
regressions_unset_pseudo_type.php:54: PHPDoc tag @var for variable $guarded contains unknown class Conformance\Tests\RegressionsUnsetPseudoType\unset.
regressions_unset_pseudo_type.php:54: Variable $guarded in isset() always exists and is not nullable.
regressions_unset_pseudo_type.php:55: Call to method format() on an unknown class Conformance\Tests\RegressionsUnsetPseudoType\unset.
regressions_unset_pseudo_type.php:56: Parameter #1 $object of function date_format expects DateTimeInterface, Conformance\Tests\RegressionsUnsetPseudoType\unset|DateTime given.
  • Recognition: spelling not resolved — reported on declaration line(s) 48, 51, 54
  • Enforcement: 2/5 of the expected violations reported — incidental, since the spelling was not resolved
Expectation diff
Line 54: Expected silence (quiet probe), got ["PHPDoc tag @var for variable $guarded contains unknown class Conformance\\Tests\\RegressionsUnsetPseudoType\\unset.","Variable $guarded in isset() always exists and is not nullable."]
Line 55: Expected silence (quiet probe), got ["Call to method format() on an unknown class Conformance\\Tests\\RegressionsUnsetPseudoType\\unset."]
Line 56: Expected silence (quiet probe), got ["Parameter #1 $object of function date_format expects DateTimeInterface, Conformance\\Tests\\RegressionsUnsetPseudoType\\unset|DateTime given."]
psalm-next 7.0.0-beta19Psalm 7.0.0-beta19@7e751c06a756fa64dc4c759c09fe4a173afcb433 Unrecognized
regressions_unset_pseudo_type.php:48: Docblock-defined class, interface or enum named Conformance\Tests\RegressionsUnsetPseudoType\unset does not exist [UndefinedDocblockClass]
regressions_unset_pseudo_type.php:48: Docblock-defined class, interface or enum named Conformance\Tests\RegressionsUnsetPseudoType\unset does not exist [UndefinedDocblockClass]
regressions_unset_pseudo_type.php:51: Docblock-defined class, interface or enum named Conformance\Tests\RegressionsUnsetPseudoType\unset does not exist [UndefinedDocblockClass]
regressions_unset_pseudo_type.php:51: Argument 1 of date_format expects DateTimeInterface, but possibly different type Conformance\Tests\RegressionsUnsetPseudoType\unset|DateTime provided [PossiblyInvalidArgument]
regressions_unset_pseudo_type.php:54: Docblock-defined class, interface or enum named Conformance\Tests\RegressionsUnsetPseudoType\unset does not exist [UndefinedDocblockClass]
regressions_unset_pseudo_type.php:54: Docblock-defined type Conformance\Tests\RegressionsUnsetPseudoType\unset|DateTime for $guarded is never null [RedundantConditionGivenDocblockType]
regressions_unset_pseudo_type.php:55: Docblock-defined class, interface or enum named Conformance\Tests\RegressionsUnsetPseudoType\unset does not exist [UndefinedDocblockClass]
regressions_unset_pseudo_type.php:56: Argument 1 of date_format expects DateTimeInterface, but possibly different type Conformance\Tests\RegressionsUnsetPseudoType\unset|DateTime provided [PossiblyInvalidArgument]
  • Recognition: spelling not resolved — reported on declaration line(s) 48, 51, 54
  • Enforcement: 2/5 of the expected violations reported — incidental, since the spelling was not resolved
Expectation diff
Line 54: Expected silence (quiet probe), got ["Docblock-defined class, interface or enum named Conformance\\Tests\\RegressionsUnsetPseudoType\\unset does not exist [UndefinedDocblockClass]","Docblock-defined type Conformance\\Tests\\RegressionsUnsetPseudoType\\unset|DateTime for $guarded is never null [RedundantConditionGivenDocblockType]"]
Line 55: Expected silence (quiet probe), got ["Docblock-defined class, interface or enum named Conformance\\Tests\\RegressionsUnsetPseudoType\\unset does not exist [UndefinedDocblockClass]"]
Line 56: Expected silence (quiet probe), got ["Argument 1 of date_format expects DateTimeInterface, but possibly different type Conformance\\Tests\\RegressionsUnsetPseudoType\\unset|DateTime provided [PossiblyInvalidArgument]"]

Source

<?php

declare(strict_types=1);

namespace Conformance\Tests\RegressionsUnsetPseudoType;

/**
 * `unset` as a possibly-undefined pseudo-type.
 *
 * In a top-level script, a `@var` union carrying `unset` — the Blade-view
 * idiom — states that the variable is either the given type or not defined
 * at all. Reading `unset` as the undefined state, or as a nullable reading
 * of the union, are both honest interpretations; resolving it as a class
 * name is the defect. PHPantom reports "Class 'unset' not found" for it
 * (issue #366), and most tools measured here resolve the spelling the same
 * way — as an unknown class rather than as the possibly-undefined state.
 *
 * Each probe gets its own variable so that one read cannot narrow the next.
 * A method call and a function argument are both unsafe on the undefined
 * path, so a tool that models the state flags them. Inside an `isset()`
 * guard the undefined path is gone: the guarded reads must stay silent, and
 * the guard itself must not be reported as redundant — a variable that may
 * be undefined makes `isset()` meaningful.
 *
 * The `// V` controls repeat both reads under a plain `\DateTime` `@var`. A
 * diagnostic there means the reactions below are about a top-level variable
 * with no assignment, not about the `unset` member of the union.
 *
 * mir looked like the exception and is not one. Its source resolves `unset`
 * the same way as the rest: the spelling is absent from the docblock keyword
 * table, falls through to the catch-all named-object atom, and is flagged
 * because it is not in the `is_pseudo_type` exemption list. That diagnostic
 * is info severity, which this adapter used to ask for only on `debug_*`
 * files — mir's apparent silence here was the harness's, and MirChecker now
 * reads info everywhere. mir does carry a possibly-undefined concept, but it
 * is set from control flow alone and no docblock type can reach it, so
 * exempting the spelling would quiet the complaint without implementing any
 * of the semantics above.
 *
 * Source lead: PHPantom-dev/phpantom_lsp#366. Tracked here in #7.
 */

/** @var \DateTime $defined */
echo $defined->format('Y-m-d'); // V: the same method call without `unset` in the union
echo date_format($defined, 'Y-m-d'); // V: the same argument without `unset` in the union

/** @var \DateTime|unset $read */
echo $read->format('Y-m-d'); // E?: $read may be undefined (`unset`) or not a DateTime // T: unset

/** @var \DateTime|unset $passed */
echo date_format($passed, 'Y-m-d'); // E?: same through a function argument // T: unset

/** @var \DateTime|unset $guarded */
if (isset($guarded)) { // Q: `unset` in the union makes the guard meaningful, not redundant // T: unset
    echo $guarded->format('Y-m-d'); // Q: isset() must narrow the undefined state away
    echo date_format($guarded, 'Y-m-d'); // Q: same through a function argument
}