Skip to content

Commit 299df51

Browse files
committed
MethodAssertRule - do not report implicitly inherited assert tags
1 parent d2c30d1 commit 299df51

File tree

6 files changed

+62
-8
lines changed

6 files changed

+62
-8
lines changed

src/PhpDoc/PhpDocNodeResolver.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -515,19 +515,19 @@ private function resolveAssertTagsFor(PhpDocNode $phpDocNode, NameScope $nameSco
515515
foreach ($phpDocNode->getAssertTagValues($tagName) as $assertTagValue) {
516516
$type = $this->typeNodeResolver->resolve($assertTagValue->type, $nameScope);
517517
$parameter = new AssertTagParameter($assertTagValue->parameter, null, null);
518-
$resolved[] = new AssertTag($if, $type, $parameter, $assertTagValue->isNegated, $assertTagValue->isEquality ?? false);
518+
$resolved[] = new AssertTag($if, $type, $parameter, $assertTagValue->isNegated, $assertTagValue->isEquality ?? false, true);
519519
}
520520

521521
foreach ($phpDocNode->getAssertPropertyTagValues($tagName) as $assertTagValue) {
522522
$type = $this->typeNodeResolver->resolve($assertTagValue->type, $nameScope);
523523
$parameter = new AssertTagParameter($assertTagValue->parameter, $assertTagValue->property, null);
524-
$resolved[] = new AssertTag($if, $type, $parameter, $assertTagValue->isNegated, $assertTagValue->isEquality ?? false);
524+
$resolved[] = new AssertTag($if, $type, $parameter, $assertTagValue->isNegated, $assertTagValue->isEquality ?? false, true);
525525
}
526526

527527
foreach ($phpDocNode->getAssertMethodTagValues($tagName) as $assertTagValue) {
528528
$type = $this->typeNodeResolver->resolve($assertTagValue->type, $nameScope);
529529
$parameter = new AssertTagParameter($assertTagValue->parameter, null, $assertTagValue->method);
530-
$resolved[] = new AssertTag($if, $type, $parameter, $assertTagValue->isNegated, $assertTagValue->isEquality ?? false);
530+
$resolved[] = new AssertTag($if, $type, $parameter, $assertTagValue->isNegated, $assertTagValue->isEquality ?? false, true);
531531
}
532532

533533
return $resolved;

src/PhpDoc/ResolvedPhpDocBlock.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,7 @@ private static function mergeAssertTags(array $assertTags, array $parents, array
934934
static fn (AssertTag $assertTag) => self::resolveTemplateTypeInTag(
935935
$assertTag->withParameter(
936936
$phpDocBlock->transformAssertTagParameterWithParameterNameMapping($assertTag->getParameter()),
937-
),
937+
)->toImplicit(),
938938
$phpDocBlock,
939939
TemplateTypeVariance::createCovariant(),
940940
),

src/PhpDoc/Tag/AssertTag.php

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ final class AssertTag implements TypedTag
1818
/**
1919
* @param self::NULL|self::IF_TRUE|self::IF_FALSE $if
2020
*/
21-
public function __construct(private string $if, private Type $type, private AssertTagParameter $parameter, private bool $negated, private bool $equality)
21+
public function __construct(private string $if, private Type $type, private AssertTagParameter $parameter, private bool $negated, private bool $equality, private bool $isExplicit)
2222
{
2323
}
2424

@@ -60,14 +60,14 @@ public function isEquality(): bool
6060
*/
6161
public function withType(Type $type): TypedTag
6262
{
63-
$tag = new self($this->if, $type, $this->parameter, $this->negated, $this->equality);
63+
$tag = new self($this->if, $type, $this->parameter, $this->negated, $this->equality, $this->isExplicit);
6464
$tag->originalType = $this->getOriginalType();
6565
return $tag;
6666
}
6767

6868
public function withParameter(AssertTagParameter $parameter): self
6969
{
70-
$tag = new self($this->if, $this->type, $parameter, $this->negated, $this->equality);
70+
$tag = new self($this->if, $this->type, $parameter, $this->negated, $this->equality, $this->isExplicit);
7171
$tag->originalType = $this->getOriginalType();
7272
return $tag;
7373
}
@@ -78,9 +78,19 @@ public function negate(): self
7878
throw new ShouldNotHappenException();
7979
}
8080

81-
$tag = new self($this->if, $this->type, $this->parameter, !$this->negated, $this->equality);
81+
$tag = new self($this->if, $this->type, $this->parameter, !$this->negated, $this->equality, $this->isExplicit);
8282
$tag->originalType = $this->getOriginalType();
8383
return $tag;
8484
}
8585

86+
public function isExplicit(): bool
87+
{
88+
return $this->isExplicit;
89+
}
90+
91+
public function toImplicit(): self
92+
{
93+
return new self($this->if, $this->type, $this->parameter, $this->negated, $this->equality, false);
94+
}
95+
8696
}

src/Rules/PhpDoc/AssertRuleHelper.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ public function check(ExtendedMethodReflection|FunctionReflection $reflection, P
4949
continue;
5050
}
5151

52+
if (!$assert->isExplicit()) {
53+
continue;
54+
}
55+
5256
$assertedExpr = $assert->getParameter()->getExpr(new TypeExpr($parametersByName[$parameterName]));
5357
$assertedExprType = $this->initializerExprTypeResolver->getType($assertedExpr, $context);
5458
if ($assertedExprType instanceof ErrorType) {

tests/PHPStan/Rules/PhpDoc/MethodAssertRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,9 @@ public function testRule(): void
5757
]);
5858
}
5959

60+
public function testBug10573(): void
61+
{
62+
$this->analyse([__DIR__ . '/data/bug-10573.php'], []);
63+
}
64+
6065
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Bug10573;
4+
5+
/**
6+
* @template TAttribute of string
7+
* @template TSubject of mixed
8+
*/
9+
abstract class Voter
10+
{
11+
/**
12+
* Determines if the attribute and subject are supported by this voter.
13+
*
14+
* @param string $attribute
15+
* @param mixed $subject
16+
*
17+
* @phpstan-assert-if-true TSubject $subject
18+
* @phpstan-assert-if-true TAttribute $attribute
19+
*
20+
* @return bool
21+
*/
22+
abstract protected function supports(string $attribute, $subject);
23+
}
24+
25+
class Post {}
26+
27+
/**
28+
* @extends Voter<string, Post>
29+
*/
30+
class PostVoter extends Voter {
31+
function supports($attribute, $subject): bool
32+
{
33+
return $attribute === 'POST_READ' && $subject instanceof Post;
34+
}
35+
}

0 commit comments

Comments
 (0)