Skip to content

Commit a88f398

Browse files
ondrejmirtesclaude
andcommitted
Key types by their value where describe() was standing in for it
Two hot paths built a description only to use it as a key. RecursionGuard::run() keys every operation it guards with describe(VerbosityLevel::value()), and that level resolves the class name through the ReflectionProvider - two round-trips per guarded call, for a string that never changes. ObjectType now remembers it. UnionType::getFiniteTypes() uniqued its members by description. They are finite types, so FiniteTypeSet keys them by the value they stand for, which is what the uniquing wants; floats have no such key, equals() not agreeing with value identity for them, and keep describing themselves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LDqrN1pcN3qWc7xzKfkJaF
1 parent b234a39 commit a88f398

2 files changed

Lines changed: 23 additions & 3 deletions

File tree

‎src/Type/ObjectType.php‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,14 @@ class ObjectType implements TypeWithClassName, SubtractableType
124124

125125
private ?string $cachedDescription = null;
126126

127+
/**
128+
* The class name as the reflection spells it - the properly cased name, or the
129+
* readable `class@anonymous...` form. Resolving it takes two ReflectionProvider
130+
* round-trips and both the type-only and the value level need it, the latter on
131+
* every operation RecursionGuard guards.
132+
*/
133+
private ?string $cachedPreciseName = null;
134+
127135
/**
128136
* The reflection resolved on demand by getClassReflection(), kept apart from the one
129137
* handed to the constructor. The constructor's reflection is part of the type's value
@@ -742,13 +750,21 @@ private function checkSubclassAcceptability(string $thatClass): AcceptsResult
742750

743751
public function describe(VerbosityLevel $level): string
744752
{
753+
if ($this->cachedPreciseName !== null && ($level->isValue() || $level->isTypeOnly())) {
754+
return $this->cachedPreciseName;
755+
}
756+
745757
$preciseNameCallback = function (): string {
758+
if ($this->cachedPreciseName !== null) {
759+
return $this->cachedPreciseName;
760+
}
761+
746762
$reflectionProvider = ReflectionProviderStaticAccessor::getInstance();
747763
if (!$reflectionProvider->hasClass($this->className)) {
748-
return $this->className;
764+
return $this->cachedPreciseName = $this->className;
749765
}
750766

751-
return $reflectionProvider->getClassName($this->className);
767+
return $this->cachedPreciseName = $reflectionProvider->getClassName($this->className);
752768
};
753769

754770
$preciseWithSubtracted = fn (): string => $this->className . $this->describeSubtractedType($this->subtractedType, $level);

‎src/Type/UnionType.php‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1662,7 +1662,11 @@ public function getFiniteTypes(): array
16621662
$types = $this->notBenevolentPickFromTypes(static fn (Type $type) => $type->getFiniteTypes());
16631663
$uniquedTypes = [];
16641664
foreach ($types as $type) {
1665-
$uniquedTypes[$type->describe(VerbosityLevel::cache())] = $type;
1665+
// These are finite types, so FiniteTypeSet keys them by the value they
1666+
// stand for - which is what the uniquing wants and what describe() was
1667+
// standing in for. Floats have no such key (equals() does not agree with
1668+
// value identity for them) and keep describing themselves.
1669+
$uniquedTypes[FiniteTypeSet::key($type) ?? $type->describe(VerbosityLevel::cache())] = $type;
16661670
}
16671671

16681672
if (count($uniquedTypes) > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT) {

0 commit comments

Comments
 (0)