diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index 277d417c..181f2111 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -308,6 +308,9 @@
+
+ $reflectionMethod->getReturnType()
+
DocBlockGenerator::fromArray($value)
ParameterGenerator::fromArray($parameter)
@@ -346,6 +349,9 @@
+
+ $reflectionParameter->getType()
+
$array['name']
$value
@@ -373,6 +379,9 @@
+
+ $reflectionProperty->getType()
+
DocBlockGenerator::fromArray($value)
@@ -491,19 +500,29 @@
-
+
+
allowsNull
- getName
- getName
- getParentClass
getTypes
getTypes
+
+ $type
+
+
+ $atomicType !== 'null'
+ $atomicType->type !== 'mixed' && $atomicType !== 'null'
+
+
+ $type instanceof ReflectionNamedType
+
-
-
- $types
-
+
+
+ getName
+ getName
+ getParentClass
+
@@ -1029,9 +1048,6 @@
-
- new TagManager()
-
testConstructorWithOptions
testCreatingTagFromReflection
@@ -1048,9 +1064,6 @@
-
- new TagManager()
-
testConstructorWithOptions
testCreatingTagFromReflection
@@ -1066,9 +1079,6 @@
-
- new TagManager()
-
testConstructorWithOptions
testCreatingTagFromReflection
@@ -1085,9 +1095,6 @@
-
- new TagManager()
-
testConstructorWithOptions
testCreatingTagFromReflection
@@ -1105,9 +1112,6 @@
-
- new TagManager()
-
testConstructorWithOptions
testCreatingTagFromReflection
@@ -1125,9 +1129,6 @@
-
- new TagManager()
-
testConstructorWithOptions
testCreatingTagFromReflection
@@ -1145,9 +1146,6 @@
-
- new TagManager()
-
testCreatingTagFromReflection
testNameIsCorrect
@@ -1162,9 +1160,6 @@
-
- new TagManager()
-
testCreatingTagFromReflection
testNameIsCorrect
@@ -1191,13 +1186,6 @@
null
-
-
- new TagManager()
- setVariableName
- setVariableName
-
-
setDatatype
@@ -1413,13 +1401,6 @@
-
- CompositeType::fromString($typeString)
-
-
- CompositeType::fromString($typeString)
- fullyQualifiedName
-
iterable
@@ -1466,9 +1447,6 @@
setMethods
-
- new PrototypeClassFactory()
-
testAddAndGetPrototype
testFallBackToGeneric
@@ -1872,12 +1850,6 @@
-
- new DocBlockScanner($docComment)
- new DocBlockScanner($docComment)
- new DocBlockScanner($docComment)
- new DocBlockScanner($docComment)
-
testDocBlockScannerDescriptions
testDocBlockScannerParsesTagsWithNoValuesProperly
diff --git a/psalm.xml b/psalm.xml
index 8c2df273..2501d42c 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -29,34 +29,17 @@
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
diff --git a/src/Generator/TypeGenerator.php b/src/Generator/TypeGenerator.php
index 14b8ed0a..014d2b19 100644
--- a/src/Generator/TypeGenerator.php
+++ b/src/Generator/TypeGenerator.php
@@ -1,5 +1,7 @@
assertCanBeStandaloneNullable();
+ }
+ }
+
/**
* @internal
*
* @psalm-pure
*/
public static function fromReflectionType(
- ?ReflectionType $type,
- ?ReflectionClass $currentClass
+ ReflectionNamedType|ReflectionUnionType|ReflectionIntersectionType|null $type,
+ ?ReflectionClass $currentClass
): ?self {
if (null === $type) {
return null;
}
- // Having to go through `fromTypeString` leads to interesting invalid types as "acceptable", but that's neither
- // a security issue, nor a major problem, since {@see ReflectionType} should itself produce valid/usable strings
- return self::fromTypeString(self::reflectionTypeToString($type, $currentClass));
- }
-
- /** @psalm-pure */
- private static function reflectionTypeToString(ReflectionType $type, ?ReflectionClass $currentClass): string
- {
- assert(
- $type instanceof ReflectionNamedType
- || $type instanceof ReflectionUnionType
- || $type instanceof ReflectionIntersectionType
- );
+ if ($type instanceof ReflectionUnionType) {
+ return new self(
+ new UnionType(array_map(
+ static fn(
+ ReflectionIntersectionType|ReflectionNamedType $type
+ ): IntersectionType|AtomicType => $type instanceof ReflectionNamedType
+ ? AtomicType::fromReflectionNamedTypeAndClass($type, $currentClass)
+ : self::fromIntersectionType($type, $currentClass),
+ $type->getTypes()
+ )),
+ false
+ );
+ }
- if ($type instanceof ReflectionNamedType) {
- return self::reflectionNamedTypeToString($type, $currentClass);
+ if ($type instanceof ReflectionIntersectionType) {
+ return new self(self::fromIntersectionType($type, $currentClass), false);
}
+
+ $atomicType = AtomicType::fromReflectionNamedTypeAndClass($type, $currentClass);
- return implode(
- $type instanceof ReflectionIntersectionType
- ? CompositeType::INTERSECTION_SEPARATOR
- : CompositeType::UNION_SEPARATOR,
- array_map(
- static function (ReflectionType $type) use ($currentClass): string {
- $typeString = self::reflectionTypeToString($type, $currentClass);
-
- return $type instanceof ReflectionIntersectionType
- ? sprintf('(%s)', $typeString)
- : $typeString;
- },
- $type->getTypes()
- )
+ return new self(
+ $atomicType,
+ $atomicType->type !== 'mixed' && $atomicType !== 'null' && $type->allowsNull()
);
}
/** @psalm-pure */
- private static function reflectionNamedTypeToString(
- ReflectionNamedType $type,
- ?ReflectionClass $currentClass
- ): string {
- $lowerCaseName = strtolower($type->getName());
-
- if ('mixed' === $lowerCaseName || 'null' === $lowerCaseName) {
- // `mixed` and `null` are implicitly nullable, therefore we need to skip adding nullability markers to it
- return $lowerCaseName;
- }
-
- $nullabilityMarker = $type->allowsNull()
- ? self::NULL_MARKER
- : '';
-
- if ('self' === $lowerCaseName && $currentClass) {
- return $nullabilityMarker . $currentClass->getName();
- }
-
- if ('parent' === $lowerCaseName && $currentClass && $parentClass = $currentClass->getParentClass()) {
- return $nullabilityMarker . $parentClass->getName();
- }
-
- return $nullabilityMarker . $type->getName();
+ private static function fromIntersectionType(
+ ReflectionIntersectionType $intersectionType,
+ ?ReflectionClass $currentClass
+ ): IntersectionType {
+ return new IntersectionType(array_map(
+ static fn(
+ ReflectionNamedType $type
+ ): AtomicType => AtomicType::fromReflectionNamedTypeAndClass($type, $currentClass),
+ $intersectionType->getTypes()
+ ));
}
/**
@@ -120,7 +97,7 @@ public static function fromTypeString(string $type): self
! str_contains($trimmedNullable, CompositeType::INTERSECTION_SEPARATOR)
&& ! str_contains($trimmedNullable, CompositeType::UNION_SEPARATOR)
) {
- return new self(AtomicType::fromString($trimmedNullable), $nullable);
+ return new self(CompositeType::fromString($trimmedNullable), $nullable);
}
if ($nullable) {
@@ -133,13 +110,6 @@ public static function fromTypeString(string $type): self
return new self(CompositeType::fromString($trimmedNullable));
}
- private function __construct(private readonly UnionType|IntersectionType|AtomicType $type, private readonly bool $nullable = false)
- {
- if ($nullable && $type instanceof AtomicType) {
- $type->assertCanBeStandaloneNullable();
- }
- }
-
/**
* {@inheritDoc}
*
@@ -160,9 +130,9 @@ public function equals(TypeGenerator $otherType): bool
}
/**
- * @return string the cleaned type string. Please note that this value is not suitable for code generation,
- * since the returned value does not include any root namespace prefixes, when applicable,
- * and therefore the values cannot be used as FQCN in generated code.
+ * @return non-empty-string the cleaned type string. Note that this value is not suitable for code generation,
+ * since the returned value does not include any root namespace prefixes, when applicable,
+ * and therefore the values cannot be used as FQCN in generated code.
*/
public function __toString(): string
{
diff --git a/src/Generator/TypeGenerator/AtomicType.php b/src/Generator/TypeGenerator/AtomicType.php
index bc90d9c1..73d4c2aa 100644
--- a/src/Generator/TypeGenerator/AtomicType.php
+++ b/src/Generator/TypeGenerator/AtomicType.php
@@ -4,6 +4,9 @@
use Laminas\Code\Generator\Exception\InvalidArgumentException;
+use ReflectionClass;
+use ReflectionNamedType;
+
use function array_key_exists;
use function assert;
use function implode;
@@ -113,10 +116,26 @@ public static function fromString(string $type): self
return new self($trimmedType, 0);
}
- /** @psalm-pure */
- public static function null(): self
- {
- return new self('null', self::BUILT_IN_TYPES_PRECEDENCE['null']);
+ /**
+ * @psalm-pure
+ * @throws InvalidArgumentException
+ */
+ public static function fromReflectionNamedTypeAndClass(
+ ReflectionNamedType $type,
+ ?ReflectionClass $currentClass
+ ): self {
+ $name = $type->getName();
+ $lowerCaseName = strtolower($name);
+
+ if ('self' === $lowerCaseName && $currentClass) {
+ return new self($currentClass->getName(), 0);
+ }
+
+ if ('parent' === $lowerCaseName && $currentClass && $parentClass = $currentClass->getParentClass()) {
+ return new self($parentClass->getName(), 0);
+ }
+
+ return self::fromString($name);
}
/** @psalm-return non-empty-string */
@@ -173,11 +192,8 @@ public function assertCanUnionWith(self|IntersectionType $other): void
}
}
- /**
- * @psalm-param non-empty-array $others
- * @throws InvalidArgumentException
- */
- public function assertCanIntersectWith(array $others): void
+ /** @throws InvalidArgumentException */
+ public function assertCanIntersectWith(AtomicType $other): void
{
if (array_key_exists($this->type, self::BUILT_IN_TYPES_PRECEDENCE)) {
throw new InvalidArgumentException(sprintf(
@@ -186,14 +202,12 @@ public function assertCanIntersectWith(array $others): void
));
}
- foreach ($others as $other) {
- if ($other->type === $this->type) {
- throw new InvalidArgumentException(sprintf(
- 'Type "%s" cannot be composed in an intersection with the same type "%s"',
- $this->type,
- $other->type
- ));
- }
+ if ($other->type === $this->type) {
+ throw new InvalidArgumentException(sprintf(
+ 'Type "%s" cannot be composed in an intersection with the same type "%s"',
+ $this->type,
+ $other->type
+ ));
}
}
diff --git a/src/Generator/TypeGenerator/IntersectionType.php b/src/Generator/TypeGenerator/IntersectionType.php
index f625d9f6..52e9094e 100644
--- a/src/Generator/TypeGenerator/IntersectionType.php
+++ b/src/Generator/TypeGenerator/IntersectionType.php
@@ -37,9 +37,9 @@ public function __construct(array $types)
);
foreach ($types as $index => $atomicType) {
- $otherTypes = array_diff_key($types, array_flip([$index]));
-
- $atomicType->assertCanIntersectWith($otherTypes);
+ foreach (array_diff_key($types, array_flip([$index])) as $otherType) {
+ $atomicType->assertCanIntersectWith($otherType);
+ }
}
$this->types = $types;
diff --git a/test/Generator/Cases/BackedCasesTest.php b/test/Generator/Cases/BackedCasesTest.php
index 373d07fa..c373cb49 100644
--- a/test/Generator/Cases/BackedCasesTest.php
+++ b/test/Generator/Cases/BackedCasesTest.php
@@ -15,7 +15,6 @@ public function testProvidingInvalidTypeThrowsException(): void
'"bool" is not a valid type for Enums, only "int" and "string" types are allowed.'
);
- /** @psalm-suppress InternalMethod, InternalClass */
BackedCases::fromCasesWithType([], 'bool');
}
}
diff --git a/test/Generator/TypeGenerator/IntersectionTypeTest.php b/test/Generator/TypeGenerator/IntersectionTypeTest.php
index 34cd8aa1..598d39d3 100644
--- a/test/Generator/TypeGenerator/IntersectionTypeTest.php
+++ b/test/Generator/TypeGenerator/IntersectionTypeTest.php
@@ -75,16 +75,20 @@ public function testWillRejectInvalidIntersections(array $types): void
public static function invalidIntersectionsExamples(): array
{
return [
- [
- 'same type makes no sense' => [
+ 'same type makes no sense' => [
+ [
AtomicType::fromString('A'),
AtomicType::fromString('A'),
],
- 'same type makes no sense, even with different namespace qualifier' => [
+ ],
+ 'same type makes no sense, even with different namespace qualifier' => [
+ [
AtomicType::fromString('A'),
AtomicType::fromString('\A'),
],
- 'duplicate type in long chain of types' => [
+ ],
+ 'duplicate type in long chain of types' => [
+ [
AtomicType::fromString('A'),
AtomicType::fromString('B'),
AtomicType::fromString('C'),
@@ -92,7 +96,9 @@ public static function invalidIntersectionsExamples(): array
AtomicType::fromString('A'),
AtomicType::fromString('E'),
],
- 'native types cannot intersect with other types' => [
+ ],
+ 'native types cannot intersect with other types' => [
+ [
AtomicType::fromString('A'),
AtomicType::fromString('bool'),
],
diff --git a/test/Generator/TypeGenerator/UnionTypeTest.php b/test/Generator/TypeGenerator/UnionTypeTest.php
index 3dfd1870..a54bf29a 100644
--- a/test/Generator/TypeGenerator/UnionTypeTest.php
+++ b/test/Generator/TypeGenerator/UnionTypeTest.php
@@ -16,8 +16,8 @@ class UnionTypeTest extends TestCase
/**
* @dataProvider sortingExamples
*
- * @param AtomicType|IntersectionType $types
- * @param non-empty-string $expected
+ * @param non-empty-list $types
+ * @param non-empty-string $expected
*/
public function testTypeSorting(array $types, string $expected): void
{
@@ -108,7 +108,7 @@ public function testWillRejectInvalidUnions(array $types): void
new UnionType($types);
}
- /** @return non-empty-array}> */
+ /** @return non-empty-array}> */
public static function invalidUnionsExamples(): array
{
return [