Skip to content

Commit 02501f9

Browse files
committed
fix(#39): extend all-caps support to expression contexts
The initial fix covered declaration positions. All-caps identifiers in expression contexts were still failing: new RBF(), RBF::method(), obj->RBF(), RBF(), obj->RBF, RBF::CONST Add CONSTANT alternatives to: new, xx_fcall_expr, xx_scall_expr, xx_mcall_expr, xx_common_expr property/static access, xx_literal_expr static-constant-access. Error/semantic tests added: errors/all-caps-still-errors.phpt - genuinely invalid syntax still errors errors/all-caps-constant-semantics.phpt - CONSTANT in expression context is still parsed as a PHP constant, not confused with a variable name cf/all-caps-expressions.phpt - new/fcall/scall/mcall/property-access All 122 tests pass.
1 parent 03687eb commit 02501f9

4 files changed

Lines changed: 402 additions & 0 deletions

File tree

parser/zephir.lemon

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2379,6 +2379,14 @@ xx_common_expr(R) ::= xx_common_expr(V) ARROW IDENTIFIER(I) . {
23792379
xx_ret_expr(&R, "property-access", &V, &identifier, NULL, status->scanner_state);
23802380
}
23812381
}
2382+
/* property-access with all-caps property name: obj->RBF */
2383+
xx_common_expr(R) ::= xx_common_expr(V) ARROW CONSTANT(I) . {
2384+
{
2385+
zval identifier;
2386+
xx_ret_literal(&identifier, XX_T_IDENTIFIER, I, status->scanner_state);
2387+
xx_ret_expr(&R, "property-access", &V, &identifier, NULL, status->scanner_state);
2388+
}
2389+
}
23822390

