createTypeHash($firstType) === $this->createTypeHash($secondType); } public function createTypeHash(Type $type) : string { if ($type instanceof MixedType) { return $type->describe(VerbosityLevel::precise()) . $type->isExplicitMixed(); } if ($type instanceof ArrayType) { return $this->createTypeHash($type->getItemType()) . $this->createTypeHash($type->getKeyType()) . '[]'; } if ($type instanceof GenericObjectType) { return $type->describe(VerbosityLevel::precise()); } if ($type instanceof TypeWithClassName) { return $this->resolveUniqueTypeWithClassNameHash($type); } if ($type instanceof ConstantType) { return \get_class($type); } if ($type instanceof UnionType) { return $this->createUnionTypeHash($type); } $type = $this->normalizeObjectType($type); // normalize iterable $type = TypeTraverser::map($type, static function (Type $currentType, callable $traverseCallback) : Type { if (!$currentType instanceof ObjectType) { return $traverseCallback($currentType); } if ($currentType->getClassName() === 'iterable') { return new IterableType(new MixedType(), new MixedType()); } return $traverseCallback($currentType); }); return $type->describe(VerbosityLevel::value()); } private function resolveUniqueTypeWithClassNameHash(TypeWithClassName $typeWithClassName) : string { if ($typeWithClassName instanceof ShortenedObjectType) { return $typeWithClassName->getFullyQualifiedName(); } if ($typeWithClassName instanceof AliasedObjectType) { return $typeWithClassName->getFullyQualifiedName(); } return $typeWithClassName->getClassName(); } private function createUnionTypeHash(UnionType $unionType) : string { $booleanType = new BooleanType(); if ($booleanType->isSuperTypeOf($unionType)->yes()) { return $booleanType->describe(VerbosityLevel::precise()); } $normalizedUnionType = clone $unionType; // change alias to non-alias TypeTraverser::map($normalizedUnionType, static function (Type $type, callable $callable) : Type { if (!$type instanceof AliasedObjectType && !$type instanceof ShortenedObjectType) { return $callable($type); } return new FullyQualifiedObjectType($type->getFullyQualifiedName()); }); return $normalizedUnionType->describe(VerbosityLevel::precise()); } private function normalizeObjectType(Type $type) : Type { return TypeTraverser::map($type, static function (Type $currentType, callable $traverseCallback) : Type { if ($currentType instanceof ShortenedObjectType) { return new FullyQualifiedObjectType($currentType->getFullyQualifiedName()); } if ($currentType instanceof AliasedObjectType) { return new FullyQualifiedObjectType($currentType->getFullyQualifiedName()); } if ($currentType instanceof ObjectType && !$currentType instanceof GenericObjectType && $currentType->getClassName() !== 'Iterator' && $currentType->getClassName() !== 'iterable') { return new FullyQualifiedObjectType($currentType->getClassName()); } return $traverseCallback($currentType); }); } }