Skip to content

DeprecationExtensions: allow custom deprecation-marking logic #3932

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Apr 14, 2025
3 changes: 3 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2157,6 +2157,9 @@ services:
-
class: PHPStan\Reflection\BetterReflection\SourceStubber\ReflectionSourceStubberFactory

-
class: PHPStan\Reflection\Deprecation\DeprecationProvider

php8Lexer:
class: PhpParser\Lexer\Emulative
factory: @PHPStan\Parser\LexerFactory::createEmulative()
Expand Down
3 changes: 3 additions & 0 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
use PHPStan\Reflection\Callables\SimpleImpurePoint;
use PHPStan\Reflection\Callables\SimpleThrowPoint;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\Deprecation\DeprecationProvider;
use PHPStan\Reflection\ExtendedMethodReflection;
use PHPStan\Reflection\ExtendedParameterReflection;
use PHPStan\Reflection\ExtendedParametersAcceptor;
Expand Down Expand Up @@ -255,6 +256,7 @@ public function __construct(
private readonly StubPhpDocProvider $stubPhpDocProvider,
private readonly PhpVersion $phpVersion,
private readonly SignatureMapProvider $signatureMapProvider,
private readonly DeprecationProvider $deprecationProvider,
private readonly AttributeReflectionFactory $attributeReflectionFactory,
private readonly PhpDocInheritanceResolver $phpDocInheritanceResolver,
private readonly FileHelper $fileHelper,
Expand Down Expand Up @@ -2140,6 +2142,7 @@ private function createAstClassReflection(Node\Stmt\ClassLike $stmt, string $cla
$this->phpDocInheritanceResolver,
$this->phpVersion,
$this->signatureMapProvider,
$this->deprecationProvider,
$this->attributeReflectionFactory,
$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(),
$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(),
Expand Down
35 changes: 23 additions & 12 deletions src/Reflection/BetterReflection/BetterReflectionProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\Constant\RuntimeConstantReflection;
use PHPStan\Reflection\ConstantReflection;
use PHPStan\Reflection\Deprecation\DeprecationProvider;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\FunctionReflectionFactory;
use PHPStan\Reflection\InitializerExprContext;
Expand Down Expand Up @@ -85,6 +86,7 @@ public function __construct(
private Reflector $reflector,
private FileTypeMapper $fileTypeMapper,
private PhpDocInheritanceResolver $phpDocInheritanceResolver,
private DeprecationProvider $deprecationProvider,
private PhpVersion $phpVersion,
private NativeFunctionReflectionProvider $nativeFunctionReflectionProvider,
private StubPhpDocProvider $stubPhpDocProvider,
Expand Down Expand Up @@ -148,6 +150,7 @@ public function getClass(string $className): ClassReflection
$this->phpDocInheritanceResolver,
$this->phpVersion,
$this->signatureMapProvider,
$this->deprecationProvider,
$this->attributeReflectionFactory,
$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(),
$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(),
Expand Down Expand Up @@ -243,6 +246,7 @@ public function getAnonymousClassReflection(Node\Stmt\Class_ $classNode, Scope $
$this->phpDocInheritanceResolver,
$this->phpVersion,
$this->signatureMapProvider,
$this->deprecationProvider,
$this->attributeReflectionFactory,
$this->classReflectionExtensionRegistryProvider->getRegistry()->getPropertiesClassReflectionExtensions(),
$this->classReflectionExtensionRegistryProvider->getRegistry()->getMethodsClassReflectionExtensions(),
Expand Down Expand Up @@ -305,8 +309,11 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection
$phpDocParameterTypes = [];
$phpDocReturnTag = null;
$phpDocThrowsTag = null;
$deprecatedTag = null;
$isDeprecated = false;

$deprecation = $this->deprecationProvider->getFunctionDeprecation($reflectionFunction);
$deprecationDescription = $deprecation === null ? null : $deprecation->getDescription();
$isDeprecated = $deprecation !== null;

$isInternal = false;
$isPure = null;
$asserts = Assertions::createEmpty();
Expand All @@ -327,8 +334,10 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection
$phpDocParameterTypes = array_map(static fn ($tag) => $tag->getType(), $resolvedPhpDoc->getParamTags());
$phpDocReturnTag = $resolvedPhpDoc->getReturnTag();
$phpDocThrowsTag = $resolvedPhpDoc->getThrowsTag();
$deprecatedTag = $resolvedPhpDoc->getDeprecatedTag();
$isDeprecated = $resolvedPhpDoc->isDeprecated();
if (!$isDeprecated) {
$deprecationDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : $deprecationDescription;
$isDeprecated = $resolvedPhpDoc->isDeprecated();
}
$isInternal = $resolvedPhpDoc->isInternal();
$isPure = $resolvedPhpDoc->isPure();
$asserts = Assertions::createFromResolvedPhpDocBlock($resolvedPhpDoc);
Expand All @@ -347,7 +356,7 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection
$phpDocParameterTypes,
$phpDocReturnTag !== null ? $phpDocReturnTag->getType() : null,
$phpDocThrowsTag !== null ? $phpDocThrowsTag->getType() : null,
$deprecatedTag !== null ? $deprecatedTag->getMessage() : null,
$deprecationDescription,
$isDeprecated,
$isInternal,
$reflectionFunction->getFileName() !== false ? $reflectionFunction->getFileName() : null,
Expand Down Expand Up @@ -407,13 +416,15 @@ public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAn
$constantValueType = $this->initializerExprTypeResolver->getType($constantReflection->getValueExpression(), InitializerExprContext::fromGlobalConstant($constantReflection));
$docComment = $constantReflection->getDocComment();

$isDeprecated = TrinaryLogic::createNo();
$deprecatedDescription = null;
if ($docComment !== null) {
$deprecation = $this->deprecationProvider->getConstantDeprecation($constantReflection);
$isDeprecated = $deprecation !== null;
$deprecatedDescription = $deprecation === null ? null : $deprecation->getDescription();

if ($isDeprecated === false && $docComment !== null) {
$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc($fileName, null, null, null, $docComment);
$isDeprecated = TrinaryLogic::createFromBoolean($resolvedPhpDoc->isDeprecated());
$isDeprecated = $resolvedPhpDoc->isDeprecated();

if ($resolvedPhpDoc->isDeprecated() && $resolvedPhpDoc->getDeprecatedTag() !== null) {
if ($isDeprecated && $resolvedPhpDoc->getDeprecatedTag() !== null) {
$deprecatedMessage = $resolvedPhpDoc->getDeprecatedTag()->getMessage();

$matches = Strings::match($deprecatedMessage ?? '', '#^(\d+)\.(\d+)(?:\.(\d+))?$#');
Expand All @@ -423,7 +434,7 @@ public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAn
$patch = $matches[3] ?? 0;
$versionId = sprintf('%d%02d%02d', $major, $minor, $patch);

$isDeprecated = TrinaryLogic::createFromBoolean($this->phpVersion->getVersionId() >= $versionId);
$isDeprecated = $this->phpVersion->getVersionId() >= $versionId;
} else {
// filter raw version number messages like in
// https://github.com/JetBrains/phpstorm-stubs/blob/9608c953230b08f07b703ecfe459cc58d5421437/filter/filter.php#L478
Expand All @@ -436,7 +447,7 @@ public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAn
$constantName,
$constantValueType,
$fileName,
$isDeprecated,
TrinaryLogic::createFromBoolean($isDeprecated),
$deprecatedDescription,
);
}
Expand Down
56 changes: 45 additions & 11 deletions src/Reflection/ClassReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use PHPStan\PhpDoc\Tag\TemplateTag;
use PHPStan\PhpDoc\Tag\TypeAliasImportTag;
use PHPStan\PhpDoc\Tag\TypeAliasTag;
use PHPStan\Reflection\Deprecation\DeprecationProvider;
use PHPStan\Reflection\Php\PhpClassReflectionExtension;
use PHPStan\Reflection\Php\PhpPropertyReflection;
use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension;
Expand Down Expand Up @@ -161,6 +162,7 @@ public function __construct(
private PhpDocInheritanceResolver $phpDocInheritanceResolver,
private PhpVersion $phpVersion,
private SignatureMapProvider $signatureMapProvider,
private DeprecationProvider $deprecationProvider,
private AttributeReflectionFactory $attributeReflectionFactory,
private array $propertiesClassReflectionExtensions,
private array $methodsClassReflectionExtensions,
Expand Down Expand Up @@ -793,7 +795,8 @@ public function getEnumCases(): array
$valueType = $this->initializerExprTypeResolver->getType($case->getValueExpression(), $initializerExprContext);
}
$caseName = $case->getName();
$cases[$caseName] = new EnumCaseReflection($this, $case, $valueType, $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName())));
$attributes = $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName()));
$cases[$caseName] = new EnumCaseReflection($this, $case, $valueType, $attributes, $this->deprecationProvider);
}

return $this->enumCases = $cases;
Expand All @@ -819,7 +822,9 @@ public function getEnumCase(string $name): EnumCaseReflection
$valueType = $this->initializerExprTypeResolver->getType($case->getValueExpression(), InitializerExprContext::fromClassReflection($this));
}