23832391
xx_common_expr(R) ::= xx_common_expr(V) ARROW BRACKET_OPEN IDENTIFIER(I) BRACKET_CLOSE . {
23842392
{
@@ -2414,6 +2422,24 @@ xx_common_expr(R) ::= IDENTIFIER(V) DOUBLECOLON CONSTANT(I) . {
24142422
}
24152423
}
24162424

2425+
/* static-property/constant access with all-caps class name: RBF::prop, RBF::CONST */
2426+
xx_common_expr(R) ::= CONSTANT(V) DOUBLECOLON IDENTIFIER(I) . [COMMA] {
2427+
{
2428+
zval identifier, identifier2;
2429+
xx_ret_literal(&identifier, XX_T_IDENTIFIER, V, status->scanner_state);
2430+
xx_ret_literal(&identifier2, XX_T_IDENTIFIER, I, status->scanner_state);
2431+
xx_ret_expr(&R, "static-property-access", &identifier, &identifier2, NULL, status->scanner_state);
2432+
}
2433+
}
2434+
xx_common_expr(R) ::= CONSTANT(V) DOUBLECOLON CONSTANT(I) . {
2435+
{
2436+
zval identifier, identifier2;
2437+
xx_ret_literal(&identifier, XX_T_IDENTIFIER, V, status->scanner_state);
2438+
xx_ret_literal(&identifier2, XX_T_IDENTIFIER, I, status->scanner_state);
2439+
xx_ret_expr(&R, "static-constant-access", &identifier, &identifier2, NULL, status->scanner_state);
2440+
}
2441+
}
2442+
24172443
/* y = v[expr] */
24182444
/*xx_common_expr(R) ::= IDENTIFIER(V) SBRACKET_OPEN xx_common_expr(I) SBRACKET_CLOSE . {
24192445
xx_ret_expr(&R, "array-access", xx_ret_literal(XX_T_IDENTIFIER, V, status->scanner_state), I, NULL, status->scanner_state);
@@ -2601,16 +2627,28 @@ xx_common_expr(R) ::= NEW STATIC PARENTHESES_OPEN xx_call_parameters(P) PARENTHE
26012627
xx_common_expr(R) ::= NEW IDENTIFIER(I) . {
26022628
xx_ret_new_instance(&R, 0, I, NULL, status->scanner_state);
26032629
}
2630+
/* y = new RBF (all-caps class name) */
2631+
xx_common_expr(R) ::= NEW CONSTANT(I) . {
2632+
xx_ret_new_instance(&R, 0, I, NULL, status->scanner_state);
2633+
}
26042634

26052635
/* y = new MyClass() */
26062636
xx_common_expr(R) ::= NEW IDENTIFIER(I) PARENTHESES_OPEN PARENTHESES_CLOSE . {
26072637
xx_ret_new_instance(&R, 0, I, NULL, status->scanner_state);
26082638
}
2639+
/* y = new RBF() */
2640+
xx_common_expr(R) ::= NEW CONSTANT(I) PARENTHESES_OPEN PARENTHESES_CLOSE . {
2641+
xx_ret_new_instance(&R, 0, I, NULL, status->scanner_state);
2642+
}
26092643

26102644
/* y = new MyClass(false, x) */
26112645
xx_common_expr(R) ::= NEW IDENTIFIER(I) PARENTHESES_OPEN xx_call_parameters(P) PARENTHESES_CLOSE . {
26122646
xx_ret_new_instance(&R, 0, I, &P, status->scanner_state);
26132647
}
2648+
/* y = new RBF(false, x) */
2649+
xx_common_expr(R) ::= NEW CONSTANT(I) PARENTHESES_OPEN xx_call_parameters(P) PARENTHESES_CLOSE . {
2650+
xx_ret_new_instance(&R, 0, I, &P, status->scanner_state);
2651+
}
26142652

26152653
/* y = new {MyClass} */
26162654
xx_common_expr(R) ::= NEW BRACKET_OPEN IDENTIFIER(I) BRACKET_CLOSE . {
@@ -2636,11 +2674,19 @@ xx_common_expr(R) ::= NEW xx_parameter_type(T) PARENTHESES_OPEN xx_call_paramete
26362674
xx_fcall_expr(R) ::= IDENTIFIER(I) PARENTHESES_OPEN xx_call_parameters(P) PARENTHESES_CLOSE . {
26372675
xx_ret_fcall(&R, 1, I, &P, status->scanner_state);
26382676
}
2677+
/* y = RBF(false, x) — all-caps function call */
2678+
xx_fcall_expr(R) ::= CONSTANT(I) PARENTHESES_OPEN xx_call_parameters(P) PARENTHESES_CLOSE . {
2679+
xx_ret_fcall(&R, 1, I, &P, status->scanner_state);
2680+
}
26392681

26402682
/* y = f() */
26412683
xx_fcall_expr(R) ::= IDENTIFIER(I) PARENTHESES_OPEN PARENTHESES_CLOSE . {
26422684
xx_ret_fcall(&R, 1, I, NULL, status->scanner_state);
26432685
}
2686+
/* y = RBF() — all-caps function call */
2687+
xx_fcall_expr(R) ::= CONSTANT(I) PARENTHESES_OPEN PARENTHESES_CLOSE . {
2688+
xx_ret_fcall(&R, 1, I, NULL, status->scanner_state);
2689+
}
26442690

26452691
/* y = {f}(false, x) */
26462692
xx_fcall_expr(R) ::= BRACKET_OPEN IDENTIFIER(I) BRACKET_CLOSE PARENTHESES_OPEN xx_call_parameters(P) PARENTHESES_CLOSE . {
@@ -2658,13 +2704,43 @@ xx_scall_expr(R) ::= IDENTIFIER(O) DOUBLECOLON IDENTIFIER(M) PARENTHESES_OPEN PA
26582704
efree(O->token);
26592705
efree(O);
26602706
}
2707+
xx_scall_expr(R) ::= CONSTANT(O) DOUBLECOLON IDENTIFIER(M) PARENTHESES_OPEN PARENTHESES_CLOSE . {
2708+
xx_ret_scall(&R, 0, O->token, 0, M, NULL, status->scanner_state);
2709+
efree(O->token);
2710+
efree(O);
2711+
}
2712+
xx_scall_expr(R) ::= IDENTIFIER(O) DOUBLECOLON CONSTANT(M) PARENTHESES_OPEN PARENTHESES_CLOSE . {
2713+
xx_ret_scall(&R, 0, O->token, 0, M, NULL, status->scanner_state);
2714+
efree(O->token);
2715+
efree(O);
2716+
}
2717+
xx_scall_expr(R) ::= CONSTANT(O) DOUBLECOLON CONSTANT(M) PARENTHESES_OPEN PARENTHESES_CLOSE . {
2718+
xx_ret_scall(&R, 0, O->token, 0, M, NULL, status->scanner_state);
2719+
efree(O->token);
2720+
efree(O);
2721+
}
26612722

26622723
/* o::m(false, x) */
26632724
xx_scall_expr(R) ::= IDENTIFIER(O) DOUBLECOLON IDENTIFIER(M) PARENTHESES_OPEN xx_call_parameters(P) PARENTHESES_CLOSE . {
26642725
xx_ret_scall(&R, 0, O->token, 0, M, &P, status->scanner_state);
26652726
efree(O->token);
26662727
efree(O);
26672728
}
2729+
xx_scall_expr(R) ::= CONSTANT(O) DOUBLECOLON IDENTIFIER(M) PARENTHESES_OPEN xx_call_parameters(P) PARENTHESES_CLOSE . {
2730+
xx_ret_scall(&R, 0, O->token, 0, M, &P, status->scanner_state);
2731+
efree(O->token);
2732+
efree(O);
2733+
}
2734+
xx_scall_expr(R) ::= IDENTIFIER(O) DOUBLECOLON CONSTANT(M) PARENTHESES_OPEN xx_call_parameters(P) PARENTHESES_CLOSE . {
2735+
xx_ret_scall(&R, 0, O->token, 0, M, &P, status->scanner_state);
2736+
efree(O->token);
2737+
efree(O);
2738+
}
2739+
xx_scall_expr(R) ::= CONSTANT(O) DOUBLECOLON CONSTANT(M) PARENTHESES_OPEN xx_call_parameters(P) PARENTHESES_CLOSE . {
2740+
xx_ret_scall(&R, 0, O->token, 0, M, &P, status->scanner_state);
2741+
efree(O->token);
2742+
efree(O);
2743+
}
26682744

26692745
/* static::m(false, x) */
26702746
xx_scall_expr(R) ::= STATIC DOUBLECOLON IDENTIFIER(M) PARENTHESES_OPEN xx_call_parameters(P) PARENTHESES_CLOSE . {
@@ -2722,11 +2798,19 @@ xx_scall_expr(R) ::= IDENTIFIER(O) DOUBLECOLON BRACKET_OPEN IDENTIFIER(M) BRACKE
27222798
xx_mcall_expr(R) ::= xx_common_expr(O) ARROW IDENTIFIER(M) PARENTHESES_OPEN xx_call_parameters(P) PARENTHESES_CLOSE . {
27232799
xx_ret_mcall(&R, 1, &O, M, &P, status->scanner_state);
27242800
}
2801+
/* o->RBF(false, x) */
2802+
xx_mcall_expr(R) ::= xx_common_expr(O) ARROW CONSTANT(M) PARENTHESES_OPEN xx_call_parameters(P) PARENTHESES_CLOSE . {
2803+
xx_ret_mcall(&R, 1, &O, M, &P, status->scanner_state);
2804+
}
27252805

27262806
/* o->m() */
27272807
xx_mcall_expr(R) ::= xx_common_expr(O) ARROW IDENTIFIER(M) PARENTHESES_OPEN PARENTHESES_CLOSE . {
27282808
xx_ret_mcall(&R, 1, &O, M, NULL, status->scanner_state);
27292809
}
2810+
/* o->RBF() */
2811+
xx_mcall_expr(R) ::= xx_common_expr(O) ARROW CONSTANT(M) PARENTHESES_OPEN PARENTHESES_CLOSE . {
2812+
xx_ret_mcall(&R, 1, &O, M, NULL, status->scanner_state);
2813+
}
27302814

27312815
/* o->{m}(false, x) */
27322816
xx_mcall_expr(R) ::= xx_common_expr(O) ARROW BRACKET_OPEN IDENTIFIER(M) BRACKET_CLOSE PARENTHESES_OPEN xx_call_parameters(P) PARENTHESES_CLOSE . {
@@ -2932,6 +3016,15 @@ xx_literal_expr(R) ::= IDENTIFIER(V) DOUBLECOLON CONSTANT(I) . {
29323016
xx_ret_expr(&R, "static-constant-access", &identifier, &identifier2, NULL, status->scanner_state);
29333017
}
29343018
}
3019+
/* RBF::MY_CONST — all-caps class name in literal constant access */
3020+
xx_literal_expr(R) ::= CONSTANT(V) DOUBLECOLON CONSTANT(I) . {
3021+
{
3022+
zval identifier, identifier2;
3023+
xx_ret_literal(&identifier, XX_T_IDENTIFIER, V, status->scanner_state);
3024+
xx_ret_literal(&identifier2, XX_T_IDENTIFIER, I, status->scanner_state);
3025+
xx_ret_expr(&R, "static-constant-access", &identifier, &identifier2, NULL, status->scanner_state);
3026+
}
3027+
}
29353028

29363029
xx_literal_expr(R) ::= CONSTANT(I) . {
29373030
xx_ret_literal(&R, XX_T_CONSTANT, I, status->scanner_state);

tests/cf/all-caps-expressions.phpt

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
--TEST--
2+
All-caps names in expression contexts: new, fcall, scall, mcall, property-access (issue #39)
3+
--SKIPIF--
4+
<?php include(__DIR__ . '/../skipif.inc'); ?>
5+
--FILE--
6+
<?php
7+
// new RBF()
8+
$code =<<<ZEP
9+
class Foo
10+
{
11+
public function bar()
12+
{
13+
var x;
14+
let x = new RBF();
15+
}
16+
}
17+
ZEP;
18+
$ir = zephir_parse_file($code, '(eval code)');
19+
$e = $ir[0]['definition']['methods'][0]['statements'][1]['assignments'][0]['expr'];
20+
var_dump($e['type']);
21+
var_dump($e['class']);
22+
23+
// new RBF(1) — with arguments
24+
$code2 =<<<ZEP
25+
class Foo
26+
{
27+
public function bar()
28+
{
29+
var x;
30+
let x = new RBF(1);
31+
}
32+
}
33+
ZEP;
34+
$ir2 = zephir_parse_file($code2, '(eval code)');
35+
$e2 = $ir2[0]['definition']['methods'][0]['statements'][1]['assignments'][0]['expr'];
36+
var_dump($e2['type']);
37+
var_dump($e2['class']);
38+
39+
// RBF::compute() — static call with all-caps class name
40+
$code3 =<<<ZEP
41+
class Foo
42+
{
43+
public function bar()
44+
{
45+
RBF::compute();
46+
}
47+
}
48+
ZEP;
49+
$ir3 = zephir_parse_file($code3, '(eval code)');
50+
$stmt3 = $ir3[0]['definition']['methods'][0]['statements'][0];
51+
var_dump($stmt3['type']);
52+
var_dump($stmt3['expr']['class']);
53+
var_dump($stmt3['expr']['name']);
54+
55+
// this->RBF() — method call with all-caps method name
56+
$code4 =<<<ZEP
57+
class Foo
58+
{
59+
public function bar()
60+
{
61+
this->RBF();
62+
}
63+
}
64+
ZEP;
65+
$ir4 = zephir_parse_file($code4, '(eval code)');
66+
$stmt4 = $ir4[0]['definition']['methods'][0]['statements'][0];
67+
var_dump($stmt4['type']);
68+
var_dump($stmt4['expr']['name']);
69+
70+
// RBF() — global function call with all-caps name
71+
$code5 =<<<ZEP
72+
class Foo
73+
{
74+
public function bar()
75+
{
76+
RBF();
77+
}
78+
}
79+
ZEP;
80+
$ir5 = zephir_parse_file($code5, '(eval code)');
81+
$stmt5 = $ir5[0]['definition']['methods'][0]['statements'][0];
82+
var_dump($stmt5['type']);
83+
var_dump($stmt5['expr']['name']);
84+
85+
// this->RBF — property-access with all-caps property name
86+
$code6 =<<<ZEP
87+
class Foo
88+
{
89+
public function bar()
90+
{
91+
var x;
92+
let x = this->RBF;
93+
}
94+
}
95+
ZEP;
96+
$ir6 = zephir_parse_file($code6, '(eval code)');
97+
$e6 = $ir6[0]['definition']['methods'][0]['statements'][1]['assignments'][0]['expr'];
98+
var_dump($e6['type']);
99+
var_dump($e6['right']['value']);
100+
101+
// RBF::MY_CONST — static constant access with all-caps class
102+
$code7 =<<<ZEP
103+
class Foo
104+
{
105+
public function bar()
106+
{
107+
var x;
108+
let x = RBF::MY_CONST;
109+
}
110+
}
111+
ZEP;
112+
$ir7 = zephir_parse_file($code7, '(eval code)');
113+
$e7 = $ir7[0]['definition']['methods'][0]['statements'][1]['assignments'][0]['expr'];
114+
var_dump($e7['type']);
115+
var_dump($e7['left']['value']);
116+
var_dump($e7['right']['value']);
117+
?>
118+
--EXPECT--
119+
string(3) "new"
120+
string(3) "RBF"
121+
string(3) "new"
122+
string(3) "RBF"
123+
string(5) "scall"
124+
string(3) "RBF"
125+
string(7) "compute"
126+
string(5) "mcall"
127+
string(3) "RBF"
128+
string(5) "fcall"
129+
string(3) "RBF"
130+
string(15) "property-access"
131+
string(3) "RBF"
132+
string(22) "static-constant-access"
133+
string(3) "RBF"
134+
string(8) "MY_CONST"
135+
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
--TEST--
2+
All-caps token in expression context must still be parsed as a constant, not a variable (issue #39)
3+
--SKIPIF--
4+
<?php include(__DIR__ . '/../skipif.inc'); ?>
5+
--FILE--
6+
<?php
7+
// When RBF appears on the RIGHT side of a let assignment (i.e. in an
8+
// expression), it must remain type="constant" — NOT type="variable".
9+
// The fix only reinterprets CONSTANT tokens as names in DECLARATION
10+
// positions (let LHS, class name, method name, etc.).
11+
12+
$code =<<<ZEP
13+
class Foo
14+
{
15+
public function bar()
16+
{
17+
var x;
18+
let x = RBF;
19+
}
20+
}
21+
ZEP;
22+
$ir = zephir_parse_file($code, '(eval code)');
23+
$let = $ir[0]['definition']['methods'][0]['statements'][1]['assignments'][0];
24+
// LHS: x is a plain variable assignment
25+
var_dump($let['assign-type']);
26+
var_dump($let['variable']);
27+
// RHS: RBF is a PHP constant reference, not a variable
28+
var_dump($let['expr']['type']);
29+
var_dump($let['expr']['value']);
30+
31+
// All-caps token in a binary expression must also remain type="constant"
32+
$code2 =<<<ZEP
33+
class Bar
34+
{
35+
public function test()
36+
{
37+
var x;
38+
let x = RBF + 1;
39+
}
40+
}
41+
ZEP;
42+
$ir2 = zephir_parse_file($code2, '(eval code)');
43+
$expr = $ir2[0]['definition']['methods'][0]['statements'][1]['assignments'][0]['expr'];
44+
var_dump($expr['type']);
45+
var_dump($expr['left']['type']);
46+
var_dump($expr['left']['value']);
47+
48+
// const MYCONSTANT = value — the name after const keyword is still stored correctly
49+
$code3 =<<<ZEP
50+
class Baz
51+
{
52+
const MYCONSTANT = 1;
53+
}
54+
ZEP;
55+
$ir3 = zephir_parse_file($code3, '(eval code)');
56+
$c = $ir3[0]['definition']['constants'][0];
57+
var_dump($c['type']);
58+
var_dump($c['name']);
59+
?>
60+
--EXPECT--
61+
string(8) "variable"
62+
string(1) "x"
63+
string(8) "constant"
64+
string(3) "RBF"
65+
string(3) "add"
66+
string(8) "constant"
67+
string(3) "RBF"
68+
string(5) "const"
69+
string(10) "MYCONSTANT"
70+

0 commit comments

Comments
 (0)