Skip to content

Commit

Permalink
Merge pull request #53 from xp-framework/feature/generic-class-resolu…
Browse files Browse the repository at this point in the history
…tion

Add syntactic support for `T<string>::class`
  • Loading branch information
thekid authored Aug 4, 2024
2 parents 8dbdcdd + 9467b87 commit 9481a4e
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 4 deletions.
38 changes: 36 additions & 2 deletions src/main/php/lang/ast/syntax/PHP.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ public function __construct() {
$expr= new InvokeExpression($expr, $arguments, $token->line);
}

return new ScopeExpression($scope, $expr, $left->line);
return new ScopeExpression($scope, $expr, $token->line);
});

$this->infix('(', 100, function($parse, $token, $left) {
Expand Down Expand Up @@ -264,7 +264,7 @@ public function __construct() {
$parse->forward();
$skipped[]= $parse->token;
}
$parse->queue= $parse->queue ? array_merge($skipped, $parse->queue) : $skipped;
$parse->queue= $parse->queue ? [...$skipped, ...$parse->queue] : $skipped;

if ($cast && ('operator' !== $parse->token->kind || '(' === $parse->token->value || '[' === $parse->token->value)) {
$parse->forward();
Expand Down Expand Up @@ -482,6 +482,40 @@ public function __construct() {
});

$this->prefix('(name)', 0, function($parse, $token) {
static $types= ['(' => 1, ')' => 1, ',' => 1, '?' => 1, ':' => 1, '|' => 1, '&' => 1];

// Disambiguate `Class<T>` from `const < expr` by looking ahead
if ('<' === $parse->token->value) {
$generic= true;
$level= 1;
$skipped= [$parse->token];
while ($level > 0) {
$parse->forward();
$skipped[]= $parse->token;

if ('<' === $parse->token->symbol->id) {
$level++;
} else if ('<?' === $parse->token->symbol->id) {
$level++;
} else if ('>' === $parse->token->symbol->id) {
$level--;
} else if ('>>' === $parse->token->symbol->id) {
$level-= 2;
} else if ('name' !== $parse->token->kind && !isset($types[$parse->token->value])) {
$generic= false;
break;
}
}

$parse->queue= $parse->queue ? [...$skipped, ...$parse->queue] : $skipped;
if ($generic) {
$parse->token= $token;
return $this->type($parse, false);
}

$parse->forward();
}

return new Literal($token->value, $token->line);
});

Expand Down
10 changes: 9 additions & 1 deletion src/test/php/lang/ast/unittest/parse/MembersTest.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
Variable,
Parameter
};
use lang\ast\types\{IsFunction, IsLiteral, IsNullable, IsUnion, IsValue};
use lang\ast\types\{IsFunction, IsLiteral, IsNullable, IsUnion, IsValue, IsGeneric};
use test\{Assert, Test, Values};

class MembersTest extends ParseTest {
Expand Down Expand Up @@ -297,6 +297,14 @@ public function class_resolution() {
);
}

#[Test]
public function generic_class_resolution() {
$this->assertParsed(
[new ScopeExpression(new IsGeneric(new IsValue('\\A'), [new IsValue('\\T')]), new Literal('class', self::LINE), self::LINE)],
'A<T>::class;'
);
}

#[Test]
public function instance_method_invocation() {
$this->assertParsed(
Expand Down
27 changes: 26 additions & 1 deletion src/test/php/lang/ast/unittest/parse/OperatorTest.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
UnaryExpression,
Variable
};
use lang\ast\types\{IsExpression, IsValue};
use lang\ast\types\{IsExpression, IsGeneric, IsValue};
use test\{Assert, Test, Values};

class OperatorTest extends ParseTest {
Expand Down Expand Up @@ -283,6 +283,18 @@ public function precedence_of_not_and_instance_of() {
);
}

#[Test]
public function instanceof_generic() {
$this->assertParsed(
[new InstanceOfExpression(
new Variable('this', self::LINE),
new IsGeneric(new IsValue('self'), [new IsValue('T')]),
self::LINE
)],
'$this instanceof self<T>;'
);
}

#[Test, Values(['+', '-', '~'])]
public function precedence_of_prefix($operator) {
$this->assertParsed(
Expand Down Expand Up @@ -332,4 +344,17 @@ public function multiple_semicolons() {
';; $a= 1 ;;; $b= 2;'
);
}

#[Test]
public function const_less_than_const() {
$this->assertParsed(
[new BinaryExpression(
new Literal('a', self::LINE),
'<',
new Literal('b', self::LINE),
self::LINE
)],
'a < b;'
);
}
}

0 comments on commit 9481a4e

Please sign in to comment.