1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PHPStan\Php\PhpVersion;
6: use PHPStan\TrinaryLogic;
7: use PHPStan\Turbo\ReferencedByTurboExtension;
8:
9: /**
10: * Marker interface for types that require bidirectional type comparison.
11: *
12: * Simple types like `StringType` or `IntegerType` can answer `isSuperTypeOf()`
13: * and `accepts()` on their own — they check whether the incoming type fits.
14: * But compound types (unions, intersections, mixed, never, accessory types,
15: * integer ranges, callables, iterables, conditionals, etc.) need to be asked
16: * from the other direction, because they carry internal structure that the
17: * simple type on the other side knows nothing about.
18: *
19: * The protocol works like a double dispatch:
20: *
21: * 1. A simple type's `accepts()`/`isSuperTypeOf()` receives an argument.
22: * 2. It checks `if ($type instanceof CompoundType)`.
23: * 3. If true, it delegates to `$type->isAcceptedBy($this, …)` or `$type->isSubTypeOf($this)`.
24: * 4. The compound type then decomposes itself (e.g., iterates union members)
25: * and calls back to the simple type for each component.
26: *
27: * This avoids the simple type having to understand union/intersection/mixed/never
28: * semantics. For example, `StringType::accepts()` doesn't need to know how to
29: * check a `UnionType<string|int>` — it just delegates to `UnionType::isAcceptedBy()`,
30: * which iterates its members and asks `StringType::accepts()` for each one.
31: *
32: * Unlike `instanceof SomeSpecificType` checks (which are discouraged in CLAUDE.md),
33: * `instanceof CompoundType` is the correct and intended pattern throughout the
34: * type system. It is part of the double-dispatch protocol, not a type query.
35: *
36: * Implementations include:
37: * - `UnionType` — `isSubTypeOf()` requires ALL members to be subtypes, `isAcceptedBy()` requires ALL to be accepted
38: * - `IntersectionType` — `isSubTypeOf()` requires at least ONE member to be a subtype (via `maxMin`)
39: * - `MixedType`, `NeverType` — terminal cases (mixed accepts everything, never is subtype of everything)
40: * - All `AccessoryType` implementations — refinement types that live inside intersections
41: * - `IntegerRangeType`, `CallableType`, `IterableType` — types with internal structure
42: * - `ConditionalType`, `KeyOfType`, `ValueOfType`, etc. — late-resolvable types
43: *
44: * @api
45: * @api-do-not-implement
46: */
47: #[ReferencedByTurboExtension(key: 'compoundType')]
48: interface CompoundType extends Type
49: {
50:
51: /**
52: * Answers "is this compound type accepted by $acceptingType?" from the compound type's perspective.
53: *
54: * Called by simple types when they encounter a CompoundType argument in their `accepts()` method.
55: * The compound type decomposes itself and calls `$acceptingType->accepts()` for each component.
56: *
57: * For example, `UnionType(string|int)::isAcceptedBy(StringType)` asks StringType to accept
58: * `string` and `int` separately, then combines results with `extremeIdentity` (all must pass).
59: */
60: public function isAcceptedBy(Type $acceptingType, bool $strictTypes): AcceptsResult;
61:
62: /**
63: * Answers "is this compound type a subtype of $otherType?" from the compound type's perspective.
64: *
65: * Called by simple types when they encounter a CompoundType argument in their `isSuperTypeOf()` method.
66: * The compound type decomposes itself and calls `$otherType->isSuperTypeOf()` for each component.
67: *
68: * For example, `UnionType(string|int)::isSubTypeOf(MixedType)` asks MixedType whether it is
69: * a supertype of `string` and `int` separately, then combines with `extremeIdentity` (all must pass).
70: */
71: public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult;
72:
73: /**
74: * Compares this compound type against $otherType using greater-than semantics.
75: *
76: * Used for comparison operators (`>`). Each compound type decomposes the comparison
77: * across its members (e.g., IntegerRangeType checks whether all values in the range
78: * are greater than the other type).
79: */
80: public function isGreaterThan(Type $otherType, PhpVersion $phpVersion): TrinaryLogic;
81:
82: /**
83: * Compares this compound type against $otherType using greater-than-or-equal semantics.
84: *
85: * Used for comparison operators (`>=`). Same decomposition strategy as `isGreaterThan()`.
86: */
87: public function isGreaterThanOrEqual(Type $otherType, PhpVersion $phpVersion): TrinaryLogic;
88:
89: }
90: