Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 32 additions & 12 deletions src/main/php/lang/ast/syntax/php/IsOperator.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -284,31 +284,37 @@ public function setup($language, $emitter) {
}
return $compound;
} else if ($pattern instanceof IsArrayStructure) {
$compound= new BinaryExpression(
new InvokeExpression(new Literal('is_array'), [$init]),
'&&',
new BinaryExpression(
new InvokeExpression(new Literal('sizeof'), [$use]),
$pattern->rest ? '>=' : '===',
new Literal((string)sizeof($pattern->patterns))
)
$size= new BinaryExpression(
new InvokeExpression(new Literal('sizeof'), [$use]),
$pattern->rest ? '>=' : '===',
new Literal((string)sizeof($pattern->patterns))
);
$arrays= new BinaryExpression(new InvokeExpression(new Literal('is_array'), [$init]), '&&', $size);
$objects= new BinaryExpression(new InstanceOfExpression($init, new IsValue('ArrayAccess')), '&&', $size);

$matched= new ArrayLiteral([]);
$null= new Literal('null');
foreach ($pattern->patterns as $key => $p) {
$offset= new Literal((string)$key);
$matched->values[]= [$offset, $null];
$compound= new BinaryExpression($compound, '&&', new BinaryExpression(

$apply= $match($codegen, new OffsetExpression($use, $offset), $p);
$arrays= new BinaryExpression($arrays, '&&', new BinaryExpression(
new InvokeExpression(new Literal('array_key_exists'), [$offset, $use]),
'&&',
$match($codegen, new OffsetExpression($use, $offset), $p),
$apply,
));
$objects= new BinaryExpression($objects, '&&', new BinaryExpression(
new InvokeExpression(new InstanceExpression($use, new Literal('offsetExists')), [$offset]),
'&&',
$apply,
));
}

// array_diff_key() removes entries for keys in the second argument,
// unpacking re-keys lists but keeps map key-value pairs intact.
if ($pattern->rest instanceof Variable) {
$compound= new BinaryExpression($compound, '&&', new Braced(new BinaryExpression(
$arrays= new BinaryExpression($arrays, '&&', new Braced(new BinaryExpression(
new Braced(new Assignment($pattern->rest, '=', $pattern->patterns
? new ArrayLiteral([[
null,
Expand All @@ -319,9 +325,23 @@ public function setup($language, $emitter) {
'||',
new Literal('true')
)));
$objects= new BinaryExpression($objects, '&&', new Braced(new BinaryExpression(
new Braced(new Assignment($pattern->rest, '=', $pattern->patterns
? new ArrayLiteral([[
null,
new UnpackExpression(new InvokeExpression(new Literal('array_diff_key'), [
new ArrayLiteral([[null, new UnpackExpression($use)]]),
$matched
]))
]])
: new ArrayLiteral([[null, new UnpackExpression($use)]])
)),
'||',
new Literal('true')
)));
}

return $compound;
return new Braced(new BinaryExpression($arrays, '||', $objects));
}

// Should be unreachable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php namespace lang\ast\syntax\php\unittest;

use lang\ast\unittest\emit\EmittingTest;
use test\{Assert, Test, Values};

class ArrayAccessTest extends EmittingTest {

/** @return iterable */
private function fixtures() {
yield ['$this is [$a, $b] ? [$a, $b] : null', null];
yield ['$this is [$a, $b, true] ? [$a, $b] : null', null];
yield ['$this is [$a, $b, $c] ? [$a, $b, $c] : null', [1, 2, 3]];

yield ['$this is [...$rest] ? $rest : null', [1, 2, 3]];
yield ['$this is [$a, $b, ...] ? [$a, $b] : null', [1, 2]];
yield ['$this is [$a, ...$rest] ? [$a, $rest] : null', [1, [2, 3]]];
yield ['$this is [$a, $b, $c, ...$rest] ? [$a, $b, $c, $rest] : null', [1, 2, 3, []]];
}

#[Test, Values(from: 'fixtures')]
public function integrates_with_array_access($expr, $expected) {
Assert::equals($expected, $this->run('class %T implements ArrayAccess, IteratorAggregate, Countable {

public function count(): int { return 3; }

public function offsetExists($i): bool { return true; }

public function offsetGet($i): mixed { return $i + 1; }

public function offsetSet($i, $value): void { /* NOOP */ }

public function offsetUnset($i): void { /* NOOP */ }

public function getIterator(): Traversable { yield from [1, 2, 3]; }

public function run() {
return '.$expr.';
}
}'));
}
}
Loading