diff --git a/build/target-repository/docs/rector_rules_overview.md b/build/target-repository/docs/rector_rules_overview.md
index 30f82a2df57..7301dfb8cbe 100644
--- a/build/target-repository/docs/rector_rules_overview.md
+++ b/build/target-repository/docs/rector_rules_overview.md
@@ -1,4 +1,4 @@
-# 351 Rules Overview
+# 353 Rules Overview
@@ -44,7 +44,7 @@
- [Php82](#php82) (4)
-- [Php83](#php83) (1)
+- [Php83](#php83) (2)
- [Privatization](#privatization) (4)
@@ -5263,6 +5263,22 @@ Add override attribute to overridden methods
+### AddTypeToConstRector
+
+Add const to type
+
+- class: [`Rector\Php83\Rector\ClassConst\AddTypeToConstRector`](../rules/Php83/Rector/ClassConst/AddTypeToConstRector.php)
+
+```diff
+ final class SomeClass
+ {
+- public const TYPE = 'some_type';
++ public const string TYPE = 'some_type';
+ }
+```
+
+
+
## Privatization
### FinalizeClassesWithoutChildrenRector
diff --git a/config/set/php83.php b/config/set/php83.php
index 474fd7d2d4f..25ded98cf97 100644
--- a/config/set/php83.php
+++ b/config/set/php83.php
@@ -3,8 +3,10 @@
declare(strict_types=1);
use Rector\Config\RectorConfig;
-use Rector\Php83\Rector\ClassMethod\AddOverrideAttributeToOverriddenMethodsRector;
return static function (RectorConfig $rectorConfig): void {
- $rectorConfig->rules([AddOverrideAttributeToOverriddenMethodsRector::class]);
+ $rectorConfig->rules([
+ \Rector\Php83\Rector\ClassMethod\AddOverrideAttributeToOverriddenMethodsRector::class,
+ \Rector\Php83\Rector\ClassConst\AddTypeToConstRector::class,
+ ]);
};
diff --git a/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/AddTypeToConstRectorTest.php b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/AddTypeToConstRectorTest.php
new file mode 100644
index 00000000000..5e5f3f3d694
--- /dev/null
+++ b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/AddTypeToConstRectorTest.php
@@ -0,0 +1,28 @@
+doTestFile($filePath);
+ }
+
+ public static function provideData(): Iterator
+ {
+ return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
+ }
+
+ public function provideConfigFilePath(): string
+ {
+ return __DIR__ . '/config/configured_rule.php';
+ }
+}
diff --git a/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/apply_type_to_class_const_when_class_final.php.inc b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/apply_type_to_class_const_when_class_final.php.inc
new file mode 100644
index 00000000000..d3537c54aa8
--- /dev/null
+++ b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/apply_type_to_class_const_when_class_final.php.inc
@@ -0,0 +1,41 @@
+
+-----
+
diff --git a/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/apply_type_to_class_const_when_const_protected.php.inc b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/apply_type_to_class_const_when_const_protected.php.inc
new file mode 100644
index 00000000000..fae0e0a1efa
--- /dev/null
+++ b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/apply_type_to_class_const_when_const_protected.php.inc
@@ -0,0 +1,41 @@
+
+-----
+
diff --git a/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_abstract_class.php.inc b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_abstract_class.php.inc
new file mode 100644
index 00000000000..ea96a52a0e8
--- /dev/null
+++ b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_abstract_class.php.inc
@@ -0,0 +1,11 @@
+
+
diff --git a/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_implementable_not_autoloaded.php.inc b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_implementable_not_autoloaded.php.inc
new file mode 100644
index 00000000000..a62e294528f
--- /dev/null
+++ b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_implementable_not_autoloaded.php.inc
@@ -0,0 +1,11 @@
+
+
diff --git a/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_interface_defines_const.php.inc b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_interface_defines_const.php.inc
new file mode 100644
index 00000000000..59fe0b21a7c
--- /dev/null
+++ b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_interface_defines_const.php.inc
@@ -0,0 +1,13 @@
+
+
diff --git a/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_operations_used.php.inc b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_operations_used.php.inc
new file mode 100644
index 00000000000..266bbeb1c61
--- /dev/null
+++ b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_operations_used.php.inc
@@ -0,0 +1,16 @@
+
diff --git a/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_parent_class_const_defines_type.php.inc b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_parent_class_const_defines_type.php.inc
new file mode 100644
index 00000000000..120f0f35982
--- /dev/null
+++ b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_parent_class_const_defines_type.php.inc
@@ -0,0 +1,13 @@
+
+
diff --git a/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_parent_not_autoloaded.php.inc b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_parent_not_autoloaded.php.inc
new file mode 100644
index 00000000000..3eb6748b9f5
--- /dev/null
+++ b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_parent_not_autoloaded.php.inc
@@ -0,0 +1,11 @@
+
+
diff --git a/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_referencing_another_const.php.inc b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_referencing_another_const.php.inc
new file mode 100644
index 00000000000..287664ce825
--- /dev/null
+++ b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_referencing_another_const.php.inc
@@ -0,0 +1,12 @@
+
diff --git a/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_trait_defines_const.php.inc b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_trait_defines_const.php.inc
new file mode 100644
index 00000000000..21f74013583
--- /dev/null
+++ b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_trait_defines_const.php.inc
@@ -0,0 +1,15 @@
+
+
diff --git a/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_trait_not_autoloaded.php.inc b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_trait_not_autoloaded.php.inc
new file mode 100644
index 00000000000..2d544303501
--- /dev/null
+++ b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_trait_not_autoloaded.php.inc
@@ -0,0 +1,13 @@
+
+
diff --git a/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_type_already_set.php.inc b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_type_already_set.php.inc
new file mode 100644
index 00000000000..dae15e64449
--- /dev/null
+++ b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Fixture/skip_when_type_already_set.php.inc
@@ -0,0 +1,10 @@
+
diff --git a/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Source/ParentClass.php b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Source/ParentClass.php
new file mode 100644
index 00000000000..58325c04722
--- /dev/null
+++ b/rules-tests/Php83/Rector/ClassConst/AddTypeToConstRector/Source/ParentClass.php
@@ -0,0 +1,8 @@
+rule(\Rector\Php83\Rector\ClassConst\AddTypeToConstRector::class);
+
+ $rectorConfig->phpVersion(PhpVersion::PHP_83);
+};
diff --git a/rules/Php83/Rector/ClassConst/AddTypeToConstRector.php b/rules/Php83/Rector/ClassConst/AddTypeToConstRector.php
new file mode 100644
index 00000000000..67df5df23ef
--- /dev/null
+++ b/rules/Php83/Rector/ClassConst/AddTypeToConstRector.php
@@ -0,0 +1,236 @@
+isAbstract()) {
+ return null;
+ }
+
+ $consts = array_filter($node->stmts, function (Node $stmt) {
+ return $stmt instanceof Node\Stmt\ClassConst;
+ });
+
+ if ($consts === []) {
+ return null;
+ }
+
+ try {
+ $parents = $this->getParents($node);
+ $implementations = $this->getImplementations($node);
+ $traits = $this->getTraits($node);
+ } catch (FullyQualifiedNameNotAutoloadedException) {
+ return null;
+ }
+
+ $changes = false;
+
+ foreach ($consts as $const) {
+ // If a type is set, skip
+ if ($const->type !== null) {
+ continue;
+ }
+
+ foreach ($const->consts as $constNode) {
+ if ($this->shouldSkipDueToInheritance($constNode, $parents, $implementations, $traits)) {
+ continue;
+ }
+ if ($this->canBeInheritied($const, $node)) {
+ continue;
+ }
+ $valueType = $this->findValueType($constNode->value);
+ }
+
+ if (($valueType ?? null) === null) {
+ continue;
+ }
+
+ $const->type = $valueType;
+
+ $changes = true;
+ }
+
+ if (! $changes) {
+ return null;
+ }
+
+ return $node;
+ }
+
+ public function provideMinPhpVersion(): int
+ {
+ return PhpVersionFeature::TYPED_CLASS_CONSTANTS;
+ }
+
+ /**
+ * @param ClassReflection[] $parents
+ * @param ClassReflection[] $implementations
+ * @param ClassReflection[] $traits
+ */
+ public function shouldSkipDueToInheritance(
+ Node\Const_ $constNode,
+ array $parents,
+ array $implementations,
+ array $traits,
+ ): bool {
+ foreach ([$parents, $implementations, $traits] as $inheritance) {
+ foreach ($inheritance as $inheritanceItem) {
+ if ($constNode->name->name === '') {
+ continue;
+ }
+ try {
+ $inheritanceItem->getConstant($constNode->name->name);
+ return true;
+ } catch (MissingConstantFromReflectionException) {
+ }
+ }
+ }
+ return false;
+ }
+
+ private function findValueType(Node\Expr $value): ?Node\Identifier
+ {
+ if ($value instanceof Node\Scalar\String_) {
+ return new Node\Identifier('string');
+ }
+ if ($value instanceof Node\Scalar\LNumber) {
+ return new Node\Identifier('int');
+ }
+ if ($value instanceof Node\Scalar\DNumber) {
+ return new Node\Identifier('float');
+ }
+ if ($value instanceof Node\Expr\ConstFetch && $value->name->toLowerString() !== 'null') {
+ return new Node\Identifier('bool');
+ }
+ if ($value instanceof Node\Expr\ConstFetch && $value->name->toLowerString() === 'null') {
+ return new Node\Identifier('null');
+ }
+ if ($value instanceof Node\Expr\Array_) {
+ return new Node\Identifier('array');
+ }
+
+ return null;
+ }
+
+ /**
+ * @return ClassReflection[]
+ */
+ private function getParents(Class_ $class): array
+ {
+ $parents = array_filter([$class->extends]);
+
+ return array_map(function (Node\Name $name): ClassReflection {
+ if (! $name instanceof FullyQualified) {
+ throw new FullyQualifiedNameNotAutoloadedException($name);
+ }
+
+ if ($this->reflectionProvider->hasClass($name->toString())) {
+ return $this->reflectionProvider->getClass($name->toString());
+ }
+
+ throw new FullyQualifiedNameNotAutoloadedException($name);
+ }, $parents);
+ }
+
+ /**
+ * @return ClassReflection[]
+ */
+ private function getImplementations(Class_ $class): array
+ {
+ return array_map(function (Node\Name $name): ClassReflection {
+ if (! $name instanceof FullyQualified) {
+ throw new FullyQualifiedNameNotAutoloadedException($name);
+ }
+
+ if ($this->reflectionProvider->hasClass($name->toString())) {
+ return $this->reflectionProvider->getClass($name->toString());
+ }
+
+ throw new FullyQualifiedNameNotAutoloadedException($name);
+ }, $class->implements);
+ }
+
+ /**
+ * @return ClassReflection[]
+ */
+ private function getTraits(Class_ $node): array
+ {
+ $traits = [];
+ foreach ($node->getTraitUses() as $traitUse) {
+ $traits = [...$traits, ...$traitUse->traits];
+ }
+
+ return array_map(function (Node\Name $name): ClassReflection {
+ if (! $name instanceof FullyQualified) {
+ throw new FullyQualifiedNameNotAutoloadedException($name);
+ }
+
+ if ($this->reflectionProvider->hasClass($name->toString())) {
+ return $this->reflectionProvider->getClass($name->toString());
+ }
+
+ throw new FullyQualifiedNameNotAutoloadedException($name);
+ }, $traits);
+ }
+
+ private function canBeInheritied(Node\Stmt\ClassConst $constNode, Class_ $node): bool
+ {
+ return ! $node->isFinal() && ! $constNode->isPrivate();
+ }
+}
diff --git a/src/Exception/FullyQualifiedNameNotAutoloadedException.php b/src/Exception/FullyQualifiedNameNotAutoloadedException.php
new file mode 100644
index 00000000000..7adac11de3c
--- /dev/null
+++ b/src/Exception/FullyQualifiedNameNotAutoloadedException.php
@@ -0,0 +1,17 @@
+toString()));
+ }
+}
diff --git a/src/ValueObject/PhpVersionFeature.php b/src/ValueObject/PhpVersionFeature.php
index bc41c89be98..48382c5cbd5 100644
--- a/src/ValueObject/PhpVersionFeature.php
+++ b/src/ValueObject/PhpVersionFeature.php
@@ -649,4 +649,10 @@ final class PhpVersionFeature
* @var int
*/
public const OVERRIDE_ATTRIBUTE = PhpVersion::PHP_83;
+
+ /**
+ * @see https://wiki.php.net/rfc/typed_class_constants
+ * @var int
+ */
+ public const TYPED_CLASS_CONSTANTS = PhpVersion::PHP_83;
}