Skip to content

Instantly share code, notes, and snippets.

@thekid
Created December 27, 2025 16:34
Show Gist options
  • Select an option

  • Save thekid/97646f250ac2868fb98052019fe8973d to your computer and use it in GitHub Desktop.

Select an option

Save thekid/97646f250ac2868fb98052019fe8973d to your computer and use it in GitHub Desktop.
Is operator with binding defaults using ??
diff --git a/src/main/php/lang/ast/syntax/php/IsOperator.class.php b/src/main/php/lang/ast/syntax/php/IsOperator.class.php
index 7e34fc1..ca7f336 100755
--- a/src/main/php/lang/ast/syntax/php/IsOperator.class.php
+++ b/src/main/php/lang/ast/syntax/php/IsOperator.class.php
@@ -14,6 +14,7 @@ use lang\ast\nodes\{
MatchExpression,
OffsetExpression,
ScopeExpression,
+ TernaryExpression,
Variable
};
use lang\ast\syntax\Extension;
@@ -70,6 +71,12 @@ class IsOperator implements Extension {
$parse->forward();
$r->restriction= $pattern($parse, $types);
}
+
+ if ('??' === $parse->token->value) {
+ $parse->forward();
+ return new IsOptional($r, $types->expression($parse, 0));
+ }
+
return $r;
} else if ('>' === $parse->token->value || '>=' === $parse->token->value || '<' === $parse->token->value || '<=' === $parse->token->value) {
$operator= $parse->token->value;
@@ -220,11 +227,11 @@ class IsOperator implements Extension {
new BinaryExpression($use, $pattern->operator, $pattern->value)
);
} else if ($pattern instanceof IsNullable) {
- return new BinaryExpression(
+ return new Braced(new BinaryExpression(
new BinaryExpression(new Literal('null'), '===', $init),
'||',
$match($codegen, $use, $pattern->element)
- );
+ ));
} else if ($pattern instanceof IsCompound) {
$compound= $match($codegen, $init, $pattern->patterns[0]);
for ($i= 1, $s= sizeof($pattern->patterns), $op= $pattern->operator.$pattern->operator; $i < $s; $i++) {
@@ -242,22 +249,40 @@ class IsOperator implements Extension {
}
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))
);
+ $compound= new BinaryExpression(new InvokeExpression(new Literal('is_array'), [$init]), '&&', $size);
+
+ $optional= 0;
foreach ($pattern->patterns as $key => $p) {
$offset= new Literal((string)$key);
- $compound= new BinaryExpression($compound, '&&', new BinaryExpression(
- new InvokeExpression(new Literal('array_key_exists'), [$offset, $use]),
- '&&',
- $match($codegen, new OffsetExpression($use, $offset), $p),
- ));
+ $exists= new InvokeExpression(new Literal('array_key_exists'), [$offset, $use]);
+
+ if ($p instanceof IsOptional) {
+ $test= new Braced(new TernaryExpression(
+ $exists,
+ $match($codegen, new OffsetExpression($use, $offset), $p->binding),
+ $match($codegen, $p->default, $p->binding)
+ ));
+ $optional++;
+ } else {
+ $test= new BinaryExpression(
+ $exists,
+ '&&',
+ $match($codegen, new OffsetExpression($use, $offset), $p),
+ );
+ }
+
+ $compound= new BinaryExpression($compound, '&&', $test);
+ }
+
+ // Optional implies rest
+ if ($optional) {
+ $size->operator= '>=';
+ $size->right->expression= (string)($size->right->expression - $optional);
}
return $compound;
}
diff --git a/src/test/php/lang/ast/syntax/php/unittest/VariableBindingTest.class.php b/src/test/php/lang/ast/syntax/php/unittest/VariableBindingTest.class.php
index 3c02f5e..e811c64 100755
--- a/src/test/php/lang/ast/syntax/php/unittest/VariableBindingTest.class.php
+++ b/src/test/php/lang/ast/syntax/php/unittest/VariableBindingTest.class.php
@@ -36,6 +36,15 @@ class VariableBindingTest extends EmittingTest {
yield ['["one" => 0, "two" => 2] is ["one" => 1, "two" => $t] ? $t : null', null];
}
+ #[Test, Values([[['key' => 1], null], [[], ['matched' => null]], [['key' => 'value'], ['matched' => 'value']]])]
+ public function with_default($input, $expected) {
+ Assert::equals($expected, $this->run('class %T {
+ public function run($input) {
+ return $input is ["key" => $expr & ?string ?? null] ? ["matched" => $expr] : null;
+ }
+ }', $input));
+ }
+
#[Test, Values(from: 'fixtures')]
public function test($expr, $expected) {
Assert::equals($expected, $this->run('use lang\\Value, lang\\ast\\syntax\\php\\unittest\\Point; class %T {
<?php namespace lang\ast\syntax\php;
use lang\ast\Type;
use util\Objects;
class IsOptional extends Type {
public $binding, $default;
/**
* Creates a an optional binding "type"
*
* @param lang.ast.syntax.php.IsBinding $binding
* @param lang.ast.Node $default
*/
public function __construct($binding, $default) {
$this->binding= $binding;
$this->default= $default;
}
/** @return string */
public function toString() {
return nameof($this).'('.$this->binding->toString().' ?? '.Objects::stringOf($this->default).')';
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment