Introduce Stmt\Block

Stmt\Block will be created for { $a; } style blocks, unless these
occur directly inside some structure that is usually combined
with a block.

For example if ($a) { $b; } will continue to use the old
representation (plain array in in If_::$stmts), but a free-standing
{ $b; } will become a Stmt\Block.

Fixes #590.
This commit is contained in:
Nikita Popov 2023-09-24 20:27:30 +02:00
parent f5adbb5e3f
commit a1ccf57727
19 changed files with 1962 additions and 1903 deletions

View File

@ -366,21 +366,13 @@ inner_statement:
;
non_empty_statement:
'{' inner_statement_list '}'
{
if ($2) {
$$ = $2; prependLeadingComments($$);
} else {
makeNop($$);
if (null === $$) { $$ = array(); }
}
}
| T_IF '(' expr ')' statement elseif_list else_single
{ $$ = Stmt\If_[$3, ['stmts' => toArray($5), 'elseifs' => $6, 'else' => $7]]; }
'{' inner_statement_list '}' { $$ = Stmt\Block[$2]; }
| T_IF '(' expr ')' blocklike_statement elseif_list else_single
{ $$ = Stmt\If_[$3, ['stmts' => $5, 'elseifs' => $6, 'else' => $7]]; }
| T_IF '(' expr ')' ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';'
{ $$ = Stmt\If_[$3, ['stmts' => $6, 'elseifs' => $7, 'else' => $8]]; }
| T_WHILE '(' expr ')' while_statement { $$ = Stmt\While_[$3, $5]; }
| T_DO statement T_WHILE '(' expr ')' ';' { $$ = Stmt\Do_ [$5, toArray($2)]; }
| T_DO blocklike_statement T_WHILE '(' expr ')' ';' { $$ = Stmt\Do_ [$5, $2]; }
| T_FOR '(' for_expr ';' for_expr ';' for_expr ')' for_statement
{ $$ = Stmt\For_[['init' => $3, 'cond' => $5, 'loop' => $7, 'stmts' => $9]]; }
| T_SWITCH '(' expr ')' switch_case_list { $$ = Stmt\Switch_[$3, $5]; }
@ -416,14 +408,16 @@ non_empty_statement:
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
| T_GOTO identifier_not_reserved semi { $$ = Stmt\Goto_[$2]; }
| identifier_not_reserved ':' { $$ = Stmt\Label[$1]; }
| error { $$ = array(); /* means: no statement */ }
| error { $$ = null; /* means: no statement */ }
;
statement:
non_empty_statement
| ';'
{ makeNop($$);
if ($$ === null) $$ = array(); /* means: no statement */ }
| ';' { makeNop($$); }
;
blocklike_statement:
statement { toBlock($1); }
;
catches:
@ -554,17 +548,17 @@ non_empty_class_name_list:
;
for_statement:
statement { $$ = toArray($1); }
blocklike_statement
| ':' inner_statement_list T_ENDFOR ';' { $$ = $2; }
;
foreach_statement:
statement { $$ = toArray($1); }
blocklike_statement
| ':' inner_statement_list T_ENDFOREACH ';' { $$ = $2; }
;
declare_statement:
non_empty_statement { $$ = toArray($1); }
non_empty_statement { toBlock($1); }
| ';' { $$ = null; }
| ':' inner_statement_list T_ENDDECLARE ';' { $$ = $2; }
;
@ -624,7 +618,7 @@ match_arm:
;
while_statement:
statement { $$ = toArray($1); }
blocklike_statement { $$ = $1; }
| ':' inner_statement_list T_ENDWHILE ';' { $$ = $2; }
;
@ -634,7 +628,7 @@ elseif_list:
;
elseif:
T_ELSEIF '(' expr ')' statement { $$ = Stmt\ElseIf_[$3, toArray($5)]; }
T_ELSEIF '(' expr ')' blocklike_statement { $$ = Stmt\ElseIf_[$3, $5]; }
;
new_elseif_list:
@ -649,7 +643,7 @@ new_elseif:
else_single:
/* empty */ { $$ = null; }
| T_ELSE statement { $$ = Stmt\Else_[toArray($2)]; }
| T_ELSE blocklike_statement { $$ = Stmt\Else_[$2]; }
;
new_else_single:

View File

@ -87,14 +87,15 @@ function resolveMacros($code) {
if ('pushNormalizing' === $name) {
assertArgs(2, $args, $name);
return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); }'
. ' else { ' . $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0] . '; }';
return 'if (' . $args[1] . ' !== null) { ' . $args[0] . '[] = ' . $args[1] . '; } $$ = ' . $args[0] . ';';
}
if ('toArray' == $name) {
if ('toBlock' == $name) {
assertArgs(1, $args, $name);
return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')';
return 'if (' . $args[0] . ' instanceof Stmt\Block) { $$ = ' . $args[0] . '->stmts; } '
. 'else if (' . $args[0] . ' === null) { $$ = []; } '
. 'else { $$ = [' . $args[0] . ']; }';
}
if ('parseVar' === $name) {
@ -122,15 +123,6 @@ function resolveMacros($code) {
return $args[0] . ' = $this->maybeCreateZeroLengthNop($this->tokenPos);';
}
if ('prependLeadingComments' === $name) {
assertArgs(1, $args, $name);
return '$comments = $this->getCommentsBeforeToken($this->tokenStartStack[#1]); $stmts = ' . $args[0] . '; '
. 'if (!empty($comments)) {'
. '$stmts[0]->setAttribute(\'comments\', '
. 'array_merge($comments, $stmts[0]->getAttribute(\'comments\', []))); }';
}
return $matches[0];
},
$code

View File

@ -195,12 +195,6 @@ class TokenStream {
return false;
}
public function haveBracesInRange(int $startPos, int $endPos): bool {
return $this->haveTokenInRange($startPos, $endPos, '{')
|| $this->haveTokenInRange($startPos, $endPos, T_CURLY_OPEN)
|| $this->haveTokenInRange($startPos, $endPos, '}');
}
public function haveTagInRange(int $startPos, int $endPos): bool {
return $this->haveTokenInRange($startPos, $endPos, \T_OPEN_TAG)
|| $this->haveTokenInRange($startPos, $endPos, \T_CLOSE_TAG);

View File

@ -0,0 +1,29 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\Stmt;
class Block extends Stmt {
/** @var Stmt[] Statements */
public array $stmts;
/**
* A block of statements.
*
* @param Stmt[] $stmts Statements
* @param array<string, mixed> $attributes Additional attributes
*/
public function __construct(array $stmts, array $attributes = []) {
$this->attributes = $attributes;
$this->stmts = $stmts;
}
public function getType(): string {
return 'Stmt_Block';
}
public function getSubNodeNames(): array {
return ['stmts'];
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1012,6 +1012,10 @@ class Standard extends PrettyPrinterAbstract {
return '';
}
protected function pStmt_Block(Stmt\Block $node): string {
return '{' . $this->pStmts($node->stmts) . $this->nl . '}';
}
// Helpers
protected function pClassCommon(Stmt\Class_ $node, string $afterClassToken): string {

View File

@ -826,9 +826,8 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
}
if ($skipRemovedNode) {
if ($isStmtList && ($this->origTokens->haveBracesInRange($pos, $itemStartPos) ||
$this->origTokens->haveTagInRange($pos, $itemStartPos))) {
// We'd remove the brace of a code block.
if ($isStmtList && $this->origTokens->haveTagInRange($pos, $itemStartPos)) {
// We'd remove an opening/closing PHP tag.
// TODO: Preserve formatting.
$this->setIndentLevel($origIndentLevel);
return null;
@ -937,9 +936,8 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
$pos, $itemStartPos, $indentAdjustment);
$skipRemovedNode = true;
} else {
if ($isStmtList && ($this->origTokens->haveBracesInRange($pos, $itemStartPos) ||
$this->origTokens->haveTagInRange($pos, $itemStartPos))) {
// We'd remove the brace of a code block.
if ($isStmtList && $this->origTokens->haveTagInRange($pos, $itemStartPos)) {
// We'd remove an opening/closing PHP tag.
// TODO: Preserve formatting.
return null;
}
@ -1540,6 +1538,7 @@ abstract class PrettyPrinterAbstract implements PrettyPrinter {
Stmt\Function_::class . '->stmts' => "\n",
Stmt\If_::class . '->stmts' => "\n",
Stmt\Namespace_::class . '->stmts' => "\n",
Stmt\Block::class . '->stmts' => "\n",
// Attribute groups
Stmt\Class_::class . '->attrGroups' => "\n",

View File

@ -153,8 +153,9 @@ $stmts[1] = $tmp;
* fallback. */
-----
<?php
$x;
{
$x;
}
function foo() {
foo();
/*

View File

@ -354,3 +354,16 @@ array_splice($stmts[0]->expr->var->items, 1, 0, [null]);
-----
<?php
list($x, , $y) = $z;
-----
<?php
{
$a; $b;
}
-----
$stmts[0]->stmts[] = new Stmt\Expression(new Expr\Variable('c'));
-----
<?php
{
$a; $b;
$c;
}

View File

@ -70,7 +70,6 @@ $y;
array_splice($stmts, 0, 1, []);
-----
<?php
$y;
-----
<?php
@ -80,8 +79,7 @@ $x;
array_splice($stmts, 0, 1, []);
-----
<?php
$y;
{ $y; }
-----
<?php
$x;
@ -90,7 +88,6 @@ $x;
array_pop($stmts);
-----
<?php
$x;
-----
<?php
@ -100,8 +97,7 @@ $y;
array_pop($stmts);
-----
<?php
$x;
{ $x; }
-----
<?php
// Foo

View File

@ -15,20 +15,34 @@ Comments on blocks
{}
-----
array(
0: Stmt_Expression(
expr: Expr_Variable(
name: a
comments: array(
0: // baz
0: Stmt_Block(
stmts: array(
0: Stmt_Block(
stmts: array(
0: Stmt_Expression(
expr: Expr_Variable(
name: a
comments: array(
0: // baz
)
)
comments: array(
0: // baz
)
)
)
comments: array(
0: // bar
)
)
)
comments: array(
0: // foo
1: // bar
2: // baz
)
)
1: Stmt_Nop(
1: Stmt_Block(
stmts: array(
)
comments: array(
0: // empty
)

View File

@ -223,13 +223,17 @@ array(
)
)
)
1: Stmt_Expression(
expr: Expr_Assign(
var: Expr_Variable(
name: j
)
expr: Scalar_Int(
value: 1
1: Stmt_Block(
stmts: array(
0: Stmt_Expression(
expr: Expr_Assign(
var: Expr_Variable(
name: j
)
expr: Scalar_Int(
value: 1
)
)
)
)
)

View File

@ -19,9 +19,13 @@ array(
-----
!!positions
array(
0: Stmt_Nop[3:0 - 3:17](
comments: array(
0: /* comment */
0: Stmt_Block[2:1 - 4:1](
stmts: array(
0: Stmt_Nop[3:0 - 3:17](
comments: array(
0: /* comment */
)
)
)
)
)

View File

@ -42,6 +42,10 @@ array(
-----
Syntax error, unexpected T_STATIC, expecting T_STRING from 1:13 to 1:18
array(
0: Stmt_Block(
stmts: array(
)
)
)
-----
<?php class A extends self {}
@ -211,6 +215,10 @@ array(
-----
Syntax error, unexpected T_STATIC, expecting T_STRING from 1:17 to 1:22
array(
0: Stmt_Block(
stmts: array(
)
)
)
-----
<?php interface A extends self {}

View File

@ -5,6 +5,10 @@ class ReadOnly {}
-----
Syntax error, unexpected T_READONLY, expecting T_STRING from 2:7 to 2:14
array(
0: Stmt_Block(
stmts: array(
)
)
)
-----
<?php

View File

@ -4,7 +4,9 @@ Declare
declare (X='Y');
declare (A='B', C='D') {}
declare (A='B', C='D') {
echo "foo";
}
declare (A='B', C='D'):
enddeclare;
@ -43,6 +45,13 @@ array(
)
)
stmts: array(
0: Stmt_Echo(
exprs: array(
0: Scalar_String(
value: foo
)
)
)
)
)
2: Stmt_Declare(

View File

@ -63,17 +63,21 @@ array(
0: // Missing NS separator
)
)
1: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
name: Bar
1: Stmt_Block(
stmts: array(
0: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
name: Bar
)
)
)
)
)
2: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
name: Baz
1: Stmt_Expression(
expr: Expr_ConstFetch(
name: Name(
name: Baz
)
)
)
)
)

View File

@ -0,0 +1,28 @@
Block statements
-----
<?php
echo "a";
{
}
{
echo "b";
}
if ($c) {
echo "c";
{
echo "d";
}
}
-----
echo "a";
{
}
{
echo "b";
}
if ($c) {
echo "c";
{
echo "d";
}
}