Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ReflectionParameter: try to get the correct default value for global constants #678

Merged
merged 1 commit into from
Jul 10, 2020

Conversation

voku
Copy link
Contributor

@voku voku commented Jul 7, 2020

For example, this function function foo(int $case = \CASE_LOWER);, ReflectionParameter->allowsNull() for $casewill currently return true.


This change is Reviewable

@@ -187,7 +188,7 @@ private function parseDefaultValueNode(): void
) {
$this->isDefaultValueConstant = true;
$this->defaultValueConstantName = $defaultValueNode->name->parts[0];
$this->defaultValue = null;
$this->defaultValue = constant($this->defaultValueConstantName);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not really acceptable, since constant() leads to autoloading here:

https://3v4l.org/BeYeT

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ocramius Node\Expr\ConstFetch is only for global constants not for class constants, instead of Node\Expr\ClassConstFetch, or? If this is correct then there is no autoloading here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but then it will crash in case of an undefined constant: https://3v4l.org/ZZnmJ

Looking at our code, we do indeed use constant() elsewhere (in Roave\BetterReflection\NodeCompiler\CompileNodeToValue)

I'd say that this patch is good if following is true:

  1. tests are added
  2. CompileNodeToValue is used in here, instead of constant()

Having a constant() lookup there is already a bug: let's keep it isolated for now.

Copy link
Contributor Author

@voku voku Jul 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ocramius constant() is replaced and a simple test case is added

But now I see Roave\BetterReflection\NodeCompiler\Exception\UnableToCompileNode : Could not locate constant "SOME_DEFINED_VALUE" ... in a different test.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test that fails is this one:

    public function testIsDefaultValueConstantAndGetDefaultValueConstantName() : void
    {
        $classInfo = $this->reflector->reflect(Methods::class);
        $method    = $classInfo->getMethod('methodWithUpperCasedDefaults');

        $boolUpper = $method->getParameter('boolUpper');
        self::assertFalse($boolUpper->isDefaultValueConstant());

        $boolLower = $method->getParameter('boolLower');
        self::assertFalse($boolLower->isDefaultValueConstant());

        $nullUpper = $method->getParameter('nullUpper');
        self::assertFalse($nullUpper->isDefaultValueConstant());

        $method       = $classInfo->getMethod('methodWithConstAsDefault');
        $constDefault = $method->getParameter('constDefault');
        self::assertTrue($constDefault->isDefaultValueConstant());
        self::assertSame(Methods::class . '::SOME_CONST', $constDefault->getDefaultValueConstantName());

        $definedDefault = $method->getParameter('definedDefault');
        self::assertTrue($definedDefault->isDefaultValueConstant());
        self::assertSame('SOME_DEFINED_VALUE', $definedDefault->getDefaultValueConstantName());

        $intDefault = $method->getParameter('intDefault');
        self::assertFalse($intDefault->isDefaultValueConstant());

        $this->expectException(LogicException::class);
        $this->expectExceptionMessage('This parameter is not a constant default value, so cannot have a constant name');
        $intDefault->getDefaultValueConstantName();
    }

The source is Methods.php:

<?php

namespace Roave\BetterReflectionTest\Fixture;

\define('SOME_DEFINED_VALUE', 1);

abstract class Methods
{
    const SOME_CONST = 1;

    public function __construct()
    {
    }

    public function publicMethod()
    {
    }

    private function privateMethod()
    {
    }

    protected function protectedMethod()
    {
    }

    final public function finalPublicMethod()
    {
    }

    abstract public function abstractPublicMethod();

    public static function staticPublicMethod()
    {
    }

    function noVisibility()
    {
    }

    public function __destruct()
    {
    }

    /**
     * @param string $parameter1
     * @param int|float $parameter2
     */
    public function methodWithParameters($parameter1, $parameter2)
    {
    }

    public function methodWithOptionalParameters($parameter, $optionalParameter = null)
    {
    }

    public function methodWithExplicitTypedParameters(
        \stdClass $stdClassParameter,
        ClassForHinting $namespaceClassParameter,
        \Roave\BetterReflectionTest\Fixture\ClassForHinting $fullyQualifiedClassParameter,
        array $arrayParameter,
        callable $callableParameter
    ) {
    }

    public function methodWithVariadic($nonVariadicParameter, ...$variadicParameter)
    {
    }

    public function methodWithReference($nonRefParameter, &$refParameter)
    {
    }

    public function methodWithNonOptionalDefaultValue($firstParameter = 'someValue', $secondParameter)
    {
    }

    public function methodToCheckAllowsNull($allowsNull, \stdClass $hintDisallowNull, \stdClass $hintAllowNull = null)
    {
    }

    public function methodWithConstAsDefault($intDefault = 1, $constDefault = self::SOME_CONST, $definedDefault = SOME_DEFINED_VALUE)
    {
    }

    public function methodWithUpperCasedDefaults($boolUpper = TRUE, $boolLower = false, $nullUpper = NULL)
    {
    }
}

I wonder if we can make the default value "lazy", so that we only trip the exception if a read on the constant is happening. The test above is indeed correct: the Methods.php file should never be included, and therefore SOME_DEFINED_VALUE cannot be found by constant().

voku added a commit to voku/Simple-PHP-Code-Parser that referenced this pull request Jul 8, 2020
@@ -449,7 +466,10 @@ public function isDefaultValueConstant(): bool
*/
public function getDefaultValueConstantName(): string
{
$this->parseDefaultValueNode();
if ($this->defaultValueAst === null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these AST caches necessary? I would suggest avoiding them, if they don't bring a massive advantage (the added complexity is noticeable)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remvoed the AST cache.

Copy link
Member

@Ocramius Ocramius left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think CS is needed (run vendor/bin/phpcbf), and maybe we should remove some conditionals that aren't strictly necessary for the patch.

Copy link
Member

@Ocramius Ocramius left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Some wonkiness in the tests, but I'm OK with ignoring CS for those couple rows :-)

Good job!

@Ocramius Ocramius added this to the 4.9.0 milestone Jul 10, 2020
@Ocramius Ocramius self-assigned this Jul 10, 2020
@Ocramius Ocramius merged commit 6d1bd57 into Roave:master Jul 10, 2020
@Ocramius Ocramius changed the title ReflectionParameter: try to get the correct default value for constants ReflectionParameter: try to get the correct default value for global constants Jul 10, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants