1
0
mirror of https://github.com/danog/PHP-Parser.git synced 2024-11-30 04:19:30 +01:00

[PHP 7.4] Add support for arrow functions (#602)

Per RFC https://wiki.php.net/rfc/arrow_functions_v2.
This commit is contained in:
Tomáš Votruba 2019-05-09 14:17:28 +02:00 committed by Nikita Popov
parent 78d9985d11
commit f3b19c19ef
20 changed files with 3177 additions and 2688 deletions

View File

@ -27,7 +27,7 @@ reserved_non_modifiers:
| T_FINALLY | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO | T_FINALLY | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO
| T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT | T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT
| T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS | T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS
| T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_HALT_COMPILER | T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_HALT_COMPILER | T_FN
; ;
semi_reserved: semi_reserved:

View File

@ -27,7 +27,7 @@ reserved_non_modifiers:
| T_FINALLY | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO | T_FINALLY | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO
| T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT | T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT
| T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS | T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS
| T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_HALT_COMPILER | T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_HALT_COMPILER | T_FN
; ;
semi_reserved: semi_reserved:
@ -729,6 +729,12 @@ expr:
| T_YIELD expr { $$ = Expr\Yield_[$2, null]; } | T_YIELD expr { $$ = Expr\Yield_[$2, null]; }
| T_YIELD expr T_DOUBLE_ARROW expr { $$ = Expr\Yield_[$4, $2]; } | T_YIELD expr T_DOUBLE_ARROW expr { $$ = Expr\Yield_[$4, $2]; }
| T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; } | T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; }
| T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
{ $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $2, 'params' => $4, 'returnType' => $6, 'expr' => $8]]; }
| T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
{ $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9]]; }
| T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type | T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
block_or_error block_or_error
{ $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $8]]; } { $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $8]]; }

View File

@ -64,6 +64,7 @@
%token T_CONTINUE %token T_CONTINUE
%token T_GOTO %token T_GOTO
%token T_FUNCTION %token T_FUNCTION
%token T_FN
%token T_CONST %token T_CONST
%token T_RETURN %token T_RETURN
%token T_TRY %token T_TRY

View File

