1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Reflection;
4:
5: use PHPStan\PhpDoc\ResolvedPhpDocBlock;
6: use PHPStan\PhpDoc\Tag\AssertTag;
7: use PHPStan\Type\Type;
8: use function array_filter;
9: use function array_map;
10: use function array_merge;
11: use function count;
12:
13: /**
14: * Collection of @phpstan-assert annotations on a function or method.
15: *
16: * PHPStan supports type assertions via PHPDoc annotations:
17: * - `@phpstan-assert Type $param` — narrows the parameter type unconditionally
18: * - `@phpstan-assert-if-true Type $param` — narrows when the method returns true
19: * - `@phpstan-assert-if-false Type $param` — narrows when the method returns false
20: *
21: * This class collects all such assertions and provides methods to retrieve them
22: * by condition type. It also handles negation: an `@phpstan-assert-if-true` assertion
23: * is automatically negated and included in the `getAssertsIfFalse()` result.
24: *
25: * Returned by ExtendedMethodReflection::getAsserts() and FunctionReflection::getAsserts().
26: *
27: * @api
28: */
29: final class Assertions
30: {
31:
32: private static ?self $empty = null;
33:
34: /**
35: * @param AssertTag[] $asserts
36: */
37: private function __construct(private array $asserts)
38: {
39: }
40:
41: /** @return AssertTag[] */
42: public function getAll(): array
43: {
44: return $this->asserts;
45: }
46:
47: /**
48: * Unconditional assertions — narrow parameter types regardless of the method's return value.
49: *
50: * @return AssertTag[]
51: */
52: public function getAsserts(): array
53: {
54: return array_filter($this->asserts, static fn (AssertTag $assert) => $assert->getIf() === AssertTag::NULL);
55: }
56:
57: /**
58: * Includes @phpstan-assert-if-true tags and negated @phpstan-assert-if-false tags.
59: *
60: * @return AssertTag[]
61: */
62: public function getAssertsIfTrue(): array
63: {
64: return array_merge(
65: array_filter($this->asserts, static fn (AssertTag $assert) => $assert->getIf() === AssertTag::IF_TRUE),
66: array_map(
67: static fn (AssertTag $assert) => $assert->negate(),
68: array_filter($this->asserts, static fn (AssertTag $assert) => $assert->getIf() === AssertTag::IF_FALSE && !$assert->isEquality()),
69: ),
70: );
71: }
72:
73: /**
74: * Includes @phpstan-assert-if-false tags and negated @phpstan-assert-if-true tags.
75: *
76: * @return AssertTag[]
77: */
78: public function getAssertsIfFalse(): array
79: {
80: return array_merge(
81: array_filter($this->asserts, static fn (AssertTag $assert) => $assert->getIf() === AssertTag::IF_FALSE),
82: array_map(
83: static fn (AssertTag $assert) => $assert->negate(),
84: array_filter($this->asserts, static fn (AssertTag $assert) => $assert->getIf() === AssertTag::IF_TRUE && !$assert->isEquality()),
85: ),
86: );
87: }
88:
89: /** @param callable(Type): Type $callable */
90: public function mapTypes(callable $callable): self
91: {
92: $assertTagsCallback = static fn (AssertTag $tag): AssertTag => $tag->withType($callable($tag->getType()));
93:
94: return new self(array_map($assertTagsCallback, $this->asserts));
95: }
96:
97: public function intersectWith(Assertions $other): self
98: {
99: return new self(array_merge($this->getAll(), $other->getAll()));
100: }
101:
102: public static function createEmpty(): self
103: {
104: $empty = self::$empty;
105:
106: if ($empty !== null) {
107: return $empty;
108: }
109:
110: $empty = new self([]);
111: self::$empty = $empty;
112:
113: return $empty;
114: }
115:
116: public static function createFromResolvedPhpDocBlock(ResolvedPhpDocBlock $phpDocBlock): self
117: {
118: $tags = $phpDocBlock->getAssertTags();
119: if (count($tags) === 0) {
120: return self::createEmpty();
121: }
122:
123: return new self($tags);
124: }
125:
126: }
127: