diff --git a/src/NodeCompiler/CompileNodeToValue.php b/src/NodeCompiler/CompileNodeToValue.php index ed8744af7..f50b76ab5 100644 --- a/src/NodeCompiler/CompileNodeToValue.php +++ b/src/NodeCompiler/CompileNodeToValue.php @@ -205,6 +205,7 @@ private function constantExists(string $constantName, CompilerContext $context): private function getConstantValue(Node\Expr\ConstFetch $node, string|null $constantName, CompilerContext $context): mixed { // It's not resolved when constant value is expression + // @infection-ignore-all Assignment, AssignCoalesce: There's no difference, ??= is just optimization $constantName ??= $this->resolveConstantName($node, $context); if (defined($constantName)) { @@ -227,6 +228,7 @@ private function resolveClassConstantName(Node\Expr\ClassConstFetch $node, Compi private function getClassConstantValue(Node\Expr\ClassConstFetch $node, string|null $classConstantName, CompilerContext $context): mixed { // It's not resolved when constant value is expression + // @infection-ignore-all Assignment, AssignCoalesce: There's no difference, ??= is just optimization $classConstantName ??= $this->resolveClassConstantName($node, $context); [$className, $constantName] = explode('::', $classConstantName); diff --git a/src/NodeCompiler/CompilerContext.php b/src/NodeCompiler/CompilerContext.php index 7464e8fd6..ae722829b 100644 --- a/src/NodeCompiler/CompilerContext.php +++ b/src/NodeCompiler/CompilerContext.php @@ -34,6 +34,7 @@ public function getFileName(): string|null return $this->contextReflection->getFileName(); } + // @infection-ignore-all Coalesce: There's no difference return $this->getClass()?->getFileName() ?? $this->getFunction()?->getFileName(); } @@ -43,6 +44,7 @@ public function getNamespace(): string return $this->contextReflection->getNamespaceName(); } + // @infection-ignore-all Coalesce: There's no difference return $this->getClass()?->getNamespaceName() ?? $this->getFunction()?->getNamespaceName() ?? ''; } diff --git a/src/Reflection/ReflectionAttribute.php b/src/Reflection/ReflectionAttribute.php index b1616756d..9faa34852 100644 --- a/src/Reflection/ReflectionAttribute.php +++ b/src/Reflection/ReflectionAttribute.php @@ -66,6 +66,7 @@ public function getTarget(): int $this->owner instanceof ReflectionProperty => Attribute::TARGET_PROPERTY, $this->owner instanceof ReflectionClassConstant => Attribute::TARGET_CLASS_CONSTANT, $this->owner instanceof ReflectionEnumCase => Attribute::TARGET_CLASS_CONSTANT, + // @infection-ignore-all InstanceOf_: There's no other option $this->owner instanceof ReflectionParameter => Attribute::TARGET_PARAMETER, }; } diff --git a/src/Reflection/ReflectionClass.php b/src/Reflection/ReflectionClass.php index c2aeac1cc..4318cefd6 100644 --- a/src/Reflection/ReflectionClass.php +++ b/src/Reflection/ReflectionClass.php @@ -1138,6 +1138,7 @@ private function addStringableInterface(array $interfaces): array // Stringable interface does not exist on target PHP version } + // @infection-ignore-all Break_: There's no difference between break and continue - break is just optimization break; } } diff --git a/src/Reflection/ReflectionClassConstant.php b/src/Reflection/ReflectionClassConstant.php index 27e76b72f..d6f5a3f48 100644 --- a/src/Reflection/ReflectionClassConstant.php +++ b/src/Reflection/ReflectionClassConstant.php @@ -26,7 +26,7 @@ class ReflectionClassConstant /** @var non-empty-string */ private string $name; - private int $modifiers = 0; + private int $modifiers; private Node\Expr $value; @@ -59,14 +59,13 @@ private function __construct( $name = $node->consts[$positionInNode]->name->name; assert($name !== ''); - $this->name = $name; - $this->value = $node->consts[$positionInNode]->value; + $this->name = $name; + $this->modifiers = $this->computeModifiers($node); + $this->value = $node->consts[$positionInNode]->value; $this->docComment = GetLastDocComment::forNode($node); $this->attributes = ReflectionAttributeHelper::createAttributes($reflector, $this, $node->attrGroups); - $this->computeModifiers($node); - $startLine = $node->getStartLine(); assert($startLine > 0); $endLine = $node->getEndLine(); @@ -147,7 +146,8 @@ public function isPublic(): bool */ public function isPrivate(): bool { - return ($this->modifiers & CoreReflectionClassConstant::IS_PRIVATE) === CoreReflectionClassConstant::IS_PRIVATE; + // Private constant cannot be final + return $this->modifiers === CoreReflectionClassConstant::IS_PRIVATE; } /** @@ -259,18 +259,19 @@ public function getAttributesByInstance(string $className): array return ReflectionAttributeHelper::filterAttributesByInstance($this->getAttributes(), $className); } - private function computeModifiers(ClassConst $node): void + private function computeModifiers(ClassConst $node): int { - if ($node->isFinal()) { - $this->modifiers = self::IS_FINAL; - } + $modifiers = $node->isFinal() ? self::IS_FINAL : 0; if ($node->isPrivate()) { - $this->modifiers += CoreReflectionClassConstant::IS_PRIVATE; + // No += because private constant cannot be final + $modifiers = CoreReflectionClassConstant::IS_PRIVATE; } elseif ($node->isProtected()) { - $this->modifiers += CoreReflectionClassConstant::IS_PROTECTED; + $modifiers += CoreReflectionClassConstant::IS_PROTECTED; } else { - $this->modifiers += CoreReflectionClassConstant::IS_PUBLIC; + $modifiers += CoreReflectionClassConstant::IS_PUBLIC; } + + return $modifiers; } } diff --git a/src/Reflection/ReflectionFunctionAbstract.php b/src/Reflection/ReflectionFunctionAbstract.php index 90d290619..19d8ef9eb 100644 --- a/src/Reflection/ReflectionFunctionAbstract.php +++ b/src/Reflection/ReflectionFunctionAbstract.php @@ -8,7 +8,6 @@ use PhpParser\Node; use PhpParser\Node\Expr\Yield_ as YieldNode; use PhpParser\Node\Expr\YieldFrom as YieldFromNode; -use PhpParser\Node\Param as ParamNode; use PhpParser\Node\Stmt\Throw_ as NodeThrow; use PhpParser\NodeTraverser; use PhpParser\NodeVisitor\FindingVisitor; @@ -101,7 +100,7 @@ public function getParameters(): array $paramNode, $this, $paramIndex, - $this->isParameterOptional($nodeParams, $paramNode, $paramIndex), + $this->isParameterOptional($nodeParams, $paramIndex), ); } @@ -109,18 +108,10 @@ public function getParameters(): array } /** @param list $parameterNodes */ - private function isParameterOptional(array $parameterNodes, ParamNode $parameterNode, int $parameterIndex): bool + private function isParameterOptional(array $parameterNodes, int $parameterIndex): bool { - if ($parameterNode->variadic) { - return true; - } - - if ($parameterNode->default === null) { - return false; - } - foreach ($parameterNodes as $otherParameterIndex => $otherParameterNode) { - if ($otherParameterIndex <= $parameterIndex) { + if ($otherParameterIndex < $parameterIndex) { continue; } diff --git a/src/Reflection/ReflectionIntersectionType.php b/src/Reflection/ReflectionIntersectionType.php index 31b01e1e3..22554a777 100644 --- a/src/Reflection/ReflectionIntersectionType.php +++ b/src/Reflection/ReflectionIntersectionType.php @@ -9,7 +9,6 @@ use Roave\BetterReflection\Reflector\Reflector; use function array_map; -use function array_values; use function assert; use function implode; @@ -25,12 +24,12 @@ public function __construct( IntersectionType $type, ) { /** @var non-empty-list $types */ - $types = array_values(array_map(static function (Node\Identifier|Node\Name $type) use ($reflector, $owner): ReflectionNamedType { + $types = array_map(static function (Node\Identifier|Node\Name $type) use ($reflector, $owner): ReflectionNamedType { $type = ReflectionType::createFromNode($reflector, $owner, $type); assert($type instanceof ReflectionNamedType); return $type; - }, $type->types)); + }, $type->types); $this->types = $types; } @@ -48,6 +47,7 @@ public function allowsNull(): bool public function __toString(): string { + // @infection-ignore-all UnwrapArrayMap: It works without array_map() as well but this is less magical return implode('&', array_map(static fn (ReflectionNamedType $type): string => $type->__toString(), $this->types)); } } diff --git a/src/Reflection/ReflectionMethod.php b/src/Reflection/ReflectionMethod.php index 5fdae5008..69c0ab13a 100644 --- a/src/Reflection/ReflectionMethod.php +++ b/src/Reflection/ReflectionMethod.php @@ -129,16 +129,17 @@ public function getPrototype(): self $currentClass = $currentClass->getParentClass(); if ($currentClass === null || ! $currentClass->hasMethod($this->getName())) { + // @infection-ignore-all Break_: There's no difference between break and continue - break is just optimization break; } $prototype = $currentClass->getMethod($this->getName())->findPrototype(); - if ($prototype !== null) { - if ($this->isConstructor() && ! $prototype->isAbstract()) { - break; - } + if ($prototype === null) { + break; + } + if (! $this->isConstructor() || $prototype->isAbstract()) { return $prototype; } } diff --git a/src/Reflection/ReflectionProperty.php b/src/Reflection/ReflectionProperty.php index 054e84a53..2476f2f4c 100644 --- a/src/Reflection/ReflectionProperty.php +++ b/src/Reflection/ReflectionProperty.php @@ -48,7 +48,7 @@ class ReflectionProperty /** @var non-empty-string */ private string $name; - private int $modifiers = 0; + private int $modifiers; private ReflectionNamedType|ReflectionUnionType|ReflectionIntersectionType|null $type; @@ -86,13 +86,12 @@ private function __construct( assert($name !== ''); $this->name = $name; + $this->modifiers = $this->computeModifiers($node); $this->type = $this->createType($node); $this->default = $node->props[$positionInNode]->default; $this->docComment = GetLastDocComment::forNode($node); $this->attributes = ReflectionAttributeHelper::createAttributes($reflector, $this, $node->attrGroups); - $this->computeModifiers($node); - $startLine = null; if ($node->hasAttribute('startLine')) { $startLine = $node->getStartLine(); @@ -566,22 +565,24 @@ private function assertObject(mixed $object): object return $object; } - private function computeModifiers(PropertyNode $node): void + private function computeModifiers(PropertyNode $node): int { if ($node->isStatic()) { - $this->modifiers = CoreReflectionProperty::IS_STATIC; - } - - if ($node->isReadonly()) { - $this->modifiers += self::IS_READONLY; + $modifiers = CoreReflectionProperty::IS_STATIC; + } elseif ($node->isReadonly()) { + $modifiers = self::IS_READONLY; + } else { + $modifiers = 0; } if ($node->isPrivate()) { - $this->modifiers += CoreReflectionProperty::IS_PRIVATE; + $modifiers += CoreReflectionProperty::IS_PRIVATE; } elseif ($node->isProtected()) { - $this->modifiers += CoreReflectionProperty::IS_PROTECTED; + $modifiers += CoreReflectionProperty::IS_PROTECTED; } else { - $this->modifiers += CoreReflectionProperty::IS_PUBLIC; + $modifiers += CoreReflectionProperty::IS_PUBLIC; } + + return $modifiers; } } diff --git a/src/Reflection/ReflectionUnionType.php b/src/Reflection/ReflectionUnionType.php index f01ce8a91..d71f7c280 100644 --- a/src/Reflection/ReflectionUnionType.php +++ b/src/Reflection/ReflectionUnionType.php @@ -11,7 +11,6 @@ use Roave\BetterReflection\Reflector\Reflector; use function array_map; -use function array_values; use function assert; use function implode; use function sprintf; @@ -28,12 +27,12 @@ public function __construct( UnionType $type, ) { /** @var non-empty-list $types */ - $types = array_values(array_map(static function (Identifier|Name|IntersectionType $type) use ($reflector, $owner): ReflectionNamedType|ReflectionIntersectionType { + $types = array_map(static function (Identifier|Name|IntersectionType $type) use ($reflector, $owner): ReflectionNamedType|ReflectionIntersectionType { $type = ReflectionType::createFromNode($reflector, $owner, $type); assert($type instanceof ReflectionNamedType || $type instanceof ReflectionIntersectionType); return $type; - }, $type->types)); + }, $type->types); $this->types = $types; } diff --git a/src/SourceLocator/Ast/Exception/ParseToAstFailure.php b/src/SourceLocator/Ast/Exception/ParseToAstFailure.php index 387360c7e..66f1d85cd 100644 --- a/src/SourceLocator/Ast/Exception/ParseToAstFailure.php +++ b/src/SourceLocator/Ast/Exception/ParseToAstFailure.php @@ -26,7 +26,7 @@ public static function fromLocatedSource(LocatedSource $locatedSource, Throwable $fileName = $locatedSource->getFileName(); if ($fileName !== null) { - $additionalInformation .= sprintf(' in file %s', $fileName); + $additionalInformation = sprintf(' in file %s', $fileName); } if ($previous instanceof Error) { diff --git a/src/SourceLocator/Type/AutoloadSourceLocator.php b/src/SourceLocator/Type/AutoloadSourceLocator.php index cad162c74..0a391b145 100644 --- a/src/SourceLocator/Type/AutoloadSourceLocator.php +++ b/src/SourceLocator/Type/AutoloadSourceLocator.php @@ -255,6 +255,7 @@ private function locateConstantByName(string $constantName): array|null // Note: looking at files in reverse order, since newer files are more likely to have // defined a constant that is being looked up. Earlier files are possibly related // to libraries/frameworks that we rely upon. + // @infection-ignore-all UnwrapArrayReverse: Ignore because the result is some with or without array_reverse() foreach (array_reverse(get_included_files()) as $includedFileName) { try { FileChecker::assertReadableFile($includedFileName); diff --git a/test/unit/Fixture/ExampleClass.php b/test/unit/Fixture/ExampleClass.php index 938d049c2..57ec44d64 100644 --- a/test/unit/Fixture/ExampleClass.php +++ b/test/unit/Fixture/ExampleClass.php @@ -44,6 +44,10 @@ class ExampleClass public static $publicStaticProperty; + protected static $protectedStaticProperty; + + private static $privateStaticProperty; + public function __construct(/** Some doccomment */ private ?int $promotedProperty = 123, $noPromotedProperty = null) { } diff --git a/test/unit/Fixture/ExampleClassExport.txt b/test/unit/Fixture/ExampleClassExport.txt index 8f1ae4d07..f3fe34128 100644 --- a/test/unit/Fixture/ExampleClassExport.txt +++ b/test/unit/Fixture/ExampleClassExport.txt @@ -1,5 +1,5 @@ Class [ class Roave\BetterReflectionTest\Fixture\ExampleClass ] { - @@ %s/test/unit/Fixture/ExampleClass.php 10-54 + @@ %s/test/unit/Fixture/ExampleClass.php 10-58 - Constants [7] { Constant [ public integer MY_CONST_1 ] { 123 } @@ -11,8 +11,10 @@ Class [ class Roave\BetterReflectionTest\Fixture\ExampleClass ] { Constant [ final protected integer MY_CONST_7 ] { 789 } } - - Static properties [1] { + - Static properties [3] { Property [ public static $publicStaticProperty ] + Property [ protected static $protectedStaticProperty ] + Property [ private static $privateStaticProperty ] } - Static methods [0] { @@ -28,7 +30,7 @@ Class [ class Roave\BetterReflectionTest\Fixture\ExampleClass ] { - Methods [2] { Method [ public method __construct ] { - @@ %s/test/unit/Fixture/ExampleClass.php 47 - 49 + @@ %s/test/unit/Fixture/ExampleClass.php 51 - 53 - Parameters [2] { Parameter #0 [ ?int $promotedProperty = 123 ] @@ -37,7 +39,7 @@ Class [ class Roave\BetterReflectionTest\Fixture\ExampleClass ] { } Method [ public method someMethod ] { - @@ %s/test/unit/Fixture/ExampleClass.php 51 - 53 + @@ %s/test/unit/Fixture/ExampleClass.php 55 - 57 } } } diff --git a/test/unit/Reflection/ReflectionClassTest.php b/test/unit/Reflection/ReflectionClassTest.php index 35e98d76a..414c1ede4 100644 --- a/test/unit/Reflection/ReflectionClassTest.php +++ b/test/unit/Reflection/ReflectionClassTest.php @@ -545,7 +545,7 @@ public function testGetProperties(): void $properties = $classInfo->getProperties(); self::assertContainsOnlyInstancesOf(ReflectionProperty::class, $properties); - self::assertCount(6, $properties); + self::assertCount(8, $properties); } public function testGetPropertiesForPureEnum(): void @@ -567,6 +567,9 @@ public function testGetPropertiesForPureEnum(): void self::assertTrue($property->isReadOnly()); self::assertFalse($property->isPromoted()); self::assertTrue($property->isDefault()); + + // No value property for pure enum + self::assertArrayNotHasKey('value', $properties); } /** @return list}> */ @@ -589,6 +592,10 @@ public function dataGetPropertiesForBackedEnum(): array BackedEnum::class, ['name' => 'string', 'value' => 'int|string'], ], + [ + InterfaceForEnum::class, + [/* No enum properties */], + ], ]; } @@ -607,6 +614,8 @@ public function testGetPropertiesForBackedEnum(string $className, array $propert $classInfo = $reflector->reflectClass($className); $properties = $classInfo->getProperties(); + self::assertCount(count($propertiesData), $properties); + foreach ($propertiesData as $propertyName => $propertyType) { $fullPropertyName = sprintf('%s::$%s', $className, $propertyName); @@ -652,16 +661,16 @@ class Foo public function getPropertiesWithFilterDataProvider(): array { return [ - [CoreReflectionProperty::IS_STATIC, 1], + [CoreReflectionProperty::IS_STATIC, 3], [CoreReflectionProperty::IS_PUBLIC, 3], - [CoreReflectionProperty::IS_PROTECTED, 1], - [CoreReflectionProperty::IS_PRIVATE, 2], + [CoreReflectionProperty::IS_PROTECTED, 2], + [CoreReflectionProperty::IS_PRIVATE, 3], [ CoreReflectionProperty::IS_STATIC | CoreReflectionProperty::IS_PUBLIC | CoreReflectionProperty::IS_PROTECTED | CoreReflectionProperty::IS_PRIVATE, - 6, + 8, ], ]; } diff --git a/test/unit/Reflection/ReflectionPropertyTest.php b/test/unit/Reflection/ReflectionPropertyTest.php index 4de9a6fde..a290c989c 100644 --- a/test/unit/Reflection/ReflectionPropertyTest.php +++ b/test/unit/Reflection/ReflectionPropertyTest.php @@ -117,6 +117,14 @@ public function testIsStatic(): void $publiStaticProp = $classInfo->getProperty('publicStaticProperty'); self::assertTrue($publiStaticProp->isPublic()); self::assertTrue($publiStaticProp->isStatic()); + + $protectedStaticProp = $classInfo->getProperty('protectedStaticProperty'); + self::assertTrue($protectedStaticProp->isProtected()); + self::assertTrue($protectedStaticProp->isStatic()); + + $privateStaticProp = $classInfo->getProperty('privateStaticProperty'); + self::assertTrue($privateStaticProp->isPrivate()); + self::assertTrue($privateStaticProp->isStatic()); } public function testIsReadOnly(): void @@ -198,8 +206,8 @@ public function testIsPromoted(): void self::assertSame('int|null', $promotedProperty->getType()->__toString()); self::assertFalse($promotedProperty->hasDefaultValue()); self::assertNull($promotedProperty->getDefaultValue()); - self::assertSame(47, $promotedProperty->getStartLine()); - self::assertSame(47, $promotedProperty->getEndLine()); + self::assertSame(51, $promotedProperty->getStartLine()); + self::assertSame(51, $promotedProperty->getEndLine()); self::assertSame(60, $promotedProperty->getStartColumn()); self::assertSame(95, $promotedProperty->getEndColumn()); self::assertSame('/** Some doccomment */', $promotedProperty->getDocComment()); diff --git a/test/unit/SourceLocator/Ast/Exception/ParseToAstFailureTest.php b/test/unit/SourceLocator/Ast/Exception/ParseToAstFailureTest.php index 7d08a1cdf..357423d84 100644 --- a/test/unit/SourceLocator/Ast/Exception/ParseToAstFailureTest.php +++ b/test/unit/SourceLocator/Ast/Exception/ParseToAstFailureTest.php @@ -207,12 +207,16 @@ public function testUnknownError(): void { $locatedSource = new LocatedSource('setAccessible(true); + $filenameProperty->setValue($locatedSource, '/foo/bar'); + $previous = new Exception('Unknown error'); $exception = ParseToAstFailure::fromLocatedSource($locatedSource, $previous); self::assertInstanceOf(ParseToAstFailure::class, $exception); - self::assertSame('AST failed to parse in located source: Unknown error', $exception->getMessage()); + self::assertSame('AST failed to parse in located source in file /foo/bar: Unknown error', $exception->getMessage()); self::assertSame($previous, $exception->getPrevious()); } } diff --git a/test/unit/SourceLocator/Type/AutoloadSourceLocatorTest.php b/test/unit/SourceLocator/Type/AutoloadSourceLocatorTest.php index 04bbaa6c5..37a835a81 100644 --- a/test/unit/SourceLocator/Type/AutoloadSourceLocatorTest.php +++ b/test/unit/SourceLocator/Type/AutoloadSourceLocatorTest.php @@ -16,6 +16,7 @@ use Roave\BetterReflection\Reflector\Exception\IdentifierNotFound; use Roave\BetterReflection\Reflector\Reflector; use Roave\BetterReflection\SourceLocator\Ast\Locator; +use Roave\BetterReflection\SourceLocator\Located\AliasLocatedSource; use Roave\BetterReflection\SourceLocator\Located\LocatedSource; use Roave\BetterReflection\SourceLocator\Type\AutoloadSourceLocator; use Roave\BetterReflectionTest\BetterReflectionSingleton; @@ -75,6 +76,7 @@ public function testClassLoads(): void self::assertFalse(class_exists(ExampleClass::class, false)); self::assertSame('ExampleClass', $classInfo->getShortName()); + self::assertNotInstanceOf(AliasLocatedSource::class, $classInfo->getLocatedSource()); } public function testClassLoadsWorksWithExistingClass(): void @@ -88,6 +90,21 @@ public function testClassLoadsWorksWithExistingClass(): void $classInfo = $reflector->reflectClass(ClassForHinting::class); self::assertSame('ClassForHinting', $classInfo->getShortName()); + self::assertNotInstanceOf(AliasLocatedSource::class, $classInfo->getLocatedSource()); + } + + public function testClassLoadsWithLowercasedName(): void + { + $reflector = new DefaultReflector(new AutoloadSourceLocator($this->astLocator)); + + // Ensure class is loaded first + new ClassForHinting(); + self::assertTrue(class_exists(ClassForHinting::class, false)); + + $classInfo = $reflector->reflectClass('roave\betterreflectiontest\fixture\classforhinting'); + + self::assertSame('ClassForHinting', $classInfo->getShortName()); + self::assertNotInstanceOf(AliasLocatedSource::class, $classInfo->getLocatedSource()); } /** @runInSeparateProcess */ @@ -200,8 +217,9 @@ public function testCanLocateAutoloadedClassByAlias(): void new IdentifierType(IdentifierType::IDENTIFIER_CLASS), )); - self::assertNotNull($reflection); + self::assertInstanceOf(BetterReflectionClass::class, $reflection); self::assertSame(AutoloadableByAlias::class, $reflection->getName()); + self::assertInstanceOf(AliasLocatedSource::class, $reflection->getLocatedSource()); } public function testFunctionLoads(): void