packages/deriver/tests/Unit/Deriver/Evaluator/Enumeration/Finite/DemandTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Enumeration\Finite;
6
7use Deriver\Deriver\Evaluator\Call\Evaluators;
8use Deriver\Deriver\Evaluator\Enumeration\Choices;
9use Deriver\Deriver\Evaluator\Enumeration\Finite\Demand;
10use Deriver\Deriver\Evaluator\Evaluator;
11use Deriver\Deriver\Evaluator\EvaluatorConfig;
12use Deriver\Deriver\Evaluator\Evidence\Provenance;
13use Deriver\Deriver\Evaluator\Index\DefinitionIndex;
14use Deriver\Deriver\Evaluator\Request\Budget;
15use Deriver\Deriver\Evaluator\Scope\Context;
16use Deriver\Deriver\Evaluator\Term;
17use Deriver\Deriver\Evaluator\TermKind;
18use Deriver\Deriver\Lowerer\Declaration\Index;
19use Deriver\Deriver\Parser\Parser;
20use Deriver\Deriver\Parser\ParserConfig;
21use Deriver\Deriver\Resolver\Resolver;
22use Deriver\Deriver\Sources\Source;
23use PHPUnit\Framework\Attributes\CoversNothing;
24use PHPUnit\Framework\TestCase;
25
26#[CoversNothing]
27final class DemandTest extends TestCase
28{
29    public function testDemandKeepsRemainingMembersInItsCacheIdentity(): void
30    {
31        $all = Demand::truth();
32        $one = Demand::truth(1);
33        self::assertNotSame($all->identity, $one->identity);
34        self::assertSame(['b:0' => true, 'b:1' => true], $all->remaining);
35    }
36
37    public function testTruthUsesOnlyTheRequestedBooleanMembers(): void
38    {
39        self::assertSame(['b:1' => true], Demand::truth(2)->remaining);
40        self::assertSame('b:0', Demand::truth()->key(Term::constant('0')));
41        self::assertSame('b:1', Demand::truth()->key(Term::constant('yes')));
42    }
43
44    public function testCollectDoesNotOpenAlternativesAfterCoverage(): void
45    {
46        $opened = 0;
47        $value = Demand::truth()->collect([
48            static fn (): Term => Term::constant(0),
49            static fn (): Term => Term::constant(7),
50            static function () use (&$opened): Term {
51                ++$opened;
52                return Term::constant(9);
53            },
54        ], new Provenance());
55        self::assertSame([0,7], array_map(static fn (array $row) => $row[0]->native(), iterator_to_array((new Choices())->alternatives($value))));
56        self::assertSame(0, $opened);
57    }
58
59    public function testCollectRetainsObservedFailuresAfterCoverage(): void
60    {
61        $failure = new Term(TermKind::Throwable, 'TypeError');
62        $value = Demand::truth()->collect([
63            static fn (): Term => $failure,
64            static fn (): Term => Term::constant(false),
65            static fn (): Term => Term::constant(true),
66        ], new Provenance());
67        $values = array_map(static fn (array $row): Term => $row[0], iterator_to_array((new Choices())->alternatives($value)));
68        self::assertContains($failure, $values);
69        self::assertCount(3, $values);
70    }
71
72    public function testKeyDoesNotInventAValueFromItsDeclaredType(): void
73    {
74        self::assertNull(Demand::truth()->key(Term::parameter('x', 'bool')));
75    }
76
77    public function testIdentityDistinguishesEnumCasesWithEqualBackingValues(): void
78    {
79        $first = new Term(TermKind::Enum, 'First::A');
80        $second = new Term(TermKind::Enum, 'Second::A');
81        self::assertNotSame(Demand::identity($first), Demand::identity($second));
82        self::assertNull(Demand::identity(Term::parameter('x')));
83    }
84
85    public function testThroughChecksStrictDeclarationsBeforeCountingWitnesses(): void
86    {
87        $source = 'function subject(){return 1;}';
88        $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.3')))->parse(new Source('a.php', '<?php ' . $source)));
89        $index = new DefinitionIndex(new Index('s', [$tree]));
90        $config = new EvaluatorConfig();
91        $engine = new Evaluator(new Context($index, $config, new Evaluators($config->target), new Budget()));
92        $strict = Demand::truth()->through($engine, 'bool', true);
93        $weak = Demand::truth()->through($engine, 'bool', false);
94        self::assertNull($strict->key(Term::constant(1)));
95        self::assertSame('b:1', $weak->key(Term::constant(1)));
96        $invalid = Term::constant(1);
97        $candidates = $strict->collect([
98            static fn (): Term => $invalid,
99            static fn (): Term => Term::constant(false),
100            static fn (): Term => Term::constant(true),
101        ], $engine->context->provenance);
102        self::assertContains($invalid, array_map(static fn (array $row): Term => $row[0], iterator_to_array((new Choices())->alternatives($candidates))));
103    }
104}
105