Skip to content

Commit

Permalink
Tip about template bound type when combined with a native type
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Oct 1, 2021
1 parent 646a5ff commit 5314a6b
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 10 deletions.
24 changes: 17 additions & 7 deletions src/Rules/PhpDoc/IncompatiblePhpDocTypeRule.php
Expand Up @@ -10,7 +10,7 @@
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\ArrayType;
use PHPStan\Type\FileTypeMapper;
use PHPStan\Type\Generic\TemplateTypeHelper;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\Type;
use PHPStan\Type\VerbosityLevel;

Expand Down Expand Up @@ -95,7 +95,7 @@ public function processNode(Node $node, Scope $scope): array
) {
$phpDocParamType = $phpDocParamType->getItemType();
}
$isParamSuperType = $nativeParamType->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocParamType));
$isParamSuperType = $nativeParamType->isSuperTypeOf($phpDocParamType);

$escapedParameterName = SprintfHelper::escapeFormatString($parameterName);

Expand Down Expand Up @@ -128,18 +128,23 @@ public function processNode(Node $node, Scope $scope): array
))->build();

} elseif ($isParamSuperType->maybe()) {
$errors[] = RuleErrorBuilder::message(sprintf(
$errorBuilder = RuleErrorBuilder::message(sprintf(
'PHPDoc tag @param for parameter $%s with type %s is not subtype of native type %s.',
$parameterName,
$phpDocParamType->describe(VerbosityLevel::typeOnly()),
$nativeParamType->describe(VerbosityLevel::typeOnly())
))->build();
));
if ($phpDocParamType instanceof TemplateType) {
$errorBuilder->tip(sprintf('Write @template %s of %s to fix this.', $phpDocParamType->getName(), $nativeParamType->describe(VerbosityLevel::typeOnly())));
}

$errors[] = $errorBuilder->build();
}
}
}

if ($resolvedPhpDoc->getReturnTag() !== null) {
$phpDocReturnType = TemplateTypeHelper::resolveToBounds($resolvedPhpDoc->getReturnTag()->getType());
$phpDocReturnType = $resolvedPhpDoc->getReturnTag()->getType();

if (
$this->unresolvableTypeHelper->containsUnresolvableType($phpDocReturnType)
Expand All @@ -163,11 +168,16 @@ public function processNode(Node $node, Scope $scope): array
))->build();

} elseif ($isReturnSuperType->maybe()) {
$errors[] = RuleErrorBuilder::message(sprintf(
$errorBuilder = RuleErrorBuilder::message(sprintf(
'PHPDoc tag @return with type %s is not subtype of native type %s.',
$phpDocReturnType->describe(VerbosityLevel::typeOnly()),
$nativeReturnType->describe(VerbosityLevel::typeOnly())
))->build();
));
if ($phpDocReturnType instanceof TemplateType) {
$errorBuilder->tip(sprintf('Write @template %s of %s to fix this.', $phpDocReturnType->getName(), $nativeReturnType->describe(VerbosityLevel::typeOnly())));
}

$errors[] = $errorBuilder->build();
}
}
}
Expand Down
11 changes: 9 additions & 2 deletions src/Rules/PhpDoc/IncompatiblePropertyPhpDocTypeRule.php
Expand Up @@ -9,6 +9,7 @@
use PHPStan\Rules\Generics\GenericObjectTypeCheck;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\VerbosityLevel;

