diff --git a/src/main/php/lang/ast/nodes/Block.class.php b/src/main/php/lang/ast/nodes/Block.class.php index 440eef3..fae0bf4 100755 --- a/src/main/php/lang/ast/nodes/Block.class.php +++ b/src/main/php/lang/ast/nodes/Block.class.php @@ -1,8 +1,9 @@ statements; } + + /** Iteration support */ + public function getIterator(): Traversable { yield from $this->statements; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/ClosureExpression.class.php b/src/main/php/lang/ast/nodes/ClosureExpression.class.php index 674b064..ebbff8a 100755 --- a/src/main/php/lang/ast/nodes/ClosureExpression.class.php +++ b/src/main/php/lang/ast/nodes/ClosureExpression.class.php @@ -7,11 +7,11 @@ class ClosureExpression extends Annotated { public function __construct($signature, $use, $body, $static= false, $line= -1) { $this->signature= $signature; $this->use= $use; - $this->body= $body; + $this->body= is_array($body) ? new Block($body, $line) : $body; $this->static= $static; $this->line= $line; } /** @return iterable */ - public function children() { return $this->body; } + public function children() { return [&$this->body]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/FunctionDeclaration.class.php b/src/main/php/lang/ast/nodes/FunctionDeclaration.class.php index 0b57d6c..14529f2 100755 --- a/src/main/php/lang/ast/nodes/FunctionDeclaration.class.php +++ b/src/main/php/lang/ast/nodes/FunctionDeclaration.class.php @@ -7,10 +7,10 @@ class FunctionDeclaration extends Annotated { public function __construct($name, $signature, $body, $line= -1) { $this->name= $name; $this->signature= $signature; - $this->body= $body; + $this->body= is_array($body) ? new Block($body, $line) : $body; $this->line= $line; } /** @return iterable */ - public function children() { return $this->body; } + public function children() { return [&$this->body]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/LambdaExpression.class.php b/src/main/php/lang/ast/nodes/LambdaExpression.class.php index 61cfe18..4c28324 100755 --- a/src/main/php/lang/ast/nodes/LambdaExpression.class.php +++ b/src/main/php/lang/ast/nodes/LambdaExpression.class.php @@ -6,13 +6,11 @@ class LambdaExpression extends Annotated { public function __construct($signature, $body, $static= false, $line= -1) { $this->signature= $signature; - $this->body= $body; + $this->body= is_array($body) ? new Block($body, $line) : $body; $this->static= $static; $this->line= $line; } /** @return iterable */ - public function children() { - return is_array($this->body) ? $this->body : [&$this->body]; - } + public function children() { return [&$this->body]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/nodes/Method.class.php b/src/main/php/lang/ast/nodes/Method.class.php index 6dad794..253e77c 100755 --- a/src/main/php/lang/ast/nodes/Method.class.php +++ b/src/main/php/lang/ast/nodes/Method.class.php @@ -8,7 +8,7 @@ public function __construct($modifiers, $name, $signature, $body= null, $annotat $this->name= $name; $this->modifiers= $modifiers; $this->signature= $signature; - $this->body= $body; + $this->body= is_array($body) ? new Block($body, $line) : $body; parent::__construct($annotations, $comment, $line); } @@ -46,5 +46,5 @@ public function append($node) { } /** @return iterable */ - public function children() { return (array)$this->body; } + public function children() { return null === $this->body ? [] : [&$this->body]; } } \ No newline at end of file diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index 35346ca..2b3a932 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -307,12 +307,6 @@ public function __construct() { return new CloneExpression($arguments, $token->line); }); - $this->prefix('{', 0, function($parse, $token) { - $statements= $this->statements($parse); - $parse->expecting('}', 'block'); - return new Block($statements, $token->line); - }); - $this->prefix('[', 0, function($parse, $token) { return new ArrayLiteral($this->list($parse, ']', 'array literal'), $token->line); }); @@ -400,9 +394,8 @@ public function __construct() { $this->prefix('fn', 0, function($parse, $token) { $signature= $this->signature($parse); - $parse->expecting('=>', 'fn'); - - return new LambdaExpression($signature, $this->expression($parse, 0), false, $token->line); + $scope= $this->scope($parse, 'fn'); + return new LambdaExpression($signature, $scope, false, $token->line); }); $this->prefix('function', 0, function($parse, $token) { @@ -435,8 +428,8 @@ public function __construct() { } else if ('fn' === $parse->token->value) { $parse->forward(); $signature= $this->signature($parse); - $parse->expecting('=>', 'fn'); - return new LambdaExpression($signature, $this->expression($parse, 0), true, $token->line); + $scope= $this->scope($parse, 'fn'); + return new LambdaExpression($signature, $scope, true, $token->line); } else { return new Literal($token->value, $token->line); } @@ -463,15 +456,14 @@ public function __construct() { while ('}' !== $parse->token->value) { if ('default' === $parse->token->value) { $parse->forward(); - $parse->expecting('=>', 'match'); - $default= $this->expression($parse, 0); + $default= $this->scope($parse, 'match'); } else { $match= []; do { $match[]= $this->expression($parse, 0); } while (',' === $parse->token->value && $parse->forward() | true); - $parse->expecting('=>', 'match'); - $cases[]= new MatchCondition($match, $this->expression($parse, 0), $parse->token->line); + + $cases[]= new MatchCondition($match, $this->scope($parse, 'match'), $parse->token->line); } if (',' === $parse->token->value) { @@ -533,6 +525,12 @@ public function __construct() { return new Literal($token->value, $token->line); }); + // Blocks and standalone semicolons + $this->stmt('{', function($parse, $token) { + $statements= $this->statements($parse); + $parse->expecting('}', 'block'); + return new Block($statements, $token->line); + }); $this->stmt(';', function($parse, $token) { return null; }); // Unexpected standalone symbols, warn but continue @@ -886,14 +884,10 @@ public function __construct() { // Function expression used as statement (e.g. for pure side-effects!) if ('(' === $parse->token->value) { - $parse->queue= [$parse->token]; + array_unshift($parse->queue, $parse->token); $parse->token= new Token($this->symbol('function')); $parse->token->line= $token->line; - $expr= $this->expression($parse, 0); - - $parse->queue= [$parse->token]; - $parse->token= new Token($this->symbol(';')); - return $expr; + return $this->expression($parse, 0); } if ('&' === $parse->token->value) { @@ -907,11 +901,9 @@ public function __construct() { $parse->forward(); $signature= $this->signature($parse, $byref); - $parse->expecting('{', 'function'); - $statements= $this->statements($parse); - $parse->expecting('}', 'function'); + $scope= $this->scope($parse, 'function'); - return new FunctionDeclaration($name, $signature, $statements, $token->line); + return new FunctionDeclaration($name, $signature, $scope, $token->line); }); $this->stmt('class', function($parse, $token) { @@ -1138,15 +1130,20 @@ public function __construct() { $parse->forward(); $signature= $this->signature($parse, $byref); - if ('{' === $parse->token->value) { // Regular body + // Cannot use scope() here as we require a semicolon after single-expressions + if ('{' === $parse->token->value) { $parse->forward(); - $statements= $this->statements($parse); + $scope= new Block($this->statements($parse), $parse->token->line); $parse->expecting('}', 'method declaration'); - } else if (';' === $parse->token->value) { // Abstract or interface method - $statements= null; + } else if ('=>' === $parse->token->value) { + $parse->forward(); + $scope= $this->expression($parse, 0); + $parse->expecting(';', 'method declaration'); + } else if (';' === $parse->token->value) { + $scope= null; $parse->expecting(';', 'method declaration'); } else { - $parse->expecting('{ or ;', 'method declaration'); + $parse->expecting('{, => or ;', 'method declaration'); return; } @@ -1154,7 +1151,7 @@ public function __construct() { $modifiers, $name, $signature, - $statements, + $scope, $meta[DETAIL_ANNOTATIONS] ?? null, $comment, $line @@ -1591,6 +1588,29 @@ private function parameters($parse) { return $parameters; } + public function scope($parse, $context) { + if ('=>' === $parse->token->value) { + $parse->forward(); + + // BC: Support deprecated arrow block syntax + if ('{' === $parse->token->value) { + trigger_error('Arrow block syntax `=> { ... }`', E_USER_DEPRECATED); + goto block; + } + + return $this->expression($parse, 0); + } else if ('{' === $parse->token->value) { + block: $line= $parse->token->line; + $parse->forward(); + $statements= $this->statements($parse); + $parse->expecting('}', $context); + return new Block($statements, $line); + } else { + $parse->expecting('=> or { ... }', $context); + return null; + } + } + public function body($id, $func) { $this->body[$id]= $func->bindTo($this, static::class); } @@ -1689,11 +1709,8 @@ public function closure($parse, $static) { $return= null; } - $parse->expecting('{', 'function'); - $statements= $this->statements($parse); - $parse->expecting('}', 'function'); - - return new ClosureExpression(new Signature($parameters, $return, false, $line), $use, $statements, $static, $line); + $scope= $this->scope($parse, 'function'); + return new ClosureExpression(new Signature($parameters, $return, false, $line), $use, $scope, $static, $line); } public function block($parse) { diff --git a/src/test/php/lang/ast/unittest/parse/BracedTest.class.php b/src/test/php/lang/ast/unittest/parse/BracedTest.class.php index 6fd10f5..aee4249 100755 --- a/src/test/php/lang/ast/unittest/parse/BracedTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/BracedTest.class.php @@ -1,6 +1,7 @@ assertParsed( - [new Braced(new ClosureExpression($signature, null, [], false, self::LINE), self::LINE)], + [new Braced(new ClosureExpression($signature, null, new Block([], self::LINE), false, self::LINE), self::LINE)], '(function(T &$arg) { });' ); } diff --git a/src/test/php/lang/ast/unittest/parse/ClosuresTest.class.php b/src/test/php/lang/ast/unittest/parse/ClosuresTest.class.php index 25d40af..1004976 100755 --- a/src/test/php/lang/ast/unittest/parse/ClosuresTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/ClosuresTest.class.php @@ -2,6 +2,7 @@ use lang\ast\nodes\{ BinaryExpression, + Block, Braced, ClosureExpression, InvokeExpression, @@ -19,22 +20,28 @@ class ClosuresTest extends ParseTest { #[Before] public function returns() { - $this->returns= new ReturnStatement( - new BinaryExpression( - new Variable('a', self::LINE), - '+', - new Literal('1', self::LINE), + $this->returns= new Block( + [new ReturnStatement( + new BinaryExpression( + new Variable('a', self::LINE), + '+', + new Literal('1', self::LINE), + self::LINE + ), self::LINE - ), + )], self::LINE ); } #[Before] public function invoke() { - $this->invoke= new InvokeExpression( - new Literal('var_dump', self::LINE), - [new Literal('true', self::LINE)], + $this->invoke= new Block( + [new InvokeExpression( + new Literal('var_dump', self::LINE), + [new Literal('true', self::LINE)], + self::LINE + )], self::LINE ); } @@ -42,16 +49,25 @@ public function invoke() { #[Test] public function with_body() { $this->assertParsed( - [new ClosureExpression(new Signature([], null, false, self::LINE), null, [$this->returns], false, self::LINE)], + [new ClosureExpression(new Signature([], null, false, self::LINE), null, $this->returns, false, self::LINE)], 'function() { return $a + 1; };' ); } + #[Test] + public function single_expression() { + $scope= new Literal('true', self::LINE); + $this->assertParsed( + [new ClosureExpression(new Signature([], null, false, self::LINE), null, $scope, false, self::LINE)], + 'function() => true;' + ); + } + #[Test] public function with_param() { $params= [new Parameter('a', null, null, false, false, null, null, null, self::LINE)]; $this->assertParsed( - [new ClosureExpression(new Signature($params, null, false, self::LINE), null, [$this->returns], false, self::LINE)], + [new ClosureExpression(new Signature($params, null, false, self::LINE), null, $this->returns, false, self::LINE)], 'function($a) { return $a + 1; };' ); } @@ -59,7 +75,7 @@ public function with_param() { #[Test] public function with_use_by_value() { $this->assertParsed( - [new ClosureExpression(new Signature([], null, false, self::LINE), ['$a', '$b'], [$this->returns], false, self::LINE)], + [new ClosureExpression(new Signature([], null, false, self::LINE), ['$a', '$b'], $this->returns, false, self::LINE)], 'function() use($a, $b) { return $a + 1; };' ); } @@ -67,7 +83,7 @@ public function with_use_by_value() { #[Test] public function with_use_and_return() { $this->assertParsed( - [new ClosureExpression(new Signature([], new Type('int'), false, self::LINE), ['$a'], [$this->returns], false, self::LINE)], + [new ClosureExpression(new Signature([], new Type('int'), false, self::LINE), ['$a'], $this->returns, false, self::LINE)], 'function() use($a): int { return $a + 1; };' ); } @@ -75,7 +91,7 @@ public function with_use_and_return() { #[Test] public function with_use_by_reference() { $this->assertParsed( - [new ClosureExpression(new Signature([], null, false, self::LINE), ['$a', '&$b'], [$this->returns], false, self::LINE)], + [new ClosureExpression(new Signature([], null, false, self::LINE), ['$a', '&$b'], $this->returns, false, self::LINE)], 'function() use($a, &$b) { return $a + 1; };' ); } @@ -83,7 +99,7 @@ public function with_use_by_reference() { #[Test] public function with_return_type() { $this->assertParsed( - [new ClosureExpression(new Signature([], new Type('int'), false, self::LINE), null, [$this->invoke], false, self::LINE)], + [new ClosureExpression(new Signature([], new Type('int'), false, self::LINE), null, $this->invoke, false, self::LINE)], 'function(): int { var_dump(true); };' ); } @@ -91,7 +107,7 @@ public function with_return_type() { #[Test] public function with_nullable_return_type() { $this->assertParsed( - [new ClosureExpression(new Signature([], new Type('?int'), false, self::LINE), null, [$this->invoke], false, self::LINE)], + [new ClosureExpression(new Signature([], new Type('?int'), false, self::LINE), null, $this->invoke, false, self::LINE)], 'function(): ?int { var_dump(true); };' ); } @@ -99,7 +115,7 @@ public function with_nullable_return_type() { #[Test] public function static_function() { $this->assertParsed( - [new ClosureExpression(new Signature([], null, false, self::LINE), null, [$this->invoke], true, self::LINE)], + [new ClosureExpression(new Signature([], null, false, self::LINE), null, $this->invoke, true, self::LINE)], 'static function() { var_dump(true); };' ); } @@ -110,7 +126,7 @@ public function iife_with_statement() { $this->assertParsed( [new InvokeExpression( new Braced( - new ClosureExpression(new Signature([], null, false, self::LINE), null, [$this->invoke], false, self::LINE), + new ClosureExpression(new Signature([], null, false, self::LINE), null, $this->invoke, false, self::LINE), self::LINE ), [], diff --git a/src/test/php/lang/ast/unittest/parse/CommentTest.class.php b/src/test/php/lang/ast/unittest/parse/CommentTest.class.php index 4f3fbbb..d6d34f2 100755 --- a/src/test/php/lang/ast/unittest/parse/CommentTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/CommentTest.class.php @@ -1,6 +1,6 @@ declare(new Method(['public'], '__construct', new Signature([], null, false, 4), [], null, new Comment('/** @api */', 3), 4)); + $class->declare(new Method( + ['public'], + '__construct', + new Signature([], null, false, 4), + new Block([], 4), + null, + new Comment('/** @api */', 3), 4) + ); $this->assertParsed([$class], ' class T { diff --git a/src/test/php/lang/ast/unittest/parse/FunctionsTest.class.php b/src/test/php/lang/ast/unittest/parse/FunctionsTest.class.php index 40c98c7..116ed52 100755 --- a/src/test/php/lang/ast/unittest/parse/FunctionsTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/FunctionsTest.class.php @@ -4,6 +4,7 @@ ArrayLiteral, Assignment, BinaryExpression, + Block, Braced, FunctionDeclaration, Literal, @@ -15,9 +16,10 @@ YieldFromExpression }; use lang\ast\types\{IsFunction, IsLiteral, IsNullable, IsUnion, IsValue}; -use test\{Assert, Test, Values}; +use test\{Assert, Before, Test, Values}; class FunctionsTest extends ParseTest { + private $empty; /** @return iterable */ private function types() { @@ -33,18 +35,32 @@ private function types() { yield ['(function(): string)', new IsFunction([], new IsLiteral('string'))]; } + #[Before] + public function empty() { + $this->empty= new Block([], self::LINE); + } + #[Test] public function empty_function_without_parameters() { $this->assertParsed( - [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), [], self::LINE)], + [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), $this->empty, self::LINE)], 'function a() { }' ); } + #[Test] + public function single_expression_function() { + $scope= new Literal('"A"', self::LINE); + $this->assertParsed( + [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), $scope, self::LINE)], + 'function a() => "A";' + ); + } + #[Test] public function returning_by_reference() { $this->assertParsed( - [new FunctionDeclaration('a', new Signature([], null, true, self::LINE), [], self::LINE)], + [new FunctionDeclaration('a', new Signature([], null, true, self::LINE), $this->empty, self::LINE)], 'function &a() { }' ); } @@ -53,8 +69,8 @@ public function returning_by_reference() { public function two_functions() { $this->assertParsed( [ - new FunctionDeclaration('a', new Signature([], null, false, self::LINE), [], self::LINE), - new FunctionDeclaration('b', new Signature([], null, false, self::LINE), [], self::LINE) + new FunctionDeclaration('a', new Signature([], null, false, self::LINE), $this->empty, self::LINE), + new FunctionDeclaration('b', new Signature([], null, false, self::LINE), $this->empty, self::LINE) ], 'function a() { } function b() { }' ); @@ -64,7 +80,7 @@ public function two_functions() { public function with_parameter($name) { $params= [new Parameter($name, null, null, false, false, null, null, null, self::LINE)]; $this->assertParsed( - [new FunctionDeclaration('a', new Signature($params, null, false, self::LINE), [], self::LINE)], + [new FunctionDeclaration('a', new Signature($params, null, false, self::LINE), $this->empty, self::LINE)], 'function a($'.$name.') { }' ); } @@ -73,7 +89,7 @@ public function with_parameter($name) { public function with_reference_parameter() { $params= [new Parameter('param', null, null, true, false, null, null, null, self::LINE)]; $this->assertParsed( - [new FunctionDeclaration('a', new Signature($params, null, false, self::LINE), [], self::LINE)], + [new FunctionDeclaration('a', new Signature($params, null, false, self::LINE), $this->empty, self::LINE)], 'function a(&$param) { }' ); } @@ -82,7 +98,7 @@ public function with_reference_parameter() { public function dangling_comma_in_parameter_lists() { $params= [new Parameter('param', null, null, false, false, null, null, null, self::LINE)]; $this->assertParsed( - [new FunctionDeclaration('a', new Signature($params, null, false, self::LINE), [], self::LINE)], + [new FunctionDeclaration('a', new Signature($params, null, false, self::LINE), $this->empty, self::LINE)], 'function a($param, ) { }' ); } @@ -91,7 +107,7 @@ public function dangling_comma_in_parameter_lists() { public function with_typed_parameter($declaration, $expected) { $params= [new Parameter('param', $expected, null, false, false, null, null, null, self::LINE)]; $this->assertParsed( - [new FunctionDeclaration('a', new Signature($params, null, false, self::LINE), [], self::LINE)], + [new FunctionDeclaration('a', new Signature($params, null, false, self::LINE), $this->empty, self::LINE)], 'function a('.$declaration.' $param) { }' ); } @@ -100,7 +116,7 @@ public function with_typed_parameter($declaration, $expected) { public function with_nullable_typed_parameter() { $params= [new Parameter('param', new IsNullable(new IsLiteral('string')), null, false, false, null, null, null, self::LINE)]; $this->assertParsed( - [new FunctionDeclaration('a', new Signature($params, null, false, self::LINE), [], self::LINE)], + [new FunctionDeclaration('a', new Signature($params, null, false, self::LINE), $this->empty, self::LINE)], 'function a(?string $param) { }' ); } @@ -109,7 +125,7 @@ public function with_nullable_typed_parameter() { public function with_variadic_parameter() { $params= [new Parameter('param', null, null, false, true, null, null, null, self::LINE)]; $this->assertParsed( - [new FunctionDeclaration('a', new Signature($params, null, false, self::LINE), [], self::LINE)], + [new FunctionDeclaration('a', new Signature($params, null, false, self::LINE), $this->empty, self::LINE)], 'function a(... $param) { }' ); } @@ -118,7 +134,7 @@ public function with_variadic_parameter() { public function with_optional_parameter() { $params= [new Parameter('param', null, new Literal('null', self::LINE), false, false, null, null, null, self::LINE)]; $this->assertParsed( - [new FunctionDeclaration('a', new Signature($params, null, false, self::LINE), [], self::LINE)], + [new FunctionDeclaration('a', new Signature($params, null, false, self::LINE), $this->empty, self::LINE)], 'function a($param= null) { }' ); } @@ -127,7 +143,7 @@ public function with_optional_parameter() { public function with_parameter_named_function() { $params= [new Parameter('function', null, null, false, false, null, null, null, self::LINE)]; $this->assertParsed( - [new FunctionDeclaration('a', new Signature($params, null, false, self::LINE), [], self::LINE)], + [new FunctionDeclaration('a', new Signature($params, null, false, self::LINE), $this->empty, self::LINE)], 'function a($function, ) { }' ); } @@ -136,7 +152,7 @@ public function with_parameter_named_function() { public function with_typed_parameter_named_function() { $params= [new Parameter('function', new IsFunction([], new IsLiteral('void')), null, false, false, null, null, null, self::LINE)]; $this->assertParsed( - [new FunctionDeclaration('a', new Signature($params, null, false, self::LINE), [], self::LINE)], + [new FunctionDeclaration('a', new Signature($params, null, false, self::LINE), $this->empty, self::LINE)], 'function a((function(): void) $function) { }' ); } @@ -144,7 +160,7 @@ public function with_typed_parameter_named_function() { #[Test, Values(from: 'types')] public function with_return_type($declaration, $expected) { $this->assertParsed( - [new FunctionDeclaration('a', new Signature([], $expected, false, self::LINE), [], self::LINE)], + [new FunctionDeclaration('a', new Signature([], $expected, false, self::LINE), $this->empty, self::LINE)], 'function a(): '.$declaration.' { }' ); } @@ -153,7 +169,7 @@ public function with_return_type($declaration, $expected) { public function generator() { $yield= new YieldExpression(null, null, self::LINE); $this->assertParsed( - [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), [$yield], self::LINE)], + [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), new Block([$yield], self::LINE), self::LINE)], 'function a() { yield; }' ); } @@ -162,7 +178,7 @@ public function generator() { public function generator_with_value() { $yield= new YieldExpression(null, new Literal('1', self::LINE), self::LINE); $this->assertParsed( - [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), [$yield], self::LINE)], + [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), new Block([$yield], self::LINE), self::LINE)], 'function a() { yield 1; }' ); } @@ -171,7 +187,7 @@ public function generator_with_value() { public function generator_with_key_and_value() { $yield= new YieldExpression(new Literal('"number"', self::LINE), new Literal('1', self::LINE), self::LINE); $this->assertParsed( - [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), [$yield], self::LINE)], + [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), new Block([$yield], self::LINE), self::LINE)], 'function a() { yield "number" => 1; }' ); } @@ -180,7 +196,7 @@ public function generator_with_key_and_value() { public function generator_delegation() { $yield= new YieldFromExpression(new ArrayLiteral([], self::LINE), self::LINE); $this->assertParsed( - [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), [$yield], self::LINE)], + [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), new Block([$yield], self::LINE), self::LINE)], 'function a() { yield from []; }' ); } @@ -194,7 +210,7 @@ public function assign_to_yield() { self::LINE ); $this->assertParsed( - [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), [$yield], self::LINE)], + [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), new Block([$yield], self::LINE), self::LINE)], 'function a() { $value= yield; }' ); } @@ -208,7 +224,7 @@ public function assign_to_yield_with_braced() { self::LINE ); $this->assertParsed( - [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), [$yield], self::LINE)], + [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), new Block([$yield], self::LINE), self::LINE)], 'function a() { $value= yield (1); }' ); } @@ -223,7 +239,7 @@ public function assign_to_yield_in_braces() { self::LINE ); $this->assertParsed( - [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), [$yield], self::LINE)], + [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), new Block([$yield], self::LINE), self::LINE)], 'function a() { $value= (yield); }' ); } @@ -237,7 +253,7 @@ public function assign_to_yield_in_array($declaration) { self::LINE ); $this->assertParsed( - [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), [$yield], self::LINE)], + [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), new Block([$yield], self::LINE), self::LINE)], $declaration ); } @@ -251,7 +267,7 @@ public function assign_to_yield_in_map($declaration) { self::LINE ); $this->assertParsed( - [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), [$yield], self::LINE)], + [new FunctionDeclaration('a', new Signature([], null, false, self::LINE), new Block([$yield], self::LINE), self::LINE)], $declaration ); } diff --git a/src/test/php/lang/ast/unittest/parse/LambdasTest.class.php b/src/test/php/lang/ast/unittest/parse/LambdasTest.class.php index ad6ce41..9c410ed 100755 --- a/src/test/php/lang/ast/unittest/parse/LambdasTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/LambdasTest.class.php @@ -42,6 +42,33 @@ public function short_closure_as_arg() { #[Test] public function short_closure_with_block() { + $this->assertParsed( + [new LambdaExpression( + new Signature([$this->parameter], null, false, self::LINE), + new Block([new ReturnStatement($this->expression, self::LINE)], self::LINE), + false, + self::LINE + )], + 'fn($a) { return $a + 1; };' + ); + } + + #[Test] + public function static_short_closure_with_block() { + $this->assertParsed( + [new LambdaExpression( + new Signature([$this->parameter], null, false, self::LINE), + new Block([new ReturnStatement($this->expression, self::LINE)], self::LINE), + true, + self::LINE + )], + 'static fn($a) { return $a + 1; };' + ); + } + + /** @deprecated */ + #[Test] + public function short_closure_with_arrow_and_block() { $this->assertParsed( [new LambdaExpression( new Signature([$this->parameter], null, false, self::LINE), @@ -51,5 +78,6 @@ public function short_closure_with_block() { )], 'fn($a) => { return $a + 1; };' ); + \xp::gc(); // Swallow deprecation warning } } \ No newline at end of file diff --git a/src/test/php/lang/ast/unittest/parse/MatchExpressionTest.class.php b/src/test/php/lang/ast/unittest/parse/MatchExpressionTest.class.php index e6e6b0c..006b8ac 100755 --- a/src/test/php/lang/ast/unittest/parse/MatchExpressionTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MatchExpressionTest.class.php @@ -36,11 +36,24 @@ public function match() { } #[Test] - public function match_with_block() { + public function match_with_default_block() { $default= new Block([new ReturnStatement(new Literal('false', self::LINE), self::LINE)], self::LINE); $this->assertParsed( [new MatchExpression(new Variable('arg', self::LINE), [], $default, self::LINE)], - 'match ($arg) { default => { return false; } };' + 'match ($arg) { default { return false; } };' + ); + } + + #[Test] + public function match_with_case_block() { + $cases= [new MatchCondition( + [new Literal('0', self::LINE)], + new Block([new ReturnStatement(new Literal('false', self::LINE), self::LINE)], self::LINE), + self::LINE + )]; + $this->assertParsed( + [new MatchExpression(new Variable('arg', self::LINE), $cases, null, self::LINE)], + 'match ($arg) { 0 { return false; } };' ); } diff --git a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php index 9320a31..172374b 100755 --- a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php @@ -20,9 +20,10 @@ Parameter }; use lang\ast\types\{IsFunction, IsLiteral, IsNullable, IsUnion, IsValue, IsGeneric}; -use test\{Assert, Test, Values}; +use test\{Assert, Before, Test, Values}; class MembersTest extends ParseTest { + private $empty; /** @return iterable */ private function types() { @@ -38,6 +39,11 @@ private function types() { yield ['(function(): string)', new IsFunction([], new IsLiteral('string'))]; } + #[Before] + public function empty() { + $this->empty= new Block([], self::LINE); + } + #[Test] public function private_instance_property() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); @@ -66,7 +72,7 @@ public function private_instance_properties() { #[Test] public function private_instance_method() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); - $class->declare(new Method(['private'], 'a', new Signature([], null, false, self::LINE), [], null, null, self::LINE)); + $class->declare(new Method(['private'], 'a', new Signature([], null, false, self::LINE), $this->empty, null, null, self::LINE)); $this->assertParsed([$class], 'class A { private function a() { } }'); } @@ -74,7 +80,7 @@ public function private_instance_method() { #[Test] public function private_static_method() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); - $class->declare(new Method(['private', 'static'], 'a', new Signature([], null, false, self::LINE), [], null, null, self::LINE)); + $class->declare(new Method(['private', 'static'], 'a', new Signature([], null, false, self::LINE), $this->empty, null, null, self::LINE)); $this->assertParsed([$class], 'class A { private static function a() { } }'); } @@ -82,11 +88,20 @@ public function private_static_method() { #[Test] public function method_returning_reference() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); - $class->declare(new Method(['private', 'static'], 'a', new Signature([], null, true, self::LINE), [], null, null, self::LINE)); + $class->declare(new Method(['private', 'static'], 'a', new Signature([], null, true, self::LINE), $this->empty, null, null, self::LINE)); $this->assertParsed([$class], 'class A { private static function &a() { } }'); } + #[Test] + public function single_expression_method() { + $scope= new Literal('"A"', self::LINE); + $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); + $class->declare(new Method(['private'], 'a', new Signature([], null, false, self::LINE), $scope, null, null, self::LINE)); + + $this->assertParsed([$class], 'class A { private function a() => "A"; }'); + } + #[Test] public function class_constant() { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); @@ -116,7 +131,7 @@ public function private_class_constant() { public function method_with_typed_parameter($declaration, $expected) { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); $params= [new Parameter('param', $expected, null, false, false, null, null, null, self::LINE)]; - $class->declare(new Method(['public'], 'a', new Signature($params, null, false, self::LINE), [], null, null, self::LINE)); + $class->declare(new Method(['public'], 'a', new Signature($params, null, false, self::LINE), $this->empty, null, null, self::LINE)); $this->assertParsed([$class], 'class A { public function a('.$declaration.' $param) { } }'); } @@ -124,7 +139,7 @@ public function method_with_typed_parameter($declaration, $expected) { #[Test, Values(from: 'types')] public function method_with_return_type($declaration, $expected) { $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); - $class->declare(new Method(['public'], 'a', new Signature([], $expected, false, self::LINE), [], null, null, self::LINE)); + $class->declare(new Method(['public'], 'a', new Signature([], $expected, false, self::LINE), $this->empty, null, null, self::LINE)); $this->assertParsed([$class], 'class A { public function a(): '.$declaration.' { } }'); } @@ -133,7 +148,7 @@ public function method_with_return_type($declaration, $expected) { public function method_with_annotation() { $annotations= new Annotations(['Test' => []], self::LINE); $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); - $class->declare(new Method(['public'], 'a', new Signature([], null, false, self::LINE), [], $annotations, null, self::LINE)); + $class->declare(new Method(['public'], 'a', new Signature([], null, false, self::LINE), $this->empty, $annotations, null, self::LINE)); $this->assertParsed([$class], 'class A { #[Test] public function a() { } }'); } @@ -142,7 +157,7 @@ public function method_with_annotation() { public function method_with_annotations() { $annotations= new Annotations(['Test' => [], 'Ignore' => [new Literal('"Not implemented"', self::LINE)]], self::LINE); $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); - $class->declare(new Method(['public'], 'a', new Signature([], null, false, self::LINE), [], $annotations, null, self::LINE)); + $class->declare(new Method(['public'], 'a', new Signature([], null, false, self::LINE), $this->empty, $annotations, null, self::LINE)); $this->assertParsed([$class], 'class A { #[Test, Ignore("Not implemented")] public function a() { } }'); } @@ -401,7 +416,7 @@ public function asymmetric_property() { public function asymmetric_property_as_constructor_argument() { $params= [new Parameter('a', new IsLiteral('int'), null, false, false, ['private(set)'], null, null, self::LINE)]; $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); - $class->declare(new Method(['public'], '__construct', new Signature($params, null, false, self::LINE), [], null, null, self::LINE)); + $class->declare(new Method(['public'], '__construct', new Signature($params, null, false, self::LINE), $this->empty, null, null, self::LINE)); $this->assertParsed([$class], 'class A { public function __construct(private(set) int $a) { } }'); }