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

View File

@ -87,14 +87,15 @@ function resolveMacros($code) {
if ('pushNormalizing' === $name) { if ('pushNormalizing' === $name) {
assertArgs(2, $args, $name); assertArgs(2, $args, $name);
return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); }' return 'if (' . $args[1] . ' !== null) { ' . $args[0] . '[] = ' . $args[1] . '; } $$ = ' . $args[0] . ';';
. ' else { ' . $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0] . '; }';
} }
if ('toArray' == $name) { if ('toBlock' == $name) {
assertArgs(1, $args, $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) { if ('parseVar' === $name) {
@ -122,15 +123,6 @@ function resolveMacros($code) {
return $args[0] . ' = $this->maybeCreateZeroLengthNop($this->tokenPos);'; 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]; return $matches[0];
}, },
$code $code

View File

@ -195,12 +195,6 @@ class TokenStream {
return false; 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 { public function haveTagInRange(int $startPos, int $endPos): bool {
return $this->haveTokenInRange($startPos, $endPos, \T_OPEN_TAG) return $this->haveTokenInRange($startPos, $endPos, \T_OPEN_TAG)
|| $this->haveTokenInRange($startPos, $endPos, \T_CLOSE_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 ''; return '';
} }
protected function pStmt_Block(Stmt\Block $node): string {
return '{' . $this->pStmts($node->stmts) . $this->nl . '}';
}
// Helpers // Helpers
protected function pClassCommon(Stmt\Class_ $node, string $afterClassToken): string { protected function pClassCommon(Stmt\Class_ $node, string $afterClassToken): string {

View File

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

View File

@ -153,8 +153,9 @@ $stmts[1] = $tmp;
* fallback. */ * fallback. */
----- -----
<?php <?php
{
$x; $x;
}
function foo() { function foo() {
foo(); foo();
/* /*
@ -186,4 +187,4 @@ echo "{$bar}bar";
[$a [$a
,$b ,$b
, ,
,] = $b; ,] = $b;

View File

@ -354,3 +354,16 @@ array_splice($stmts[0]->expr->var->items, 1, 0, [null]);
----- -----
<?php <?php
list($x, , $y) = $z; 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, []); array_splice($stmts, 0, 1, []);
----- -----
<?php <?php
$y; $y;
----- -----
<?php <?php
@ -80,8 +79,7 @@ $x;
array_splice($stmts, 0, 1, []); array_splice($stmts, 0, 1, []);
----- -----
<?php <?php
{ $y; }
$y;
----- -----
<?php <?php
$x; $x;
@ -90,7 +88,6 @@ $x;
array_pop($stmts); array_pop($stmts);
----- -----
<?php <?php
$x; $x;
----- -----
<?php <?php
@ -100,8 +97,7 @@ $y;
array_pop($stmts); array_pop($stmts);
----- -----
<?php <?php
{ $x; }
$x;
----- -----
<?php <?php
// Foo // Foo

View File

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

View File

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

View File

@ -19,9 +19,13 @@ array(
----- -----
!!positions !!positions
array( array(
0: Stmt_Nop[3:0 - 3:17]( 0: Stmt_Block[2:1 - 4:1](
comments: array( stmts: array(
0: /* comment */ 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 Syntax error, unexpected T_STATIC, expecting T_STRING from 1:13 to 1:18
array( array(
0: Stmt_Block(
stmts: array(
)
)
) )
----- -----
<?php class A extends self {} <?php class A extends self {}
@ -211,6 +215,10 @@ array(
----- -----
Syntax error, unexpected T_STATIC, expecting T_STRING from 1:17 to 1:22 Syntax error, unexpected T_STATIC, expecting T_STRING from 1:17 to 1:22
array( array(
0: Stmt_Block(
stmts: array(
)
)
) )
----- -----
<?php interface A extends self {} <?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 Syntax error, unexpected T_READONLY, expecting T_STRING from 2:7 to 2:14
array( array(
0: Stmt_Block(
stmts: array(
)
)
) )
----- -----
<?php <?php

View File

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

View File

@ -63,17 +63,21 @@ array(
0: // Missing NS separator 0: // Missing NS separator
) )
) )
1: Stmt_Expression( 1: Stmt_Block(
expr: Expr_ConstFetch( stmts: array(
name: Name( 0: Stmt_Expression(
name: Bar expr: Expr_ConstFetch(
name: Name(
name: Bar
)
)
) )
) 1: Stmt_Expression(
) expr: Expr_ConstFetch(
2: Stmt_Expression( name: Name(
expr: Expr_ConstFetch( name: Baz
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";
}
}