From 3ed21df89854bfaabd091b7869d5561cc2290e9f Mon Sep 17 00:00:00 2001 From: El-Virus Date: Tue, 25 Feb 2025 17:35:31 +0100 Subject: [PATCH 1/2] Add support for self-referencing constants --- .../Traits/ConflictingTraitConstantsRule.php | 70 ++++++++++++++----- .../ConflictingTraitConstantsRuleTest.php | 12 ++++ .../conflicting-trait-constants-types.php | 61 ++++++++++++++++ 3 files changed, 124 insertions(+), 19 deletions(-) diff --git a/src/Rules/Traits/ConflictingTraitConstantsRule.php b/src/Rules/Traits/ConflictingTraitConstantsRule.php index 4e38e44bcb..31b2f31f52 100644 --- a/src/Rules/Traits/ConflictingTraitConstantsRule.php +++ b/src/Rules/Traits/ConflictingTraitConstantsRule.php @@ -224,26 +224,58 @@ private function processSingleConstant(ClassReflection $classReflection, Reflect } $classConstantValueType = $this->initializerExprTypeResolver->getType($valueExpr, InitializerExprContext::fromClassReflection($classReflection)); - $traitConstantValueType = $this->initializerExprTypeResolver->getType( - $traitConstant->getValueExpression(), - InitializerExprContext::fromClass( - $traitDeclaringClass->getName(), - $traitDeclaringClass->getFileName() !== false ? $traitDeclaringClass->getFileName() : null, - ), + $traitValueExpr = $traitConstant->getValueExpression(); + $isTraitSelfReferencing = ( + $traitValueExpr instanceof Node\Expr\ClassConstFetch + && $traitValueExpr->class->name === 'self' + && $traitValueExpr->name->name === $traitConstant->getName() ); - if (!$classConstantValueType->equals($traitConstantValueType)) { - $errors[] = RuleErrorBuilder::message(sprintf( - 'Constant %s::%s with value %s overriding constant %s::%s with different value %s should have the same value.', - $classReflection->getDisplayName(), - $traitConstant->getName(), - $classConstantValueType->describe(VerbosityLevel::value()), - $traitConstant->getDeclaringClass()->getName(), - $traitConstant->getName(), - $traitConstantValueType->describe(VerbosityLevel::value()), - )) - ->nonIgnorable() - ->identifier('classConstant.value') - ->build(); + if ($isTraitSelfReferencing) { + $isValueSelfReference = ( + $valueExpr instanceof Node\Expr\ClassConstFetch + && $valueExpr->class->name === 'self' + && $valueExpr->name->name === $traitConstant->getName() + ); + if ( + !$isValueSelfReference + && isset($classConstantValueType) + && !$classConstantValueType instanceof $constantNativeTypeType + && $classConstantValueType->isSuperTypeOf($constantNativeTypeType)->no() + ) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Constant %s::%s with value %s cannot override native type of constant %s::%s.', + $classReflection->getDisplayName(), + $traitConstant->getName(), + $classConstantValueType->describe(VerbosityLevel::value()), + $traitConstant->getDeclaringClass()->getName(), + $traitConstant->getName(), + )) + ->nonIgnorable() + ->identifier('classConstant.value') + ->build(); + } + } else { + $traitConstantValueType = $this->initializerExprTypeResolver->getType( + $traitValueExpr, + InitializerExprContext::fromClass( + $traitDeclaringClass->getName(), + $traitDeclaringClass->getFileName() !== false ? $traitDeclaringClass->getFileName() : null, + ), + ); + if (!$classConstantValueType->equals($traitConstantValueType)) { + $errors[] = RuleErrorBuilder::message(sprintf( + 'Constant %s::%s with value %s overriding constant %s::%s with different value %s should have the same value.', + $classReflection->getDisplayName(), + $traitConstant->getName(), + $classConstantValueType->describe(VerbosityLevel::value()), + $traitConstant->getDeclaringClass()->getName(), + $traitConstant->getName(), + $traitConstantValueType->describe(VerbosityLevel::value()), + )) + ->nonIgnorable() + ->identifier('classConstant.value') + ->build(); + } } return $errors; diff --git a/tests/PHPStan/Rules/Traits/ConflictingTraitConstantsRuleTest.php b/tests/PHPStan/Rules/Traits/ConflictingTraitConstantsRuleTest.php index c3b06e73d0..92af927994 100644 --- a/tests/PHPStan/Rules/Traits/ConflictingTraitConstantsRuleTest.php +++ b/tests/PHPStan/Rules/Traits/ConflictingTraitConstantsRuleTest.php @@ -78,6 +78,18 @@ public function testNativeTypes(): void 'Constant ConflictingTraitConstantsTypes\Lorem::FOO_CONST overriding constant ConflictingTraitConstantsTypes\Foo::FOO_CONST (int|string) should also have native type int|string.', 39, ], + [ + 'Constant ConflictingTraitConstantsTypes\SelfRefWrongType::SR_CONST with value array{1} cannot override native type of constant ConflictingTraitConstantsTypes\SelfRef::SR_CONST.', + 64, + ], + [ + 'Constant ConflictingTraitConstantsTypes\SelfRefExtWrong::SR_CONST (string) overriding constant ConflictingTraitConstantsTypes\SelfRef::SR_CONST (int) should have the same native type int.', + 82, + ], + [ + 'Constant ConflictingTraitConstantsTypes\SelfRefExtOverrideExt::SR_CONST with value mixed overriding constant ConflictingTraitConstantsTypes\SelfRefExtOverride::SR_CONST with different value 1 should have the same value.', + 100, + ], ]); } diff --git a/tests/PHPStan/Rules/Traits/data/conflicting-trait-constants-types.php b/tests/PHPStan/Rules/Traits/data/conflicting-trait-constants-types.php index eaa130ffeb..c27b8e5caf 100644 --- a/tests/PHPStan/Rules/Traits/data/conflicting-trait-constants-types.php +++ b/tests/PHPStan/Rules/Traits/data/conflicting-trait-constants-types.php @@ -39,3 +39,64 @@ class Lorem public const FOO_CONST = 1; } + +trait SelfRef +{ + + public const int SR_CONST = self::SR_CONST; + +} + +class SelfRefOverride +{ + + use SelfRef; + + public const int SR_CONST = 1; + +} + +class SelfRefWrongType +{ + + use SelfRef; + + public const int SR_CONST = [1]; + +} + +class SelfRefExt +{ + + use SelfRef; + + public const int SR_CONST = self::SR_CONST; + +} + +class SelfRefExtWrong +{ + + use SelfRef; + + public const string SR_CONST = self::SR_CONST; + +} + +class SelfRefExtOverride +{ + + use SelfRefExt; + + public const int SR_CONST = 1; + +} + +class SelfRefExtOverrideExt +{ + + use SelfRefExtOverride; + + public const int SR_CONST = self::SR_CONST; + +} \ No newline at end of file From 1673dd5a3c8a8ce6c77627ff591073cddfd3ea01 Mon Sep 17 00:00:00 2001 From: El-Virus Date: Tue, 25 Feb 2025 20:46:39 +0100 Subject: [PATCH 2/2] Add missing type checks for self-referencing constant --- src/Rules/Traits/ConflictingTraitConstantsRule.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Rules/Traits/ConflictingTraitConstantsRule.php b/src/Rules/Traits/ConflictingTraitConstantsRule.php index 31b2f31f52..c28e3b763f 100644 --- a/src/Rules/Traits/ConflictingTraitConstantsRule.php +++ b/src/Rules/Traits/ConflictingTraitConstantsRule.php @@ -227,18 +227,22 @@ private function processSingleConstant(ClassReflection $classReflection, Reflect $traitValueExpr = $traitConstant->getValueExpression(); $isTraitSelfReferencing = ( $traitValueExpr instanceof Node\Expr\ClassConstFetch + && $traitValueExpr->class instanceof Node\Name + && $traitValueExpr->name instanceof Node\Identifier && $traitValueExpr->class->name === 'self' && $traitValueExpr->name->name === $traitConstant->getName() ); if ($isTraitSelfReferencing) { $isValueSelfReference = ( $valueExpr instanceof Node\Expr\ClassConstFetch + && $valueExpr->class instanceof Node\Name + && $valueExpr->name instanceof Node\Identifier && $valueExpr->class->name === 'self' && $valueExpr->name->name === $traitConstant->getName() ); if ( !$isValueSelfReference - && isset($classConstantValueType) + && isset($constantNativeTypeType) && !$classConstantValueType instanceof $constantNativeTypeType && $classConstantValueType->isSuperTypeOf($constantNativeTypeType)->no() ) {