packages/deriver/tests/Unit/Deriver/Evaluator/Call/Plan/PlanInvocationsTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Call\Plan;
6
7use Deriver\Deriver\Evaluator\Call\Plan\Action;
8use Deriver\Deriver\Evaluator\Call\Plan\CallArgument;
9use Deriver\Deriver\Evaluator\Call\Plan\Expression;
10use Deriver\Deriver\Evaluator\Call\Plan\Location\LocationRef;
11use Deriver\Deriver\Evaluator\Call\Plan\PlanCompiler;
12use Deriver\Deriver\Evaluator\Call\Plan\PlanInvocations;
13use Deriver\Deriver\Evaluator\Term;
14use Deriver\Deriver\Sources\SourceRef;
15use PHPUnit\Framework\Attributes\CoversClass;
16use PHPUnit\Framework\Attributes\DataProvider;
17use PHPUnit\Framework\Attributes\Small;
18use PHPUnit\Framework\Attributes\UsesClass;
19use PHPUnit\Framework\TestCase;
20
21/**
22 * @covers \Deriver\Deriver\Evaluator\Call\Plan\PlanInvocations
23 */
24#[CoversClass(PlanInvocations::class)]
25#[UsesClass(\Deriver\Deriver\Lowerer\Graph\Argument::class)]
26#[UsesClass(\Deriver\Deriver\Lowerer\Graph\Instruction::class)]
27#[UsesClass(LocationRef::class)]
28#[UsesClass(PlanCompiler::class)]
29#[UsesClass(\Deriver\Deriver\Evaluator\Call\Plan\PlanLocations::class)]
30#[UsesClass(Action::class)]
31#[UsesClass(CallArgument::class)]
32#[UsesClass(Expression::class)]
33#[UsesClass(SourceRef::class)]
34#[UsesClass(Term::class)]
35#[Small]
36final class PlanInvocationsTest extends TestCase
37{
38    public function testApplyPreparesCallBeforeItsArguments(): void
39    {
40        $compiler = new PlanCompiler(new SourceRef('test', 'evaluator:test', 0, 1));
41        (new PlanInvocations($compiler))->apply(Action::invoke('@result', Expression::literal(Term::constant('consume')), [new CallArgument(LocationRef::parameter('value'))], true));
42        self::assertSame(['constant', 'call-prepare', 'local', 'argument', 'invoke', 'local', 'returned-address', 'alias'], array_column($compiler->instructions[0], 'operation'));
43        self::assertTrue($compiler->instructions[0][3]->attributes['address']);
44    }
45    public function testArgumentsPreservesNamedUnpackAndLocationMetadata(): void
46    {
47        $compiler = new PlanCompiler(new SourceRef('test', 'evaluator:test', 0, 1));
48        $arguments = (new PlanInvocations($compiler))->arguments([new CallArgument(LocationRef::parameter('items'), unpack: true)], 'prepared');
49        self::assertTrue($arguments[0]->unpack);
50        self::assertSame($arguments[0]->register, $arguments[0]->location);
51        self::assertTrue($compiler->instructions[0][1]->attributes['unpack-variable']);
52    }
53
54    /**
55     * @param list<string> $operations Expected argument evaluation order
56     */
57    #[DataProvider('providerArgumentLocations')]
58    public function testArgumentsDistinguishesAddressableStorageFromComputedValues(Expression|LocationRef $input, array $operations, bool $address, bool $unpackVariable): void
59    {
60        $compiler = new PlanCompiler(new SourceRef('snapshot', 'evaluator:arguments', 0, 4));
61        $arguments = (new PlanInvocations($compiler))->arguments([new CallArgument($input, 'items', true)], 'signature');
62        $instructions = $compiler->instructions[0];
63        $instruction = $instructions[count($instructions) - 1];
64        self::assertSame($operations, array_column($instructions, 'operation'));
65        self::assertSame(['signature',$instructions[count($instructions) - 2]->result], $instruction->operands);
66        self::assertSame(['address' => $address,'argument-name' => 'items','unpack' => true,'unpack-variable' => $unpackVariable,'temporary' => false], $instruction->attributes);
67        self::assertSame([], $instruction->arguments);
68        self::assertCount(1, $arguments);
69        self::assertSame($instruction->result, $arguments[0]->register);
70        self::assertSame($instruction->result, $arguments[0]->location);
71        self::assertSame('items', $arguments[0]->name);
72        self::assertTrue($arguments[0]->unpack);
73    }
74
75    /**
76     * @return iterable<string,array{Expression|LocationRef,list<string>,bool,bool}>
77     */
78    public static function providerArgumentLocations(): iterable
79    {
80        yield 'parameter location' => [LocationRef::parameter('value'),['local','argument'],true,true];
81        yield 'parameter expression' => [Expression::parameter('value'),['local','argument'],true,true];
82        yield 'state expression' => [Expression::state('cache.value'),['local','read','evaluator-state-address','argument'],true,false];
83        yield 'state location' => [LocationRef::state('cache.value'),['local','read','evaluator-state-address','argument'],true,false];
84        yield 'element location' => [LocationRef::element(LocationRef::parameter('value'), Expression::literal(Term::constant('key'))),['local','constant','element-address','argument'],true,false];
85        yield 'explicit read' => [Expression::read(LocationRef::parameter('value')),['local','argument'],true,true];
86        yield 'computed value' => [Expression::literal(Term::constant(4)),['constant','argument'],false,false];
87    }
88
89    public function testArgumentsSuppliesEarlierBindingsToEachLaterArgument(): void
90    {
91        $compiler = new PlanCompiler(new SourceRef('snapshot', 'evaluator:arguments', 0, 4));
92        $arguments = (new PlanInvocations($compiler))->arguments([new CallArgument(Expression::literal(Term::constant(1))),new CallArgument(Expression::parameter('second'), 'other')], 'signature');
93        self::assertCount(2, $arguments);
94        self::assertSame([], $compiler->instructions[0][1]->arguments);
95        self::assertSame([$arguments[0]], $compiler->instructions[0][3]->arguments);
96        self::assertSame(['signature','m2'], $compiler->instructions[0][3]->operands);
97        self::assertSame('other', $arguments[1]->name);
98        self::assertFalse($arguments[1]->unpack);
99        self::assertSame('m3', $arguments[1]->location);
100    }
101
102    public function testApplyPreservesCallbackArgumentOrderAndResultBinding(): void
103    {
104        $compiler = new PlanCompiler(new SourceRef('snapshot', 'evaluator:callback', 0, 4));
105        $target = Term::constant('callback');
106        (new PlanInvocations($compiler))->apply(Action::callback('result', Expression::literal($target), [Expression::literal(Term::constant(11)),Expression::literal(Term::constant(29))]));
107        $instructions = $compiler->instructions[0];
108        self::assertSame(['constant','call-prepare','constant','argument','constant','argument','invoke','local','write'], array_column($instructions, 'operation'));
109        self::assertSame($target, (new \Deriver\Deriver\Evaluator\Expression\LiteralEvaluator())->instruction($instructions[0]));
110        self::assertSame(11, (new \Deriver\Deriver\Evaluator\Expression\LiteralEvaluator())->instruction($instructions[2])->literal);
111        self::assertSame(29, (new \Deriver\Deriver\Evaluator\Expression\LiteralEvaluator())->instruction($instructions[4])->literal);
112        self::assertSame(['m0'], $instructions[6]->operands);
113        self::assertSame(['m3','m5'], array_column($instructions[6]->arguments, 'register'));
114        self::assertSame(['prepared' => 'm1'], $instructions[6]->attributes);
115        self::assertSame('result', $instructions[7]->name);
116        self::assertSame(['m7','m6'], $instructions[8]->operands);
117    }
118
119    public function testApplyRetainsTheReturnedCellWhenAReferenceResultIsRequested(): void
120    {
121        $compiler = new PlanCompiler(new SourceRef('snapshot', 'evaluator:reference', 0, 4));
122        (new PlanInvocations($compiler))->apply(Action::invoke('alias', Expression::literal(Term::constant('byReference')), byReference:true));
123        $instructions = $compiler->instructions[0];
124        self::assertSame(['constant','call-prepare','invoke','local','returned-address','alias'], array_column($instructions, 'operation'));
125        self::assertSame(['m2'], $instructions[4]->operands);
126        self::assertSame(['m3','m4'], $instructions[5]->operands);
127        self::assertSame('alias', $instructions[3]->name);
128    }
129}
130