1<?php declare(strict_types=1);
2
3namespace PhpParser;
4
5use PhpParser\Node\Expr;
6use PhpParser\Node\Scalar;
7
8class ConstExprEvaluatorTest extends \PHPUnit\Framework\TestCase {
9    /** @dataProvider provideTestEvaluate */
10    public function testEvaluate($exprString, $expected): void {
11        $parser = new Parser\Php7(new Lexer());
12        $expr = $parser->parse('<?php ' . $exprString . ';')[0]->expr;
13        $evaluator = new ConstExprEvaluator();
14        $this->assertSame($expected, $evaluator->evaluateDirectly($expr));
15    }
16
17    public static function provideTestEvaluate() {
18        return [
19            ['1', 1],
20            ['1.0', 1.0],
21            ['"foo"', "foo"],
22            ['[0, 1]', [0, 1]],
23            ['["foo" => "bar"]', ["foo" => "bar"]],
24            ['[...["bar"]]', ["bar"]],
25            ['[...["foo" => "bar"]]', ["foo" => "bar"]],
26            ['["a", "b" => "b", ...["b" => "bb", "c"]]', ["a", "b" => "bb", "c"]],
27            ['NULL', null],
28            ['False', false],
29            ['true', true],
30            ['+1', 1],
31            ['-1', -1],
32            ['~0', -1],
33            ['!true', false],
34            ['[0][0]', 0],
35            ['"a"[0]', "a"],
36            ['true ? 1 : (1/0)', 1],
37            ['false ? (1/0) : 1', 1],
38            ['42 ?: (1/0)', 42],
39            ['false ?: 42', 42],
40            ['false ?? 42', false],
41            ['null ?? 42', 42],
42            ['[0][0] ?? 42', 0],
43            ['[][0] ?? 42', 42],
44            ['0b11 & 0b10', 0b10],
45            ['0b11 | 0b10', 0b11],
46            ['0b11 ^ 0b10', 0b01],
47            ['1 << 2', 4],
48            ['4 >> 2', 1],
49            ['"a" . "b"', "ab"],
50            ['4 + 2', 6],
51            ['4 - 2', 2],
52            ['4 * 2', 8],
53            ['4 / 2', 2],
54            ['4 % 2', 0],
55            ['4 ** 2', 16],
56            ['1 == 1.0', true],
57            ['1 != 1.0', false],
58            ['1 < 2.0', true],
59            ['1 <= 2.0', true],
60            ['1 > 2.0', false],
61            ['1 >= 2.0', false],
62            ['1 <=> 2.0', -1],
63            ['1 === 1.0', false],
64            ['1 !== 1.0', true],
65            ['true && true', true],
66            ['true and true', true],
67            ['false && (1/0)', false],
68            ['false and (1/0)', false],
69            ['false || false', false],
70            ['false or false', false],
71            ['true || (1/0)', true],
72            ['true or (1/0)', true],
73            ['true xor false', true],
74        ];
75    }
76
77    public function testEvaluateFails(): void {
78        $this->expectException(ConstExprEvaluationException::class);
79        $this->expectExceptionMessage('Expression of type Expr_Variable cannot be evaluated');
80        $evaluator = new ConstExprEvaluator();
81        $evaluator->evaluateDirectly(new Expr\Variable('a'));
82    }
83
84    public function testEvaluateFallback(): void {
85        $evaluator = new ConstExprEvaluator(function (Expr $expr) {
86            if ($expr instanceof Scalar\MagicConst\Line) {
87                return 42;
88            }
89            throw new ConstExprEvaluationException();
90        });
91        $expr = new Expr\BinaryOp\Plus(
92            new Scalar\Int_(8),
93            new Scalar\MagicConst\Line()
94        );
95        $this->assertSame(50, $evaluator->evaluateDirectly($expr));
96    }
97
98    /**
99     * @dataProvider provideTestEvaluateSilently
100     */
101    public function testEvaluateSilently($expr, $exception, $msg): void {
102        $evaluator = new ConstExprEvaluator();
103
104        try {
105            $evaluator->evaluateSilently($expr);
106        } catch (ConstExprEvaluationException $e) {
107            $this->assertSame(
108                'An error occurred during constant expression evaluation',
109                $e->getMessage()
110            );
111
112            $prev = $e->getPrevious();
113            $this->assertInstanceOf($exception, $prev);
114            $this->assertSame($msg, $prev->getMessage());
115        }
116    }
117
118    public static function provideTestEvaluateSilently() {
119        return [
120            [
121                new Expr\BinaryOp\Mod(new Scalar\Int_(42), new Scalar\Int_(0)),
122                \Error::class,
123                'Modulo by zero'
124            ],
125            [
126                new Expr\BinaryOp\Plus(new Scalar\Int_(42), new Scalar\String_("1foo")),
127                \ErrorException::class,
128                \PHP_VERSION_ID >= 80000
129                    ? 'A non-numeric value encountered'
130                    : 'A non well formed numeric value encountered'
131            ],
132        ];
133    }
134}
135