Skip to content

Commit 3beb667

Browse files
committed
ObjectShapeType - do not access Type::getProperty() before making sure Type::hasProperty() is yes
1 parent c33c5a1 commit 3beb667

File tree

4 files changed

+47
-30
lines changed

4 files changed

+47
-30
lines changed

src/Type/ObjectShapeType.php

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -159,29 +159,21 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult
159159
if (in_array($propertyName, $this->optionalProperties, true)) {
160160
continue;
161161
}
162-
return $hasProperty;
162+
$result = $result->and($hasProperty);
163+
continue;
163164
}
164-
if ($hasProperty->maybe() && in_array($propertyName, $this->optionalProperties, true)) {
165+
if ($hasProperty->maybe()) {
166+
if (!in_array($propertyName, $this->optionalProperties, true)) {
167+
$result = $result->and($hasProperty);
168+
continue;
169+
170+
}
171+
165172
$hasProperty = AcceptsResult::createYes();
166173
}
167174

168175
$result = $result->and($hasProperty);
169-
170-
try {
171-
$otherProperty = $type->getProperty($propertyName, $scope);
172-
} catch (MissingPropertyFromReflectionException) {
173-
return new AcceptsResult(
174-
$result->result,
175-
[
176-
sprintf(
177-
'%s %s not have property $%s.',
178-
$type->describe(VerbosityLevel::typeOnly()),
179-
$result->no() ? 'does' : 'might',
180-
$propertyName,
181-
),
182-
],
183-
);
184-
}
176+
$otherProperty = $type->getProperty($propertyName, $scope);
185177
if (!$otherProperty->isPublic()) {
186178
return new AcceptsResult(TrinaryLogic::createNo(), [
187179
sprintf('Property %s::$%s is not public.', $otherProperty->getDeclaringClass()->getDisplayName(), $propertyName),
@@ -260,20 +252,20 @@ public function isSuperTypeOf(Type $type): IsSuperTypeOfResult
260252
if (in_array($propertyName, $this->optionalProperties, true)) {
261253
continue;
262254
}
263-
return $hasProperty;
255+
$result = $result->and($hasProperty);
256+
continue;
264257
}
265-
if ($hasProperty->maybe() && in_array($propertyName, $this->optionalProperties, true)) {
258+
if ($hasProperty->maybe()) {
259+
if (!in_array($propertyName, $this->optionalProperties, true)) {
260+
$result = $result->and($hasProperty);
261+
continue;
262+
}
263+
266264
$hasProperty = IsSuperTypeOfResult::createYes();
267265
}
268266

269267
$result = $result->and($hasProperty);
270-
271-
try {
272-
$otherProperty = $type->getProperty($propertyName, $scope);
273-
} catch (MissingPropertyFromReflectionException) {
274-
return $result;
275-
}
276-
268+
$otherProperty = $type->getProperty($propertyName, $scope);
277269
if (!$otherProperty->isPublic()) {
278270
return IsSuperTypeOfResult::createNo();
279271
}

tests/PHPStan/Analyser/AnalyserIntegrationTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,14 @@ public function testBug2823(): void
201201
$this->assertNoErrors($errors);
202202
}
203203

204+
public function testBug13424(): void
205+
{
206+
$errors = $this->runAnalyse(__DIR__ . '/data/bug-13424.php');
207+
$this->assertCount(1, $errors);
208+
$this->assertSame('Instantiated class Bug13424\Hello not found.', $errors[0]->getMessage());
209+
$this->assertSame(14, $errors[0]->getLine());
210+
}
211+
204212
public function testTwoSameClassesInSingleFile(): void
205213
{
206214
$errors = $this->runAnalyse(__DIR__ . '/data/two-same-classes.php');
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Bug13424;
4+
5+
final class DemoFile
6+
{
7+
/** @param mixed[] $args */
8+
public function run( $args ): void {
9+
if ( ! empty( $args['bar'] ) ) {
10+
$hello = (object) array(
11+
'a' => 'b',
12+
);
13+
} else {
14+
$hello = new Hello( $args );
15+
}
16+
}
17+
}

tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3000,12 +3000,12 @@ public function testObjectShapes(): void
30003000
[
30013001
'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, Exception given.',
30023002
14,
3003-
PHP_VERSION_ID >= 80200 ? 'Exception does not have property $foo.' : 'Exception might not have property $foo.',
3003+
PHP_VERSION_ID >= 80200 ? "Exception does not have property \$foo.\n• Exception does not have property \$bar." : "Exception might not have property \$foo.\n• Exception might not have property \$bar.",
30043004
],
30053005
[
30063006
'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, Exception given.',
30073007
15,
3008-
'Exception might not have property $foo.',
3008+
"Exception might not have property \$foo.\n• Exception might not have property \$bar.",
30093009
],
30103010
[
30113011
'Parameter #1 $o of method ObjectShapesAcceptance\Foo::doBar() expects object{foo: int, bar: string}, object{foo: string, bar: int} given.',
@@ -3073,7 +3073,7 @@ public function testObjectShapes(): void
30733073
[
30743074
'Parameter #1 $o of method ObjectShapesAcceptance\OptionalProperty::doBaz() expects object{foo: int}, object{foo?: string} given.',
30753075
158,
3076-
'Property ($foo) type int does not accept type string.',
3076+
'object{foo?: string} might not have property $foo.',
30773077
],
30783078
[
30793079
'Parameter #1 $o of method ObjectShapesAcceptance\TestAcceptance::doFoo() expects object{foo: int}, Traversable given.',

0 commit comments

Comments
 (0)