/**
Expand Down Expand Up @@ -79,14 +80,20 @@ public function processNode(Node $node, Scope $scope): array
))->build();

} elseif ($isSuperType->maybe()) {
$messages[] = RuleErrorBuilder::message(sprintf(
$errorBuilder = RuleErrorBuilder::message(sprintf(
'%s for property %s::$%s with type %s is not subtype of native type %s.',
$description,
$propertyReflection->getDeclaringClass()->getDisplayName(),
$propertyName,
$phpDocType->describe(VerbosityLevel::typeOnly()),
$nativeType->describe(VerbosityLevel::typeOnly())
))->build();
));

if ($phpDocType instanceof TemplateType) {
$errorBuilder->tip(sprintf('Write @template %s of %s to fix this.', $phpDocType->getName(), $nativeType->describe(VerbosityLevel::typeOnly())));
}

$messages[] = $errorBuilder->build();
}

$className = SprintfHelper::escapeFormatString($propertyReflection->getDeclaringClass()->getDisplayName());
Expand Down
24 changes: 23 additions & 1 deletion tests/PHPStan/Rules/PhpDoc/IncompatiblePhpDocTypeRuleTest.php
Expand Up @@ -70,14 +70,17 @@ public function testRule(): void
[
'PHPDoc tag @param for parameter $a with type T is not subtype of native type int.',
154,
'Write @template T of int to fix this.',
],
[
'PHPDoc tag @param for parameter $b with type U of DateTimeInterface is not subtype of native type DateTime.',
154,
'Write @template U of DateTime to fix this.',
],
[
'PHPDoc tag @return with type DateTimeInterface is not subtype of native type DateTime.',
'PHPDoc tag @return with type U of DateTimeInterface is not subtype of native type DateTime.',
154,
'Write @template U of DateTime to fix this.',
],
[
'PHPDoc tag @param for parameter $foo contains generic type InvalidPhpDocDefinitions\Foo<stdClass> but class InvalidPhpDocDefinitions\Foo is not generic.',
Expand Down Expand Up @@ -143,6 +146,11 @@ public function testRule(): void
'PHPDoc tag @return contains generic type InvalidPhpDocDefinitions\Foo<int, Exception> but class InvalidPhpDocDefinitions\Foo is not generic.',
274,
],
[
'PHPDoc tag @param for parameter $i with type TFoo is not subtype of native type int.',
283,
'Write @template TFoo of int to fix this.',
],
]);
}

Expand All @@ -165,4 +173,18 @@ public function testBug3753(): void
]);
}

public function testTemplateTypeNativeTypeObject(): void
{
if (PHP_VERSION_ID < 70400 && !self::$useStaticReflectionProvider) {
$this->markTestSkipped('Test requires PHP 7.4.');
}
$this->analyse([__DIR__ . '/data/template-type-native-type-object.php'], [
[
'PHPDoc tag @return with type T is not subtype of native type object.',
23,
'Write @template T of object to fix this.',
],
]);
}

}
Expand Up @@ -76,6 +76,11 @@ public function testNativeTypes(): void
'PHPDoc tag @var for property IncompatiblePhpDocPropertyNativeType\Foo::$stringOrInt with type int|string is not subtype of native type string.',
21,
],
[
'PHPDoc tag @var for property IncompatiblePhpDocPropertyNativeType\Lorem::$string with type T is not subtype of native type string.',
45,
'Write @template T of string to fix this.',
],
]);
}

Expand Down
Expand Up @@ -36,3 +36,12 @@ class Baz
private string $stringProp;

}

/** @template T */
class Lorem
{

/** @var T */
private string $string;

}
9 changes: 9 additions & 0 deletions tests/PHPStan/Rules/PhpDoc/data/incompatible-types.php
Expand Up @@ -275,3 +275,12 @@ function genericNestedNonTemplateArgs()
{

}

/**
* @template TFoo
* @param TFoo $i
*/
function genericWrongBound(int $i)
{

}
@@ -0,0 +1,31 @@
<?php // lint >= 7.4

namespace TemplateTypeNativeTypeObject;

class HelloWorld
{
/**
* @var array<string, mixed>
*/
private array $instances;

public function __construct()
{
$this->instances = [];
}

/**
* @phpstan-template T
* @phpstan-param class-string<T> $className
*
* @phpstan-return T
*/
public function getInstanceByName(string $className, string $name): object
{
$instance = $this->instances["[{$className}]{$name}"];

\assert($instance instanceof $className);

return $instance;
}
}

0 comments on commit 5314a6b

Please sign in to comment.