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