Skip to content

Commit e800ce8

Browse files
ondrejmirtesclaude
andcommitted
Resolve isset/empty/?? chains once via IssetabilityResolver
MutatingScope::issetCheck() and Rules\IssetCheck were two hand-maintained mirrors of the same chain walk (variable / offset / property / leaf), each re-resolving types and property reflections with its own drifting copy of the logic. IssetabilityResolver now walks the chain once and resolves every link's facts into an IssetabilityResolution; the engine folds it via isSet() and the rule projects the same links into messages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7
1 parent 45adaa9 commit e800ce8

12 files changed

Lines changed: 680 additions & 296 deletions

src/Analyser/DirectInternalScopeFactory.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public function __construct(
3333
private ExprPrinter $exprPrinter,
3434
private TypeSpecifier $typeSpecifier,
3535
private PropertyReflectionFinder $propertyReflectionFinder,
36+
private IssetabilityResolver $issetabilityResolver,
3637
private Parser $parser,
3738
private PhpVersion $phpVersion,
3839
private AttributeReflectionFactory $attributeReflectionFactory,
@@ -77,6 +78,7 @@ public function create(
7778
$this->exprPrinter,
7879
$this->typeSpecifier,
7980
$this->propertyReflectionFinder,
81+
$this->issetabilityResolver,
8082
$this->parser,
8183
$this->constantResolver,
8284
$context,
@@ -112,6 +114,7 @@ public function toFiberFactory(): InternalScopeFactory
112114
$this->exprPrinter,
113115
$this->typeSpecifier,
114116
$this->propertyReflectionFinder,
117+
$this->issetabilityResolver,
115118
$this->parser,
116119
$this->phpVersion,
117120
$this->attributeReflectionFactory,
@@ -132,6 +135,7 @@ public function toMutatingFactory(): InternalScopeFactory
132135
$this->exprPrinter,
133136
$this->typeSpecifier,
134137
$this->propertyReflectionFinder,
138+
$this->issetabilityResolver,
135139
$this->parser,
136140
$this->phpVersion,
137141
$this->attributeReflectionFactory,

src/Analyser/DirectInternalScopeFactoryFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public function __construct(
2929
private ExprPrinter $exprPrinter,
3030
private TypeSpecifier $typeSpecifier,
3131
private PropertyReflectionFinder $propertyReflectionFinder,
32+
private IssetabilityResolver $issetabilityResolver,
3233
private Parser $parser,
3334
private PhpVersion $phpVersion,
3435
private AttributeReflectionFactory $attributeReflectionFactory,
@@ -51,6 +52,7 @@ public function create(?callable $nodeCallback): DirectInternalScopeFactory
5152
$this->exprPrinter,
5253
$this->typeSpecifier,
5354
$this->propertyReflectionFinder,
55+
$this->issetabilityResolver,
5456
$this->parser,
5557
$this->phpVersion,
5658
$this->attributeReflectionFactory,
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser;
4+
5+
use PhpParser\Node\Expr;
6+
use PHPStan\Rules\Properties\FoundPropertyReflection;
7+
use PHPStan\ShouldNotHappenException;
8+
use PHPStan\TrinaryLogic;
9+
use PHPStan\Type\Type;
10+
11+
/**
12+
* One resolved link of an isset/empty/?? chain. IssetabilityDescriptor::resolve()
13+
* walks the chain once and produces these (the expensive part: types, offset
14+
* existence, property reflection are resolved here and never again), so the engine
15+
* (IssetabilityResolution::isSet) and the rule (PHPStan\Rules\IssetCheck) read the
16+
* facts instead of re-walking and re-resolving.
17+
*/
18+
final class IssetabilityLinkInfo
19+
{
20+
21+
private const KIND_VARIABLE = 'variable';
22+
private const KIND_OFFSET = 'offset';
23+
private const KIND_PROPERTY = 'property';
24+
private const KIND_LEAF = 'leaf';
25+
26+
private function __construct(
27+
private string $kind,
28+
private ?string $variableName = null,
29+
private ?TrinaryLogic $hasVariable = null,
30+
private ?TrinaryLogic $isOffsetAccessible = null,
31+
private ?TrinaryLogic $hasOffsetValue = null,
32+
private bool $hasExpressionTypeOfExpr = false,
33+
private ?Type $varType = null,
34+
private ?Type $dimType = null,
35+
private ?Type $valueType = null,
36+
private ?FoundPropertyReflection $propertyReflection = null,
37+
private ?Expr $propertyFetch = null,
38+
private bool $reflectionNative = false,
39+
private bool $hasNativeType = false,
40+
private ?TrinaryLogic $isVirtual = null,
41+
private ?Type $nativeType = null,
42+
private bool $hasExpressionTypeOfFetch = false,
43+
private bool $initializedThisProperty = false,
44+
private bool $nativeReflectionExists = false,
45+
private bool $nativeIsPromoted = false,
46+
private bool $nativeIsReadOnly = false,
47+
private bool $nativeIsHooked = false,
48+
private bool $nativeHasDefaultValue = false,
49+
private ?Expr $leafExpr = null,
50+
private bool $leafIsNullsafePropertyFetch = false,
51+
)
52+
{
53+
}
54+
55+
public static function variable(string $variableName, TrinaryLogic $hasVariable, Type $valueType): self
56+
{
57+
return new self(self::KIND_VARIABLE, variableName: $variableName, hasVariable: $hasVariable, valueType: $valueType);
58+
}
59+
60+
public static function offset(TrinaryLogic $isOffsetAccessible, TrinaryLogic $hasOffsetValue, bool $hasExpressionTypeOfExpr, Type $varType, Type $dimType, Type $valueType): self
61+
{
62+
return new self(
63+
self::KIND_OFFSET,
64+
isOffsetAccessible: $isOffsetAccessible,
65+
hasOffsetValue: $hasOffsetValue,
66+
hasExpressionTypeOfExpr: $hasExpressionTypeOfExpr,
67+
varType: $varType,
68+
dimType: $dimType,
69+
valueType: $valueType,
70+
);
71+
}
72+
73+
public static function property(
74+
?FoundPropertyReflection $propertyReflection,
75+
Expr $propertyFetch,
76+
bool $reflectionNative,
77+
bool $hasNativeType,
78+
TrinaryLogic $isVirtual,
79+
Type $writableType,
80+
Type $nativeType,
81+
bool $hasExpressionTypeOfFetch,
82+
bool $initializedThisProperty,
83+
bool $nativeReflectionExists,
84+
bool $nativeIsPromoted,
85+
bool $nativeIsReadOnly,
86+
bool $nativeIsHooked,
87+
bool $nativeHasDefaultValue,
88+
): self
89+
{
90+
return new self(
91+
self::KIND_PROPERTY,
92+
valueType: $writableType,
93+
propertyReflection: $propertyReflection,
94+
propertyFetch: $propertyFetch,
95+
reflectionNative: $reflectionNative,
96+
hasNativeType: $hasNativeType,
97+
isVirtual: $isVirtual,
98+
nativeType: $nativeType,
99+
hasExpressionTypeOfFetch: $hasExpressionTypeOfFetch,
100+
initializedThisProperty: $initializedThisProperty,
101+
nativeReflectionExists: $nativeReflectionExists,
102+
nativeIsPromoted: $nativeIsPromoted,
103+
nativeIsReadOnly: $nativeIsReadOnly,
104+
nativeIsHooked: $nativeIsHooked,
105+
nativeHasDefaultValue: $nativeHasDefaultValue,
106+
);
107+
}
108+
109+
public static function leaf(Type $valueType, Expr $leafExpr, bool $leafIsNullsafePropertyFetch): self
110+
{
111+
return new self(self::KIND_LEAF, valueType: $valueType, leafExpr: $leafExpr, leafIsNullsafePropertyFetch: $leafIsNullsafePropertyFetch);
112+
}
113+
114+
public function isVariable(): bool
115+
{
116+
return $this->kind === self::KIND_VARIABLE;
117+
}
118+
119+
public function isOffset(): bool
120+
{
121+
return $this->kind === self::KIND_OFFSET;
122+
}
123+
124+
public function isProperty(): bool
125+
{
126+
return $this->kind === self::KIND_PROPERTY;
127+
}
128+
129+
public function getVariableName(): string
130+
{
131+
if ($this->variableName === null) {
132+
throw new ShouldNotHappenException();
133+
}
134+
135+
return $this->variableName;
136+
}
137+
138+
public function getHasVariable(): TrinaryLogic
139+
{
140+
if ($this->hasVariable === null) {
141+
throw new ShouldNotHappenException();
142+
}
143+
144+
return $this->hasVariable;
145+
}
146+
147+
/** The type the operator's callback inspects: variable type, offset value type, property writable type, or leaf type. */
148+
public function getValueType(): Type
149+
{
150+
if ($this->valueType === null) {
151+
throw new ShouldNotHappenException();
152+
}
153+
154+
return $this->valueType;
155+
}
156+
157+
public function getIsOffsetAccessible(): TrinaryLogic
158+
{
159+
if ($this->isOffsetAccessible === null) {
160+
throw new ShouldNotHappenException();
161+
}
162+
163+
return $this->isOffsetAccessible;
164+
}
165+
166+
public function getHasOffsetValue(): TrinaryLogic
167+
{
168+
if ($this->hasOffsetValue === null) {
169+
throw new ShouldNotHappenException();
170+
}
171+
172+
return $this->hasOffsetValue;
173+
}
174+
175+
public function hasExpressionTypeOfExpr(): bool
176+
{
177+
return $this->hasExpressionTypeOfExpr;
178+
}
179+
180+
public function getVarType(): Type
181+
{
182+
if ($this->varType === null) {
183+
throw new ShouldNotHappenException();
184+
}
185+
186+
return $this->varType;
187+
}
188+
189+
public function getDimType(): Type
190+
{
191+
if ($this->dimType === null) {
192+
throw new ShouldNotHappenException();
193+
}
194+
195+
return $this->dimType;
196+
}
197+
198+
public function getPropertyReflection(): ?FoundPropertyReflection
199+
{
200+
return $this->propertyReflection;
201+
}
202+
203+
/**
204+
* @return Expr\PropertyFetch|Expr\StaticPropertyFetch
205+
*/
206+
public function getPropertyFetch(): Expr
207+
{
208+
if (!$this->propertyFetch instanceof Expr\PropertyFetch && !$this->propertyFetch instanceof Expr\StaticPropertyFetch) {
209+
throw new ShouldNotHappenException();
210+
}
211+
212+
return $this->propertyFetch;
213+
}
214+
215+
public function isReflectionNative(): bool
216+
{
217+
return $this->reflectionNative;
218+
}
219+
220+
public function hasNativeType(): bool
221+
{
222+
return $this->hasNativeType;
223+
}
224+
225+
public function isVirtual(): TrinaryLogic
226+
{
227+
if ($this->isVirtual === null) {
228+
throw new ShouldNotHappenException();
229+
}
230+
231+
return $this->isVirtual;
232+
}
233+
234+
public function getNativeType(): Type
235+
{
236+
if ($this->nativeType === null) {
237+
throw new ShouldNotHappenException();
238+
}
239+
240+
return $this->nativeType;
241+
}
242+
243+
public function hasExpressionTypeOfFetch(): bool
244+
{
245+
return $this->hasExpressionTypeOfFetch;
246+
}
247+
248+
public function isInitializedThisProperty(): bool
249+
{
250+
return $this->initializedThisProperty;
251+
}
252+
253+
public function nativeReflectionExists(): bool
254+
{
255+
return $this->nativeReflectionExists;
256+
}
257+
258+
public function nativeIsPromoted(): bool
259+
{
260+
return $this->nativeIsPromoted;
261+
}
262+
263+
public function nativeIsReadOnly(): bool
264+
{
265+
return $this->nativeIsReadOnly;
266+
}
267+
268+
public function nativeIsHooked(): bool
269+
{
270+
return $this->nativeIsHooked;
271+
}
272+
273+
public function nativeHasDefaultValue(): bool
274+
{
275+
return $this->nativeHasDefaultValue;
276+
}
277+
278+
public function getLeafExpr(): Expr
279+
{
280+
if ($this->leafExpr === null) {
281+
throw new ShouldNotHappenException();
282+
}
283+
284+
return $this->leafExpr;
285+
}
286+
287+
public function leafIsNullsafePropertyFetch(): bool
288+
{
289+
return $this->leafIsNullsafePropertyFetch;
290+
}
291+
292+
}

0 commit comments

Comments
 (0)