return new EnumCaseReflection($this, $case, $valueType, $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName())));
$attributes = $this->attributeReflectionFactory->fromNativeReflection($case->getAttributes(), InitializerExprContext::fromClass($this->getName(), $this->getFileName()));

return new EnumCaseReflection($this, $case, $valueType, $attributes, $this->deprecationProvider);
}

public function isClass(): bool
Expand Down Expand Up @@ -1079,6 +1084,10 @@ public function getConstant(string $name): ClassConstantReflection
throw new MissingConstantFromReflectionException($this->getName(), $name);
}

$deprecation = $this->deprecationProvider->getClassConstantDeprecation($reflectionConstant);
$deprecatedDescription = $deprecation === null ? null : $deprecation->getDescription();
$isDeprecated = $deprecation !== null;

$declaringClass = $this->reflectionProvider->getClass($reflectionConstant->getDeclaringClass()->getName());
$fileName = $declaringClass->getFileName();
$phpDocType = null;
Expand All @@ -1099,8 +1108,10 @@ public function getConstant(string $name): ClassConstantReflection
);
}

$deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null;
$isDeprecated = $resolvedPhpDoc->isDeprecated();
if (!$isDeprecated) {
$deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null;
$isDeprecated = $resolvedPhpDoc->isDeprecated();
}
$isInternal = $resolvedPhpDoc->isInternal();
$isFinal = $resolvedPhpDoc->isFinal();
$varTags = $resolvedPhpDoc->getVarTags();
Expand Down Expand Up @@ -1210,11 +1221,8 @@ public function getTypeAliases(): array