@ -4,9 +4,12 @@ namespace PhpParser\Lexer;
use PhpParser\Error; use PhpParser\Error;
use PhpParser\ErrorHandler; use PhpParser\ErrorHandler;
use PhpParser\Parser; use PhpParser\Lexer;
use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
use PhpParser\Lexer\TokenEmulator\TokenEmulatorInterface;
class Emulative extends \PhpParser\Lexer class Emulative extends Lexer
{ {
const PHP_7_3 = '7.3.0dev'; const PHP_7_3 = '7.3.0dev';
const PHP_7_4 = '7.4.0dev'; const PHP_7_4 = '7.4.0dev';
@ -17,13 +20,12 @@ class Emulative extends \PhpParser\Lexer
(?<indentation>\h*)\2(?![a-zA-Z_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x (?<indentation>\h*)\2(?![a-zA-Z_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x
REGEX; REGEX;
const T_COALESCE_EQUAL = 1007; /** @var mixed[] Patches used to reverse changes introduced in the code */
/**
* @var mixed[] Patches used to reverse changes introduced in the code
*/
private $patches = []; private $patches = [];
/** @var TokenEmulatorInterface[] */
private $tokenEmulators = [];
/** /**
* @param mixed[] $options * @param mixed[] $options
*/ */
@ -31,8 +33,14 @@ REGEX;
{ {
parent::__construct($options); parent::__construct($options);
// prepare token emulators
$this->tokenEmulators[] = new FnTokenEmulator();
$this->tokenEmulators[] = new CoaleseEqualTokenEmulator();
// add emulated tokens here // add emulated tokens here
$this->tokenMap[self::T_COALESCE_EQUAL] = Parser\Tokens::T_COALESCE_EQUAL; foreach ($this->tokenEmulators as $emulativeToken) {
$this->tokenMap[$emulativeToken->getTokenId()] = $emulativeToken->getParserTokenId();
}
} }
public function startLexing(string $code, ErrorHandler $errorHandler = null) { public function startLexing(string $code, ErrorHandler $errorHandler = null) {
@ -50,8 +58,13 @@ REGEX;
$preparedCode = $this->processHeredocNowdoc($code); $preparedCode = $this->processHeredocNowdoc($code);
parent::startLexing($preparedCode, $collector); parent::startLexing($preparedCode, $collector);
// 2. emulation of ??= token // add token emulation
$this->processCoaleseEqual($code); foreach ($this->tokenEmulators as $emulativeToken) {
if ($emulativeToken->isEmulationNeeded($code)) {
$this->tokens = $emulativeToken->emulate($code, $this->tokens);
}
}
$this->fixupTokens(); $this->fixupTokens();
$errors = $collector->getErrors(); $errors = $collector->getErrors();
@ -63,41 +76,6 @@ REGEX;
} }
} }
private function isCoalesceEqualEmulationNeeded(string $code): bool
{
// skip version where this works without emulation
if (version_compare(\PHP_VERSION, self::PHP_7_4, '>=')) {
return false;
}
return strpos($code, '??=') !== false;
}
private function processCoaleseEqual(string $code)
{
if ($this->isCoalesceEqualEmulationNeeded($code) === false) {
return;
}
// We need to manually iterate and manage a count because we'll change
// the tokens array on the way
$line = 1;
for ($i = 0, $c = count($this->tokens); $i < $c; ++$i) {
if (isset($this->tokens[$i + 1])) {
if ($this->tokens[$i][0] === T_COALESCE && $this->tokens[$i + 1] === '=') {
array_splice($this->tokens, $i, 2, [
[self::T_COALESCE_EQUAL, '??=', $line]
]);
$c--;
continue;
}
}
if (\is_array($this->tokens[$i])) {
$line += substr_count($this->tokens[$i][1], "\n");
}
}
}
private function isHeredocNowdocEmulationNeeded(string $code): bool private function isHeredocNowdocEmulationNeeded(string $code): bool
{ {
// skip version where this works without emulation // skip version where this works without emulation
@ -155,15 +133,13 @@ REGEX;
private function isEmulationNeeded(string $code): bool private function isEmulationNeeded(string $code): bool
{ {
if ($this->isHeredocNowdocEmulationNeeded($code)) { foreach ($this->tokenEmulators as $emulativeToken) {
return true; if ($emulativeToken->isEmulationNeeded($code)) {
return true;
}
} }
if ($this->isCoalesceEqualEmulationNeeded($code)) { return $this->isHeredocNowdocEmulationNeeded($code);
return true;
}
return false;
} }
private function fixupTokens() private function fixupTokens()

View File

@ -0,0 +1,54 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
use PhpParser\Parser\Tokens;
final class CoaleseEqualTokenEmulator implements TokenEmulatorInterface
{
const T_COALESCE_EQUAL = 1007;
public function getTokenId(): int
{
return self::T_COALESCE_EQUAL;
}
public function getParserTokenId(): int
{
return Tokens::T_COALESCE_EQUAL;
}
public function isEmulationNeeded(string $code) : bool
{
// skip version where this is supported
if (version_compare(\PHP_VERSION, Emulative::PHP_7_4, '>=')) {
return false;
}
return strpos($code, '??=') !== false;
}
public function emulate(string $code, array $tokens): array
{
// We need to manually iterate and manage a count because we'll change
// the tokens array on the way
$line = 1;
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
if (isset($tokens[$i + 1])) {
if ($tokens[$i][0] === T_COALESCE && $tokens[$i + 1] === '=') {
array_splice($tokens, $i, 2, [
[self::T_COALESCE_EQUAL, '??=', $line]
]);
$c--;
continue;
}
}
if (\is_array($tokens[$i])) {
$line += substr_count($tokens[$i][1], "\n");
}
}
return $tokens;
}
}

View File

@ -0,0 +1,66 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
use PhpParser\Parser\Tokens;
final class FnTokenEmulator implements TokenEmulatorInterface
{
const T_FN = 1008;
public function getTokenId(): int
{
return self::T_FN;
}
public function getParserTokenId(): int
{
return Tokens::T_FN;
}
public function isEmulationNeeded(string $code) : bool
{
// skip version where this is supported
if (version_compare(\PHP_VERSION, Emulative::PHP_7_4, '>=')) {
return false;
}
return strpos($code, 'fn') !== false;
}
public function emulate(string $code, array $tokens): array
{
// We need to manually iterate and manage a count because we'll change
// the tokens array on the way
foreach ($tokens as $i => $token) {
if ($token[0] === T_STRING && $token[1] === 'fn') {
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $i);
if ($previousNonSpaceToken !== null && $previousNonSpaceToken[0] === T_OBJECT_OPERATOR) {
continue;
}
$tokens[$i][0] = self::T_FN;
}
}
return $tokens;
}
/**
* @param mixed[] $tokens
* @return mixed[]|null
*/
private function getPreviousNonSpaceToken(array $tokens, int $start)
{
for ($i = $start - 1; $i >= 0; --$i) {
if ($tokens[$i][0] === T_WHITESPACE) {
continue;
}
return $tokens[$i];
}
return null;
}
}

View File

@ -0,0 +1,17 @@
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
interface TokenEmulatorInterface
{
public function getTokenId(): int;
public function getParserTokenId(): int;
public function isEmulationNeeded(string $code): bool;
/**
* @return array Modified Tokens
*/
public function emulate(string $code, array $tokens): array;
}

View File

@ -0,0 +1,71 @@
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\FunctionLike;
class ArrowFunction extends Expr implements FunctionLike
{
/** @var bool */
public $static;
/** @var bool */
public $byRef;
/** @var Node\Param[] */
public $params = [];
/** @var null|Node\Identifier|Node\Name|Node\NullableType */
public $returnType;
/** @var Expr */
public $expr;
/**
* @param array $subNodes Array of the following optional subnodes:
* 'static' => false : Whether the closure is static
* 'byRef' => false : Whether to return by reference
* 'params' => array() : Parameters
* 'returnType' => null : Return type
* 'expr' => Expr : Expression body
* @param array $attributes Additional attributes
*/
public function __construct(array $subNodes = [], array $attributes = []) {
parent::__construct($attributes);
$this->static = $subNodes['static'] ?? false;
$this->byRef = $subNodes['byRef'] ?? false;
$this->params = $subNodes['params'] ?? [];
$returnType = $subNodes['returnType'] ?? null;
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
$this->expr = $subNodes['expr'] ?? null;
}
public function getSubNodeNames() : array {
return ['static', 'byRef', 'params', 'returnType', 'expr'];
}
public function returnsByRef() : bool {
return $this->byRef;
}
public function getParams() : array {
return $this->params;
}
public function getReturnType() {
return $this->returnType;
}
/**
* @return Node\Stmt\Return_[]
*/
public function getStmts() : array {
return [new Node\Stmt\Return_($this->expr)];
}
public function getType() : string {
return 'Expr_ArrowFunction';
}
}

View File

@ -6,9 +6,7 @@ use PhpParser\NodeAbstract;
class Name extends NodeAbstract class Name extends NodeAbstract
{ {
/** /** @var string[] Parts of the name */
* @var string[] Parts of the name
*/
public $parts; public $parts;
private static $specialClassNames = [ private static $specialClassNames = [
@ -237,7 +235,7 @@ class Name extends NodeAbstract
'Expected string, array of parts or Name instance' 'Expected string, array of parts or Name instance'
); );
} }
public function getType() : string { public function getType() : string {
return 'Name'; return 'Name';
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -6,140 +6,142 @@ namespace PhpParser\Parser;
final class Tokens final class Tokens
{ {
const YYERRTOK = 256; const YYERRTOK = 256;
const T_INCLUDE = 257; const T_ARROW_FUNCTION = 257;
const T_INCLUDE_ONCE = 258; const T_INCLUDE = 258;
const T_EVAL = 259; const T_INCLUDE_ONCE = 259;
const T_REQUIRE = 260; const T_EVAL = 260;
const T_REQUIRE_ONCE = 261; const T_REQUIRE = 261;
const T_LOGICAL_OR = 262; const T_REQUIRE_ONCE = 262;
const T_LOGICAL_XOR = 263; const T_LOGICAL_OR = 263;
const T_LOGICAL_AND = 264; const T_LOGICAL_XOR = 264;
const T_PRINT = 265; const T_LOGICAL_AND = 265;
const T_YIELD = 266; const T_PRINT = 266;
const T_DOUBLE_ARROW = 267; const T_YIELD = 267;
const T_YIELD_FROM = 268; const T_DOUBLE_ARROW = 268;
const T_PLUS_EQUAL = 269; const T_YIELD_FROM = 269;
const T_MINUS_EQUAL = 270; const T_PLUS_EQUAL = 270;
const T_MUL_EQUAL = 271; const T_MINUS_EQUAL = 271;
const T_DIV_EQUAL = 272; const T_MUL_EQUAL = 272;
const T_CONCAT_EQUAL = 273; const T_DIV_EQUAL = 273;
const T_MOD_EQUAL = 274; const T_CONCAT_EQUAL = 274;
const T_AND_EQUAL = 275; const T_MOD_EQUAL = 275;
const T_OR_EQUAL = 276; const T_AND_EQUAL = 276;
const T_XOR_EQUAL = 277; const T_OR_EQUAL = 277;
const T_SL_EQUAL = 278; const T_XOR_EQUAL = 278;
const T_SR_EQUAL = 279; const T_SL_EQUAL = 279;
const T_POW_EQUAL = 280; const T_SR_EQUAL = 280;
const T_COALESCE_EQUAL = 281; const T_POW_EQUAL = 281;
const T_COALESCE = 282; const T_COALESCE_EQUAL = 282;
const T_BOOLEAN_OR = 283; const T_COALESCE = 283;
const T_BOOLEAN_AND = 284; const T_BOOLEAN_OR = 284;
const T_IS_EQUAL = 285; const T_BOOLEAN_AND = 285;
const T_IS_NOT_EQUAL = 286; const T_IS_EQUAL = 286;
const T_IS_IDENTICAL = 287; const T_IS_NOT_EQUAL = 287;
const T_IS_NOT_IDENTICAL = 288; const T_IS_IDENTICAL = 288;
const T_SPACESHIP = 289; const T_IS_NOT_IDENTICAL = 289;
const T_IS_SMALLER_OR_EQUAL = 290; const T_SPACESHIP = 290;
const T_IS_GREATER_OR_EQUAL = 291; const T_IS_SMALLER_OR_EQUAL = 291;
const T_SL = 292; const T_IS_GREATER_OR_EQUAL = 292;
const T_SR = 293; const T_SL = 293;
const T_INSTANCEOF = 294; const T_SR = 294;
const T_INC = 295; const T_INSTANCEOF = 295;
const T_DEC = 296; const T_INC = 296;
const T_INT_CAST = 297; const T_DEC = 297;
const T_DOUBLE_CAST = 298; const T_INT_CAST = 298;
const T_STRING_CAST = 299; const T_DOUBLE_CAST = 299;
const T_ARRAY_CAST = 300; const T_STRING_CAST = 300;
const T_OBJECT_CAST = 301; const T_ARRAY_CAST = 301;
const T_BOOL_CAST = 302; const T_OBJECT_CAST = 302;
const T_UNSET_CAST = 303; const T_BOOL_CAST = 303;
const T_POW = 304; const T_UNSET_CAST = 304;
const T_NEW = 305; const T_POW = 305;
const T_CLONE = 306; const T_NEW = 306;
const T_EXIT = 307; const T_CLONE = 307;
const T_IF = 308; const T_EXIT = 308;
const T_ELSEIF = 309; const T_IF = 309;
const T_ELSE = 310; const T_ELSEIF = 310;
const T_ENDIF = 311; const T_ELSE = 311;
const T_LNUMBER = 312; const T_ENDIF = 312;
const T_DNUMBER = 313; const T_LNUMBER = 313;
const T_STRING = 314; const T_DNUMBER = 314;
const T_STRING_VARNAME = 315; const T_STRING = 315;
const T_VARIABLE = 316; const T_STRING_VARNAME = 316;
const T_NUM_STRING = 317; const T_VARIABLE = 317;
const T_INLINE_HTML = 318; const T_NUM_STRING = 318;
const T_CHARACTER = 319; const T_INLINE_HTML = 319;
const T_BAD_CHARACTER = 320; const T_CHARACTER = 320;
const T_ENCAPSED_AND_WHITESPACE = 321; const T_BAD_CHARACTER = 321;
const T_CONSTANT_ENCAPSED_STRING = 322; const T_ENCAPSED_AND_WHITESPACE = 322;
const T_ECHO = 323; const T_CONSTANT_ENCAPSED_STRING = 323;
const T_DO = 324; const T_ECHO = 324;
const T_WHILE = 325; const T_DO = 325;
const T_ENDWHILE = 326; const T_WHILE = 326;
const T_FOR = 327; const T_ENDWHILE = 327;
const T_ENDFOR = 328; const T_FOR = 328;
const T_FOREACH = 329; const T_ENDFOR = 329;
const T_ENDFOREACH = 330; const T_FOREACH = 330;
const T_DECLARE = 331; const T_ENDFOREACH = 331;
const T_ENDDECLARE = 332; const T_DECLARE = 332;
const T_AS = 333; const T_ENDDECLARE = 333;
const T_SWITCH = 334; const T_AS = 334;
const T_ENDSWITCH = 335; const T_SWITCH = 335;
const T_CASE = 336; const T_ENDSWITCH = 336;
const T_DEFAULT = 337; const T_CASE = 337;
const T_BREAK = 338; const T_DEFAULT = 338;
const T_CONTINUE = 339; const T_BREAK = 339;
const T_GOTO = 340; const T_CONTINUE = 340;
const T_FUNCTION = 341; const T_GOTO = 341;
const T_CONST = 342; const T_FUNCTION = 342;
const T_RETURN = 343; const T_FN = 343;
const T_TRY = 344; const T_CONST = 344;
const T_CATCH = 345; const T_RETURN = 345;
const T_FINALLY = 346; const T_TRY = 346;
const T_THROW = 347; const T_CATCH = 347;
const T_USE = 348; const T_FINALLY = 348;
const T_INSTEADOF = 349; const T_THROW = 349;
const T_GLOBAL = 350; const T_USE = 350;
const T_STATIC = 351; const T_INSTEADOF = 351;
const T_ABSTRACT = 352; const T_GLOBAL = 352;
const T_FINAL = 353; const T_STATIC = 353;
const T_PRIVATE = 354; const T_ABSTRACT = 354;
const T_PROTECTED = 355; const T_FINAL = 355;
const T_PUBLIC = 356; const T_PRIVATE = 356;
const T_VAR = 357; const T_PROTECTED = 357;
const T_UNSET = 358; const T_PUBLIC = 358;
const T_ISSET = 359; const T_VAR = 359;
const T_EMPTY = 360; const T_UNSET = 360;
const T_HALT_COMPILER = 361; const T_ISSET = 361;
const T_CLASS = 362; const T_EMPTY = 362;
const T_TRAIT = 363; const T_HALT_COMPILER = 363;
const T_INTERFACE = 364; const T_CLASS = 364;
const T_EXTENDS = 365; const T_TRAIT = 365;
const T_IMPLEMENTS = 366; const T_INTERFACE = 366;
const T_OBJECT_OPERATOR = 367; const T_EXTENDS = 367;
const T_LIST = 368; const T_IMPLEMENTS = 368;
const T_ARRAY = 369; const T_OBJECT_OPERATOR = 369;
const T_CALLABLE = 370; const T_LIST = 370;
const T_CLASS_C = 371; const T_ARRAY = 371;
const T_TRAIT_C = 372; const T_CALLABLE = 372;
const T_METHOD_C = 373; const T_CLASS_C = 373;
const T_FUNC_C = 374; const T_TRAIT_C = 374;
const T_LINE = 375; const T_METHOD_C = 375;
const T_FILE = 376; const T_FUNC_C = 376;
const T_COMMENT = 377; const T_LINE = 377;
const T_DOC_COMMENT = 378; const T_FILE = 378;
const T_OPEN_TAG = 379; const T_COMMENT = 379;
const T_OPEN_TAG_WITH_ECHO = 380; const T_DOC_COMMENT = 380;
const T_CLOSE_TAG = 381; const T_OPEN_TAG = 381;
const T_WHITESPACE = 382; const T_OPEN_TAG_WITH_ECHO = 382;
const T_START_HEREDOC = 383; const T_CLOSE_TAG = 383;
const T_END_HEREDOC = 384; const T_WHITESPACE = 384;
const T_DOLLAR_OPEN_CURLY_BRACES = 385; const T_START_HEREDOC = 385;
const T_CURLY_OPEN = 386; const T_END_HEREDOC = 386;
const T_PAAMAYIM_NEKUDOTAYIM = 387; const T_DOLLAR_OPEN_CURLY_BRACES = 387;
const T_NAMESPACE = 388; const T_CURLY_OPEN = 388;
const T_NS_C = 389; const T_PAAMAYIM_NEKUDOTAYIM = 389;
const T_DIR = 390; const T_NAMESPACE = 390;
const T_NS_SEPARATOR = 391; const T_NS_C = 391;
const T_ELLIPSIS = 392; const T_DIR = 392;
const T_NS_SEPARATOR = 393;
const T_ELLIPSIS = 394;
} }

View File

@ -582,6 +582,15 @@ class Standard extends PrettyPrinterAbstract
. ' {' . $this->pStmts($node->stmts) . $this->nl . '}'; . ' {' . $this->pStmts($node->stmts) . $this->nl . '}';
} }
protected function pExpr_ArrowFunction(Expr\ArrowFunction $node) {
return ($node->static ? 'static ' : '')
. 'fn' . ($node->byRef ? '&' : '')
. '(' . $this->pCommaSeparated($node->params) . ')'
. (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '')
. ' => '
. $this->p($node->expr);
}
protected function pExpr_ClosureUse(Expr\ClosureUse $node) { protected function pExpr_ClosureUse(Expr\ClosureUse $node) {
return ($node->byRef ? '&' : '') . $this->p($node->var); return ($node->byRef ? '&' : '') . $this->p($node->var);
} }

View File

@ -1195,6 +1195,7 @@ abstract class PrettyPrinterAbstract
$this->removalMap = [ $this->removalMap = [
'Expr_ArrayDimFetch->dim' => $stripBoth, 'Expr_ArrayDimFetch->dim' => $stripBoth,
'Expr_ArrayItem->key' => $stripDoubleArrow, 'Expr_ArrayItem->key' => $stripDoubleArrow,
'Expr_ArrowFunction->returnType' => $stripColon,
'Expr_Closure->returnType' => $stripColon, 'Expr_Closure->returnType' => $stripColon,
'Expr_Exit->expr' => $stripBoth, 'Expr_Exit->expr' => $stripBoth,
'Expr_Ternary->if' => $stripBoth, 'Expr_Ternary->if' => $stripBoth,
@ -1231,6 +1232,7 @@ abstract class PrettyPrinterAbstract
$this->insertionMap = [ $this->insertionMap = [
'Expr_ArrayDimFetch->dim' => ['[', false, null, null], 'Expr_ArrayDimFetch->dim' => ['[', false, null, null],
'Expr_ArrayItem->key' => [null, false, null, ' => '], 'Expr_ArrayItem->key' => [null, false, null, ' => '],
'Expr_ArrowFunction->returnType' => [')', false, ' : ', null],
'Expr_Closure->returnType' => [')', false, ' : ', null], 'Expr_Closure->returnType' => [')', false, ' : ', null],
'Expr_Ternary->if' => ['?', false, ' ', ' '], 'Expr_Ternary->if' => ['?', false, ' ', ' '],
'Expr_Yield->key' => [\T_YIELD, false, null, ' => '], 'Expr_Yield->key' => [\T_YIELD, false, null, ' => '],
@ -1274,6 +1276,7 @@ abstract class PrettyPrinterAbstract
// comma-separated lists // comma-separated lists
'Expr_Array->items' => ', ', 'Expr_Array->items' => ', ',
'Expr_ArrowFunction->params' => ', ',
'Expr_Closure->params' => ', ', 'Expr_Closure->params' => ', ',
'Expr_Closure->uses' => ', ', 'Expr_Closure->uses' => ', ',
'Expr_FuncCall->args' => ', ', 'Expr_FuncCall->args' => ', ',

View File

@ -26,7 +26,7 @@ class EmulativeTest extends LexerTest
/** /**
* @dataProvider provideTestReplaceKeywords * @dataProvider provideTestReplaceKeywords
*/ */
public function testNoReplaceKeywordsAfterObjectOperator($keyword) { public function testNoReplaceKeywordsAfterObjectOperator(string $keyword) {
$lexer = $this->getLexer(); $lexer = $this->getLexer();
$lexer->startLexing('<?php ->' . $keyword); $lexer->startLexing('<?php ->' . $keyword);
@ -35,8 +35,23 @@ class EmulativeTest extends LexerTest
$this->assertSame(0, $lexer->getNextToken()); $this->assertSame(0, $lexer->getNextToken());
} }
/**
* @dataProvider provideTestReplaceKeywords
*/
public function testNoReplaceKeywordsAfterObjectOperatorWithSpaces(string $keyword) {
$lexer = $this->getLexer();
$lexer->startLexing('<?php -> ' . $keyword);
$this->assertSame(Tokens::T_OBJECT_OPERATOR, $lexer->getNextToken());
$this->assertSame(Tokens::T_STRING, $lexer->getNextToken());
$this->assertSame(0, $lexer->getNextToken());
}
public function provideTestReplaceKeywords() { public function provideTestReplaceKeywords() {
return [ return [
// PHP 7.4
['fn', Tokens::T_FN],
// PHP 5.5 // PHP 5.5
['finally', Tokens::T_FINALLY], ['finally', Tokens::T_FINALLY],
['yield', Tokens::T_YIELD], ['yield', Tokens::T_YIELD],
@ -88,7 +103,7 @@ class EmulativeTest extends LexerTest
*/ */
public function testErrorAfterEmulation($code) { public function testErrorAfterEmulation($code) {
$errorHandler = new ErrorHandler\Collecting; $errorHandler = new ErrorHandler\Collecting;
$lexer = $this->getLexer([]); $lexer = $this->getLexer();
$lexer->startLexing('<?php ' . $code . "\0", $errorHandler); $lexer->startLexing('<?php ' . $code . "\0", $errorHandler);
$errors = $errorHandler->getErrors(); $errors = $errorHandler->getErrors();

View File

@ -212,8 +212,8 @@ interface A extends C, D {
public function a(A $a) : A; public function a(A $a) : A;
} }
function fn(A $a) : A {} function f(A $a) : A {}
function fn2(array $a) : array {} function f2(array $a) : array {}
function(A $a) : A {}; function(A $a) : A {};
function fn3(?A $a) : ?A {} function fn3(?A $a) : ?A {}
@ -249,10 +249,10 @@ interface A extends \NS\C, \NS\D
{ {
public function a(\NS\A $a) : \NS\A; public function a(\NS\A $a) : \NS\A;
} }
function fn(\NS\A $a) : \NS\A function f(\NS\A $a) : \NS\A
{ {
} }
function fn2(array $a) : array function f2(array $a) : array
{ {
} }
function (\NS\A $a) : \NS\A { function (\NS\A $a) : \NS\A {

View File

@ -0,0 +1,97 @@
Arrow function
-----
<?php
fn($a)
=>
$a;
-----
$stmts[0]->expr->expr = new Expr\Variable('b');
-----
<?php
fn($a)
=>
$b;
-----
<?php
fn(
$a
) => $a;
-----
$stmts[0]->expr->params[] = new Node\Param(new Expr\Variable('b'));
-----
<?php
fn(
$a, $b
) => $a;
-----
<?php
fn(
$a
)
=>
$a;
-----
// TODO: Format preserving currently not supported
$stmts[0]->expr->params = [];
-----
<?php
fn() => $a;
-----
<?php
fn($a)
: int
=> $a;
-----
$stmts[0]->expr->returnType = new Node\Identifier('bool');
-----
<?php
fn($a)
: bool
=> $a;
-----
<?php
fn($a)
: int
=> $a;
-----
$stmts[0]->expr->returnType = null;
-----
<?php
fn($a)
=> $a;
-----
<?php
fn($a)
: int
=> $a;
static fn($a)
: int
=> $a;
-----
// TODO: Format preserving currently not supported
$stmts[0]->expr->static = true;
$stmts[1]->expr->static = false;
-----
<?php
static fn($a): int => $a;
fn($a): int => $a;
-----
<?php
fn($a)
: int
=> $a;
fn&($a)
: int
=> $a;
-----
// TODO: Format preserving currently not supported
$stmts[0]->expr->byRef = true;
$stmts[1]->expr->byRef = false;
-----
<?php
fn&($a): int => $a;
fn($a): int => $a;

View File

@ -0,0 +1,145 @@
Arrow Functions
-----
<?php
fn(bool $a) => $a;
fn($x = 42) => $x;
static fn(&$x) => $x;
fn&($x) => $x;
fn($x, ...$rest) => $rest;
fn(): int => $x;
-----
!!php7
array(
0: Stmt_Expression(
expr: Expr_ArrowFunction(
static: false
byRef: false
params: array(
0: Param(
type: Identifier(
name: bool
)
byRef: false
variadic: false
var: Expr_Variable(
name: a
)
default: null
)
)
returnType: null
expr: Expr_Variable(
name: a
)
)
)
1: Stmt_Expression(
expr: Expr_ArrowFunction(
static: false
byRef: false
params: array(
0: Param(
type: null
byRef: false
variadic: false
var: Expr_Variable(
name: x
)
default: Scalar_LNumber(
value: 42
)
)
)
returnType: null
expr: Expr_Variable(
name: x
)
)
)
2: Stmt_Expression(
expr: Expr_ArrowFunction(
static: true
byRef: false
params: array(
0: Param(
type: null
byRef: true
variadic: false
var: Expr_Variable(
name: x
)
default: null
)
)
returnType: null
expr: Expr_Variable(
name: x
)
)
)
3: Stmt_Expression(
expr: Expr_ArrowFunction(
static: false
byRef: true
params: array(
0: Param(
type: null
byRef: false
variadic: false
var: Expr_Variable(
name: x
)
default: null
)
)
returnType: null
expr: Expr_Variable(
name: x
)
)
)
4: Stmt_Expression(
expr: Expr_ArrowFunction(
static: false
byRef: false
params: array(
0: Param(
type: null
byRef: false
variadic: false
var: Expr_Variable(
name: x
)
default: null
)
1: Param(
type: null
byRef: false
variadic: true
var: Expr_Variable(
name: rest
)
default: null
)
)
returnType: null
expr: Expr_Variable(
name: rest
)
)
)
5: Stmt_Expression(
expr: Expr_ArrowFunction(
static: false
byRef: false
params: array(
)
returnType: Identifier(
name: int
)
expr: Expr_Variable(
name: x
)
)
)
)

View File

@ -0,0 +1,18 @@
Arrow function
-----
<?php
fn($a) => $a;
fn($x = 42) => $x;
fn(&$x) => $x;
fn&($x) => $x;
static fn($x, ...$rest) => $rest;
fn(): int => $x;
-----
!!php7
fn($a) => $a;
fn($x = 42) => $x;
fn(&$x) => $x;
fn&($x) => $x;
static fn($x, ...$rest) => $rest;
fn(): int => $x;

View File

@ -1,4 +1,4 @@
wget -q https://github.com/php/php-src/archive/php-7.3.0RC1.tar.gz wget -q https://github.com/php/php-src/archive/PHP-7.4.tar.gz
mkdir -p ./data/php-src mkdir -p ./data/php-src
tar -xzf ./php-7.3.0RC1.tar.gz -C ./data/php-src --strip-components=1 tar -xzf ./PHP-7.4.tar.gz -C ./data/php-src --strip-components=1
php -n test_old/run.php --verbose --no-progress PHP7 ./data/php-src php -n test_old/run.php --verbose --no-progress PHP7 ./data/php-src