Test Failed
Push — master ( 33bfdc...47f867 )
by Kirill
02:32
created

TypeInvocationBuilder::fetchArgumentName()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 2
dl 0
loc 21
ccs 0
cts 0
cp 0
crap 12
rs 9.584
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of Railt package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
declare(strict_types=1);
9
10
namespace Railt\SDL\Frontend\Builder\Value;
11
12
use Railt\Parser\Ast\RuleInterface;
13
use Railt\SDL\Exception\TypeConflictException;
14
use Railt\SDL\Frontend\Builder\BaseBuilder;
15
use Railt\SDL\Frontend\Context\ContextInterface;
16
use Railt\SDL\Frontend\Invocation\InvocationPrimitive;
17
use Railt\SDL\IR\SymbolTable\Value;
18
use Railt\SDL\IR\SymbolTable\ValueInterface;
19
use Railt\SDL\IR\Type;
20
use Railt\SDL\IR\Type\Name;
21
use Railt\SDL\IR\Type\TypeNameInterface;
22
23
/**
24
 * Class TypeInvocationBuilder
25
 */
26
class TypeInvocationBuilder extends BaseBuilder
27
{
28
    /**
29
     * @param RuleInterface $rule
30
     * @return bool
31
     */
32
    public function match(RuleInterface $rule): bool
33
    {
34
        return $rule->getName() === 'TypeInvocation';
35
    }
36
37
    /**
38
     * @param ContextInterface $ctx
39
     * @param RuleInterface $rule
40
     * @return \Generator|ValueInterface
41
     * @throws TypeConflictException
42
     */
43
    public function reduce(ContextInterface $ctx, RuleInterface $rule): \Generator
44
    {
45
        /** @var ValueInterface $value */
46
        $value = yield $rule->first('> #GenericInvocationName')->getChild(0);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Railt\Parser\Ast\NodeInterface as the method getChild() does only exist in the following implementations of said interface: Railt\Compiler\Grammar\Delegate\IncludeDelegate, Railt\Compiler\Grammar\Delegate\RuleDelegate, Railt\Compiler\Grammar\Delegate\TokenDelegate, Railt\Parser\Ast\Rule, Railt\SDL\Frontend\AST\Value\AbstractAstValueNode, Railt\SDL\Frontend\AST\Value\ConstantValueNode, Railt\SDL\Frontend\AST\Value\NullValueNode, Railt\SDL\Frontend\AST\Value\NumberValueNode, Railt\SDL\Frontend\AST\Value\StringValueNode.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
47
48
        $primitive = clone $this->resolveName($value);
49
50
        foreach ($rule->find('> #GenericInvocationArgument') as $argument) {
51
            yield from $n = $this->fetchArgumentName($ctx, $argument);
52
            yield from $v = $this->fetchArgumentValue($ctx, $argument);
53
54
            /** @var TypeNameInterface $argumentName */
55
            $argumentName = $n->getReturn();
56
57
            /** @var ValueInterface $argumentValue */
58
            $argumentValue = $v->getReturn();
59
60
            $primitive->addArgument($argumentName->getFullyQualifiedName(), $argumentValue->getValue());
61
        }
62
63
        return new Value($primitive, Type::type());
64
    }
65
66
    /**
67
     * @param ContextInterface $ctx
68
     * @param RuleInterface $rule
69
     * @return \Generator|TypeNameInterface
70
     * @throws TypeConflictException
71
     */
72
    private function fetchArgumentName(ContextInterface $ctx, RuleInterface $rule): \Generator
73
    {
74
        /** @var TypeNameInterface|ValueInterface $name */
75
        $name = yield $rule->first('> #GenericInvocationArgumentName')->getChild(0);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Railt\Parser\Ast\NodeInterface as the method getChild() does only exist in the following implementations of said interface: Railt\Compiler\Grammar\Delegate\IncludeDelegate, Railt\Compiler\Grammar\Delegate\RuleDelegate, Railt\Compiler\Grammar\Delegate\TokenDelegate, Railt\Parser\Ast\Rule, Railt\SDL\Frontend\AST\Value\AbstractAstValueNode, Railt\SDL\Frontend\AST\Value\ConstantValueNode, Railt\SDL\Frontend\AST\Value\NullValueNode, Railt\SDL\Frontend\AST\Value\NumberValueNode, Railt\SDL\Frontend\AST\Value\StringValueNode.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
76
77
        if ($name instanceof TypeNameInterface) {
78
            return $name;
79
        }
80
81
        $isConst = $name->getType()->typeOf(Type::const());
0 ignored issues
show
Documentation introduced by
\Railt\SDL\IR\Type::const() is of type object<Railt\SDL\IR\Type\TypeInterface>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
82
83
        if (! $isConst) {
84
            $error = 'Generic argument name must be a const but %s given';
85
            $exception = new TypeConflictException(\sprintf($error, $name));
86
            $exception->throwsIn($ctx->getFile(), $rule->getOffset());
87
88
            throw $exception;
89
        }
90
91
        return Name::fromString((string)$name->getValue());
92
    }
93
94
    /**
95
     * @param ContextInterface $ctx
96
     * @param RuleInterface $rule
97
     * @return \Generator|ValueInterface
98
     */
99
    private function fetchArgumentValue(ContextInterface $ctx, RuleInterface $rule): \Generator
100
    {
101
        /** @var ValueInterface $value */
102
        $value = yield $rule->first('> #GenericInvocationArgumentValue')->getChild(0);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Railt\Parser\Ast\NodeInterface as the method getChild() does only exist in the following implementations of said interface: Railt\Compiler\Grammar\Delegate\IncludeDelegate, Railt\Compiler\Grammar\Delegate\RuleDelegate, Railt\Compiler\Grammar\Delegate\TokenDelegate, Railt\Parser\Ast\Rule, Railt\SDL\Frontend\AST\Value\AbstractAstValueNode, Railt\SDL\Frontend\AST\Value\ConstantValueNode, Railt\SDL\Frontend\AST\Value\NullValueNode, Railt\SDL\Frontend\AST\Value\NumberValueNode, Railt\SDL\Frontend\AST\Value\StringValueNode.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
103
104
        $isType = $value->getType()->typeOf(Type::type());
0 ignored issues
show
Documentation introduced by
\Railt\SDL\IR\Type::type() is of type object<Railt\SDL\IR\Type\TypeInterface>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
105
106
        if (! $isType) {
107
            $error = 'Generic argument value must be a valid type, but %s given';
108
            $exception = new TypeConflictException(\sprintf($error, $value));
109
            $exception->throwsIn($ctx->getFile(), $rule->getOffset());
110
        }
111
112
        return $value;
113
    }
114
115
    /**
116
     * @param TypeNameInterface|ValueInterface $name
117
     * @return InvocationPrimitive
118
     * @throws TypeConflictException
119
     */
120
    private function resolveName($name): InvocationPrimitive
121
    {
122
        if ($name instanceof TypeNameInterface) {
123
            return new InvocationPrimitive($name);
124
        }
125
126
        $isConst = $name->getType()->typeOf(Type::const());
0 ignored issues
show
Documentation introduced by
\Railt\SDL\IR\Type::const() is of type object<Railt\SDL\IR\Type\TypeInterface>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
127
        $isType = $name->getType()->typeOf(Type::type());
0 ignored issues
show
Documentation introduced by
\Railt\SDL\IR\Type::type() is of type object<Railt\SDL\IR\Type\TypeInterface>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
128
129
        switch (true) {
130
            case $isConst:
131
                return new InvocationPrimitive(Name::fromString((string)$name->getValue()));
132
133
            case $isType:
134
                /** @var InvocationPrimitive $value */
135
                return $name->getValue();
136
        }
137
138
        $error = 'Type name should be valid type name, but %s given';
139
        throw new TypeConflictException(\sprintf($error, $name));
140
    }
141
}
142