public function getDeprecatedDescription(): ?string
{
if ($this->deprecatedDescription === null && $this->isDeprecated()) {
$resolvedPhpDoc = $this->getResolvedPhpDoc();
if ($resolvedPhpDoc !== null && $resolvedPhpDoc->getDeprecatedTag() !== null) {
$this->deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag()->getMessage();
}
if ($this->isDeprecated === null) {
$this->resolveDeprecation();
}

return $this->deprecatedDescription;
Expand All @@ -1223,13 +1231,36 @@ public function getDeprecatedDescription(): ?string
public function isDeprecated(): bool
{
if ($this->isDeprecated === null) {
$resolvedPhpDoc = $this->getResolvedPhpDoc();
$this->isDeprecated = $resolvedPhpDoc !== null && $resolvedPhpDoc->isDeprecated();
$this->resolveDeprecation();
}

return $this->isDeprecated;
}

/**
* @phpstan-assert bool $this->isDeprecated
*/
private function resolveDeprecation(): void
{
$deprecation = $this->deprecationProvider->isClassDeprecated($this->reflection);
if ($deprecation !== null) {
$this->isDeprecated = true;
$this->deprecatedDescription = $deprecation->getDescription();
return;
}

$resolvedPhpDoc = $this->getResolvedPhpDoc();

if ($resolvedPhpDoc !== null && $resolvedPhpDoc->isDeprecated()) {
$this->isDeprecated = true;
$this->deprecatedDescription = $resolvedPhpDoc->getDeprecatedTag() !== null ? $resolvedPhpDoc->getDeprecatedTag()->getMessage() : null;
return;
}

$this->isDeprecated = false;
$this->deprecatedDescription = null;
}

public function isBuiltin(): bool
{
return $this->reflection->isInternal();
Expand Down Expand Up @@ -1559,6 +1590,7 @@ public function withTypes(array $types): self
$this->phpDocInheritanceResolver,
$this->phpVersion,
$this->signatureMapProvider,
$this->deprecationProvider,
$this->attributeReflectionFactory,
$this->propertiesClassReflectionExtensions,
$this->methodsClassReflectionExtensions,
Expand Down Expand Up @@ -1590,6 +1622,7 @@ public function withVariances(array $variances): self
$this->phpDocInheritanceResolver,
$this->phpVersion,
$this->signatureMapProvider,
$this->deprecationProvider,
$this->attributeReflectionFactory,
$this->propertiesClassReflectionExtensions,
$this->methodsClassReflectionExtensions,
Expand Down Expand Up @@ -1631,6 +1664,7 @@ public function asFinal(): self
$this->phpDocInheritanceResolver,
$this->phpVersion,
$this->signatureMapProvider,
$this->deprecationProvider,
$this->attributeReflectionFactory,
$this->propertiesClassReflectionExtensions,
$this->methodsClassReflectionExtensions,
Expand Down
27 changes: 27 additions & 0 deletions src/Reflection/Deprecation/ClassConstantDeprecationExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php declare(strict_types = 1);

namespace PHPStan\Reflection\Deprecation;

use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClassConstant;

/**
* This interface allows you to provide custom deprecation information
*
* To register it in the configuration file use the following tag:
*
* ```
* services:
* -
* class: App\PHPStan\MyProvider
* tags:
* - phpstan.classConstantDeprecationExtension
* ```
*
* @api
*/
interface ClassConstantDeprecationExtension
{

public function getClassConstantDeprecation(ReflectionClassConstant $reflection): ?Deprecation;

}
28 changes: 28 additions & 0 deletions src/Reflection/Deprecation/ClassDeprecationExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php declare(strict_types = 1);

namespace PHPStan\Reflection\Deprecation;

use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass;
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionEnum;

/**
* This interface allows you to provide custom deprecation information
*
* To register it in the configuration file use the following tag:
*
* ```
* services:
* -
* class: App\PHPStan\MyProvider
* tags:
* - phpstan.classDeprecationExtension
* ```
*
* @api
*/
interface ClassDeprecationExtension
{

public function getClassDeprecation(ReflectionClass|ReflectionEnum $reflection): ?Deprecation;

}
27 changes: 27 additions & 0 deletions src/Reflection/Deprecation/ConstantDeprecationExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php declare(strict_types = 1);

namespace PHPStan\Reflection\Deprecation;

use PHPStan\BetterReflection\Reflection\ReflectionConstant;

/**
* This interface allows you to provide custom deprecation information
*
* To register it in the configuration file use the following tag:
*
* ```
* services:
* -
* class: App\PHPStan\MyProvider
* tags:
* - phpstan.constantDeprecationExtension
* ```
*
* @api
*/
interface ConstantDeprecationExtension
{

public function getConstantDeprecation(ReflectionConstant $reflection): ?Deprecation;

}
35 changes: 35 additions & 0 deletions src/Reflection/Deprecation/Deprecation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php declare(strict_types = 1);

namespace PHPStan\Reflection\Deprecation;

/**
* @api
*/
final class Deprecation
{

private ?string $description = null;

private function __construct()
{
}

public static function create(): self
{
return new self();
}

public function getDescription(): ?string
{
return $this->description;
}

public static function createWithDescription(string $description): self
{
$clone = new self();
$clone->description = $description;

return $clone;
}

}
Loading
Loading