Skip to content

Commit ce69270

Browse files
committed
Correct parse token enqueuing during cast/braced disambiguation
Fixes #60
1 parent ebfcabc commit ce69270

3 files changed

Lines changed: 54 additions & 14 deletions

File tree

ChangeLog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ XP AST ChangeLog
33

44
## ?.?.? / ????-??-??
55

6+
## 12.1.1 / 2026-05-16
7+
8+
* Fixed issue #60: IIFE parse error - @thekid
9+
610
## 12.1.0 / 2026-04-23
711

812
* Merged PR #59: Implement logical AND and OR assignment operators `&&=`

src/main/php/lang/ast/syntax/PHP.class.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,9 @@ public function __construct() {
246246

247247
// This is ambiguous:
248248
//
249-
// - An expression surrounded by parentheses `($a ?? $b)->invoke()`;
250-
// - A cast `(int)$a` or `(int)($a / 2)`.
249+
// - An expression surrounded by parentheses `($a ?? $b)->invoke()`
250+
// - An invocation of a braced expression like `($expr)()`
251+
// - A cast `(int)$a` or `(int)($a / 2)`
251252
//
252253
// Resolve by looking ahead after the closing ")"
253254
$this->prefix('(', 0, function($parse, $token) {
@@ -553,7 +554,7 @@ public function __construct() {
553554
if (':' === $parse->token->value) {
554555
$node= new Label($token->value, $token->line);
555556
} else {
556-
$parse->queue[]= $parse->token;
557+
array_unshift($parse->queue, $parse->token);
557558
$parse->token= $token;
558559
$node= $this->expression($parse, 0);
559560
}
@@ -705,7 +706,7 @@ public function __construct() {
705706
continue;
706707
}
707708

708-
$parse->queue[]= $parse->token;
709+
array_unshift($parse->queue, $parse->token);
709710
$parse->token= $const;
710711
}
711712

@@ -1080,8 +1081,8 @@ public function __construct() {
10801081
if ('=' === $parse->token->value) {
10811082
$name= $first->value;
10821083
} else {
1083-
$parse->queue[]= $first;
1084-
$parse->queue[]= $parse->token;
1084+
array_unshift($parse->queue, $parse->token);
1085+
array_unshift($parse->queue, $first);
10851086
$parse->token= $first;
10861087

10871088
$type= $this->type($parse, false);

src/test/php/lang/ast/unittest/parse/ClosuresTest.class.php

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
<?php namespace lang\ast\unittest\parse;
22

3-
use lang\ast\nodes\{BinaryExpression, ClosureExpression, Literal, Parameter, ReturnStatement, Signature, Variable};
3+
use lang\ast\nodes\{
4+
BinaryExpression,
5+
Braced,
6+
ClosureExpression,
7+
InvokeExpression,
8+
Literal,
9+
Parameter,
10+
ReturnStatement,
11+
Signature,
12+
Variable
13+
};
414
use lang\ast\{FunctionType, Type};
515
use test\{Assert, Before, Test};
616

717
class ClosuresTest extends ParseTest {
8-
private $returns;
18+
private $returns, $invoke;
919

1020
#[Before]
1121
public function returns() {
@@ -20,6 +30,15 @@ public function returns() {
2030
);
2131
}
2232

33+
#[Before]
34+
public function invoke() {
35+
$this->invoke= new InvokeExpression(
36+
new Literal('var_dump', self::LINE),
37+
[new Literal('true', self::LINE)],
38+
self::LINE
39+
);
40+
}
41+
2342
#[Test]
2443
public function with_body() {
2544
$this->assertParsed(
@@ -64,24 +83,40 @@ public function with_use_by_reference() {
6483
#[Test]
6584
public function with_return_type() {
6685
$this->assertParsed(
67-
[new ClosureExpression(new Signature([], new Type('int'), false, self::LINE), null, [$this->returns], false, self::LINE)],
68-
'function(): int { return $a + 1; };'
86+
[new ClosureExpression(new Signature([], new Type('int'), false, self::LINE), null, [$this->invoke], false, self::LINE)],
87+
'function(): int { var_dump(true); };'
6988
);
7089
}
7190

7291
#[Test]
7392
public function with_nullable_return_type() {
7493
$this->assertParsed(
75-
[new ClosureExpression(new Signature([], new Type('?int'), false, self::LINE), null, [$this->returns], false, self::LINE)],
76-
'function(): ?int { return $a + 1; };'
94+
[new ClosureExpression(new Signature([], new Type('?int'), false, self::LINE), null, [$this->invoke], false, self::LINE)],
95+
'function(): ?int { var_dump(true); };'
7796
);
7897
}
7998

8099
#[Test]
81100
public function static_function() {
82101
$this->assertParsed(
83-
[new ClosureExpression(new Signature([], null, false, self::LINE), null, [$this->returns], true, self::LINE)],
84-
'static function() { return $a + 1; };'
102+
[new ClosureExpression(new Signature([], null, false, self::LINE), null, [$this->invoke], true, self::LINE)],
103+
'static function() { var_dump(true); };'
104+
);
105+
}
106+
107+
/** @see https://github.com/xp-framework/ast/issues/60 */
108+
#[Test]
109+
public function iife_with_statement() {
110+
$this->assertParsed(
111+
[new InvokeExpression(
112+
new Braced(
113+
new ClosureExpression(new Signature([], null, false, self::LINE), null, [$this->invoke], false, self::LINE),
114+
self::LINE
115+
),
116+
[],
117+
self::LINE
118+
)],
119+
'(function() { var_dump(true); })();'
85120
);
86121
}
87122
}

0 commit comments

Comments
 (0)