Skip to content

Commit 58be765

Browse files
Fix
1 parent 2b8dae7 commit 58be765

File tree

3 files changed

+48
-19
lines changed

3 files changed

+48
-19
lines changed

src/Rules/PhpDoc/VarTagTypeRuleHelper.php

+45-19
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
use PHPStan\Analyser\NameScope;
88
use PHPStan\Analyser\Scope;
99
use PHPStan\Node\Expr\GetOffsetValueTypeExpr;
10+
use PHPStan\PhpDoc\NameScopeAlreadyBeingCreatedException;
1011
use PHPStan\PhpDoc\Tag\VarTag;
1112
use PHPStan\PhpDoc\TypeNodeResolver;
1213
use PHPStan\Rules\IdentifierRuleError;
1314
use PHPStan\Rules\RuleErrorBuilder;
1415
use PHPStan\Type\ArrayType;
16+
use PHPStan\Type\FileTypeMapper;
1517
use PHPStan\Type\Generic\GenericObjectType;
1618
use PHPStan\Type\MixedType;
1719
use PHPStan\Type\ObjectType;
@@ -28,6 +30,7 @@ final class VarTagTypeRuleHelper
2830

2931
public function __construct(
3032
private TypeNodeResolver $typeNodeResolver,
33+
private FileTypeMapper $fileTypeMapper,
3134
private bool $checkTypeAgainstPhpDocType,
3235
private bool $strictWideningCheck,
3336
)
@@ -82,7 +85,7 @@ public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType):
8285
$errors = [];
8386
$exprNativeType = $scope->getNativeType($expr);
8487
$containsPhpStanType = $this->containsPhpStanType($varTagType);
85-
if ($this->shouldVarTagTypeBeReported($expr, $exprNativeType, $varTagType)) {
88+
if ($this->shouldVarTagTypeBeReported($scope, $expr, $exprNativeType, $varTagType)) {
8689
$verbosity = VerbosityLevel::getRecommendedLevelByType($exprNativeType, $varTagType);
8790
$errors[] = RuleErrorBuilder::message(sprintf(
8891
'PHPDoc tag @var with type %s is not subtype of native type %s.',
@@ -92,7 +95,7 @@ public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType):
9295
} else {
9396
$exprType = $scope->getType($expr);
9497
if (
95-
$this->shouldVarTagTypeBeReported($expr, $exprType, $varTagType)
98+
$this->shouldVarTagTypeBeReported($scope, $expr, $exprType, $varTagType)
9699
&& ($this->checkTypeAgainstPhpDocType || $containsPhpStanType)
97100
) {
98101
$verbosity = VerbosityLevel::getRecommendedLevelByType($exprType, $varTagType);
@@ -133,22 +136,22 @@ private function containsPhpStanType(Type $type): bool
133136
return false;
134137
}
135138

136-
private function shouldVarTagTypeBeReported(Node\Expr $expr, Type $type, Type $varTagType): bool
139+
private function shouldVarTagTypeBeReported(Scope $scope, Node\Expr $expr, Type $type, Type $varTagType): bool
137140
{
138141
if ($expr instanceof Expr\Array_) {
139142
if ($expr->items === []) {
140143
$type = new ArrayType(new MixedType(), new MixedType());
141144
}
142145

143-
return !$this->isAtLeastMaybeSuperTypeOfVarType($type, $varTagType);
146+
return !$this->isAtLeastMaybeSuperTypeOfVarType($scope, $type, $varTagType);
144147
}
145148

146149
if ($expr instanceof Expr\ConstFetch) {
147-
return !$this->isAtLeastMaybeSuperTypeOfVarType($type, $varTagType);
150+
return !$this->isAtLeastMaybeSuperTypeOfVarType($scope, $type, $varTagType);
148151
}
149152

150153
if ($expr instanceof Node\Scalar) {
151-
return !$this->isAtLeastMaybeSuperTypeOfVarType($type, $varTagType);
154+
return !$this->isAtLeastMaybeSuperTypeOfVarType($scope, $type, $varTagType);
152155
}
153156

154157
if ($expr instanceof Expr\New_) {
@@ -157,64 +160,87 @@ private function shouldVarTagTypeBeReported(Node\Expr $expr, Type $type, Type $v
157160
}
158161
}
159162

160-
return $this->checkType($type, $varTagType);
163+
return $this->checkType($scope, $type, $varTagType);
161164
}
162165

163-
private function checkType(Type $type, Type $varTagType, int $depth = 0): bool
166+
private function checkType(Scope $scope, Type $type, Type $varTagType, int $depth = 0): bool
164167
{
165168
if ($this->strictWideningCheck) {
166-
return !$this->isSuperTypeOfVarType($type, $varTagType);
169+
return !$this->isSuperTypeOfVarType($scope, $type, $varTagType);
167170
}
168171

169172
if ($type->isConstantArray()->yes()) {
170173
if ($type->isIterableAtLeastOnce()->no()) {
171174
$type = new ArrayType(new MixedType(), new MixedType());
172-
return !$this->isAtLeastMaybeSuperTypeOfVarType($type, $varTagType);
175+
return !$this->isAtLeastMaybeSuperTypeOfVarType($scope, $type, $varTagType);
173176
}
174177
}
175178

176179
if ($type->isIterable()->yes() && $varTagType->isIterable()->yes()) {
177-
if (!$this->isAtLeastMaybeSuperTypeOfVarType($type, $varTagType)) {
180+
if (!$this->isAtLeastMaybeSuperTypeOfVarType($scope, $type, $varTagType)) {
178181
return true;
179182
}
180183

181184
$innerType = $type->getIterableValueType();
182185
$innerVarTagType = $varTagType->getIterableValueType();
183186

184187
if ($type->equals($innerType) || $varTagType->equals($innerVarTagType)) {
185-
return !$this->isSuperTypeOfVarType($innerType, $innerVarTagType);
188+
return !$this->isSuperTypeOfVarType($scope, $innerType, $innerVarTagType);
186189
}
187190

188-
return $this->checkType($innerType, $innerVarTagType, $depth + 1);
191+
return $this->checkType($scope, $innerType, $innerVarTagType, $depth + 1);
189192
}
190193

191194
if ($depth === 0 && $type->isConstantValue()->yes()) {
192-
return !$this->isAtLeastMaybeSuperTypeOfVarType($type, $varTagType);
195+
return !$this->isAtLeastMaybeSuperTypeOfVarType($scope, $type, $varTagType);
193196
}
194197

195-
return !$this->isSuperTypeOfVarType($type, $varTagType);
198+
return !$this->isSuperTypeOfVarType($scope, $type, $varTagType);
196199
}
197200

198-
private function isSuperTypeOfVarType(Type $type, Type $varTagType): bool
201+
private function isSuperTypeOfVarType(Scope $scope, Type $type, Type $varTagType): bool
199202
{
200203
if ($type->isSuperTypeOf($varTagType)->yes()) {
201204
return true;
202205
}
203206

204-
$type = $this->typeNodeResolver->resolve($type->toPhpDocNode(), new NameScope(null, []));
207+
try {
208+
$type = $this->typeNodeResolver->resolve($type->toPhpDocNode(), $this->createNameScope($scope));
209+
} catch (NameScopeAlreadyBeingCreatedException) {
210+
return false;
211+
}
205212

206213
return $type->isSuperTypeOf($varTagType)->yes();
207214
}
208215

209-
private function isAtLeastMaybeSuperTypeOfVarType(Type $type, Type $varTagType): bool
216+
private function isAtLeastMaybeSuperTypeOfVarType(Scope $scope, Type $type, Type $varTagType): bool
210217
{
211218
if (!$type->isSuperTypeOf($varTagType)->no()) {
212219
return true;
213220
}
214221

215-
$type = $this->typeNodeResolver->resolve($type->toPhpDocNode(), new NameScope(null, []));
222+
try {
223+
$type = $this->typeNodeResolver->resolve($type->toPhpDocNode(), $this->createNameScope($scope));
224+
} catch (NameScopeAlreadyBeingCreatedException) {
225+
return false;
226+
}
216227

217228
return !$type->isSuperTypeOf($varTagType)->no();
218229
}
219230

231+
/**
232+
* @throws NameScopeAlreadyBeingCreatedException
233+
*/
234+
private function createNameScope(Scope $scope): NameScope
235+
{
236+
$function = $scope->getFunction();
237+
238+
return $this->fileTypeMapper->getNameScope(
239+
$scope->getFile(),
240+
$scope->isInClass() ? $scope->getClassReflection()->getName() : null,
241+
$scope->isInTrait() ? $scope->getTraitReflection()->getName() : null,
242+
$function !== null ? $function->getName() : null,
243+
);
244+
}
245+
220246
}

tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PHPStan\PhpDoc\TypeNodeResolver;
66
use PHPStan\Rules\Rule;
77
use PHPStan\Testing\RuleTestCase;
8+
use PHPStan\Type\FileTypeMapper;
89

910
/**
1011
* @extends RuleTestCase<VarTagChangedExpressionTypeRule>
@@ -16,6 +17,7 @@ protected function getRule(): Rule
1617
{
1718
return new VarTagChangedExpressionTypeRule(new VarTagTypeRuleHelper(
1819
self::getContainer()->getByType(TypeNodeResolver::class),
20+
self::getContainer()->getByType(FileTypeMapper::class),
1921
true,
2022
true,
2123
));

tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ protected function getRule(): Rule
2626
self::getContainer()->getByType(FileTypeMapper::class),
2727
new VarTagTypeRuleHelper(
2828
self::getContainer()->getByType(TypeNodeResolver::class),
29+
self::getContainer()->getByType(FileTypeMapper::class),
2930
$this->checkTypeAgainstPhpDocType,
3031
$this->strictWideningCheck,
3132
),

0 commit comments

Comments
 (0)