Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions tests/DeprecatedAndFunctionLikeGapsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,23 @@ class DeprecatedAndFunctionLikeGapsTest extends TestCase
'php84ImportedDeprecatedFunction',
];

/**
* Internal function that receives the #[\Deprecated] attribute in PHP 8.6 only
*
* @see https://github.com/goaop/parser-reflection/issues/215
*/
private const PHP86_DEPRECATED_INTERNAL_FUNCTION = 'strcoll';

/**
* Internal function that already carries the #[\Deprecated] attribute long before PHP 8.6
*/
private const LEGACY_DEPRECATED_INTERNAL_FUNCTION = 'utf8_encode';

/**
* Internal function that is not deprecated at all, used as a negative control
*/
private const PLAIN_INTERNAL_FUNCTION = 'strlen';

private string $stubFileName;

protected function setUp(): void
Expand Down Expand Up @@ -216,6 +233,112 @@ public function testCreateFromMethodNameParityWithNativeReflection(): void
$this->assertSame($nativeMethod->isDeprecated(), $parsedMethod->isDeprecated());
}

/**
* Internal functions have no source file, so the AST parser can never reflect them and the
* native reflection is always used for them instead.
*/
public function testInternalFunctionsAreReflectedByTheNativeFallback(): void
{
$parsedNamespace = $this->getStubFileNamespace();
$internalFunctions = [
self::PHP86_DEPRECATED_INTERNAL_FUNCTION,
self::LEGACY_DEPRECATED_INTERNAL_FUNCTION,
self::PLAIN_INTERNAL_FUNCTION,
];

foreach ($internalFunctions as $functionName) {
$this->assertTrue(function_exists($functionName), "Function {$functionName}() should be available");

$nativeFunction = new \ReflectionFunction($functionName);
$this->assertTrue($nativeFunction->isInternal(), "Function {$functionName}() should be internal");
$this->assertFalse(
$nativeFunction->getFileName(),
"Function {$functionName}() has no source file, therefore it can not be parsed"
);
$this->assertFalse(
$parsedNamespace->hasFunction($functionName),
"Function {$functionName}() should never be resolved to a parsed reflection"
);
}

// Parsed functions are always user-defined, so internal ones can only be served by the native
// reflection, and the parsed one is a drop-in replacement for the native \ReflectionFunction
$parsedFunction = $parsedNamespace->getFunction('php84PlainFunction');
$this->assertInstanceOf(\ReflectionFunction::class, $parsedFunction);
$this->assertFalse($parsedFunction->isInternal());
$this->assertTrue($parsedFunction->isUserDefined());
}

/**
* The #[\Deprecated] status of internal functions is reflected on every supported runtime,
* the mechanism is not specific to the functions deprecated in PHP 8.6.
*/
public function testDeprecatedInternalFunctionIsReflectedOnEveryRuntime(): void
{
$functionName = self::LEGACY_DEPRECATED_INTERNAL_FUNCTION;
$nativeFunction = new \ReflectionFunction($functionName);

$this->assertTrue($nativeFunction->isDeprecated(), "Function {$functionName}() is deprecated since PHP 8.2");
$this->assertSame('8.2', $this->getDeprecatedAttributeArguments($nativeFunction)['since']);
$this->assertStringContainsString('deprecated', $nativeFunction->__toString());

$plainFunction = new \ReflectionFunction(self::PLAIN_INTERNAL_FUNCTION);
$this->assertFalse($plainFunction->isDeprecated());
$this->assertSame([], $plainFunction->getAttributes(\Deprecated::class));
$this->assertStringNotContainsString('deprecated', $plainFunction->__toString());
}

public function testInternalFunctionDeprecatedInPhp86IsReflectedWithItsAttributePayload(): void
{
if (PHP_VERSION_ID < 80600) {
$this->markTestSkipped('Internal function strcoll() is deprecated since PHP 8.6 only');
}

$nativeFunction = new \ReflectionFunction(self::PHP86_DEPRECATED_INTERNAL_FUNCTION);

$this->assertTrue($nativeFunction->isDeprecated());
$arguments = $this->getDeprecatedAttributeArguments($nativeFunction);
$this->assertSame('8.6', $arguments['since']);
$this->assertSame('use Collator::compare() instead', $arguments['message']);
$this->assertStringContainsString('deprecated', $nativeFunction->__toString());

$deprecated = $nativeFunction->getAttributes(\Deprecated::class)[0]->newInstance();
$this->assertInstanceOf(\Deprecated::class, $deprecated);
$this->assertSame('8.6', $deprecated->since);
}

public function testInternalFunctionDeprecatedInPhp86IsNotDeprecatedBefore(): void
{
if (PHP_VERSION_ID >= 80600) {
$this->markTestSkipped('Internal function strcoll() is deprecated since PHP 8.6');
}

$nativeFunction = new \ReflectionFunction(self::PHP86_DEPRECATED_INTERNAL_FUNCTION);

$this->assertFalse($nativeFunction->isDeprecated());
$this->assertSame([], $nativeFunction->getAttributes(\Deprecated::class));
$this->assertStringNotContainsString('deprecated', $nativeFunction->__toString());
}

/**
* Returns the arguments of the single #[\Deprecated] attribute of the given function
*
* @return array<string, string>
*/
private function getDeprecatedAttributeArguments(\ReflectionFunction $function): array
{
$functionName = $function->getName();
$attributes = $function->getAttributes(\Deprecated::class);
$this->assertCount(1, $attributes, "Function {$functionName}() should have one #[\\Deprecated] attribute");
$this->assertSame(\Deprecated::class, $attributes[0]->getName());

/** @var array<string, string> $arguments */
$arguments = $attributes[0]->getArguments();
$this->assertArrayHasKey('since', $arguments, "Function {$functionName}() should report the deprecation version");

return $arguments;
}

/**
* Returns a locator that resolves the stub class without asking composer for it
*/
Expand Down