diff --git a/src/Node/Expression/AnonymousFunctionCreationExpression.php b/src/Node/Expression/AnonymousFunctionCreationExpression.php index 4699eb52..5f39b3df 100644 --- a/src/Node/Expression/AnonymousFunctionCreationExpression.php +++ b/src/Node/Expression/AnonymousFunctionCreationExpression.php @@ -35,6 +35,7 @@ class AnonymousFunctionCreationExpression extends Expression { // FunctionReturnType 'colonToken', + 'questionToken', 'returnType', // FunctionBody diff --git a/src/Node/FunctionReturnType.php b/src/Node/FunctionReturnType.php index 05358bdd..d1b250d5 100644 --- a/src/Node/FunctionReturnType.php +++ b/src/Node/FunctionReturnType.php @@ -11,6 +11,8 @@ trait FunctionReturnType { /** @var Token */ public $colonToken; + /** @var Token|null */ + public $questionToken; /** @var Token | QualifiedName */ public $returnType; } diff --git a/src/Node/MethodDeclaration.php b/src/Node/MethodDeclaration.php index b0c11045..ae90ad38 100644 --- a/src/Node/MethodDeclaration.php +++ b/src/Node/MethodDeclaration.php @@ -29,6 +29,7 @@ class MethodDeclaration extends Node { // FunctionReturnType 'colonToken', + 'questionToken', 'returnType', // FunctionBody diff --git a/src/Node/Parameter.php b/src/Node/Parameter.php index 2dd55470..456bf840 100644 --- a/src/Node/Parameter.php +++ b/src/Node/Parameter.php @@ -10,23 +10,23 @@ use Microsoft\PhpParser\Token; class Parameter extends Node { + /** @var Token|null */ + public $questionToken; /** @var QualifiedName | Token | null */ public $typeDeclaration; /** @var Token | null */ public $byRefToken; /** @var Token | null */ public $dotDotDotToken; - /** @var Token */ public $variableName; - /** @var Token | null */ public $equalsToken; - /** @var null | Expression */ public $default; const CHILD_NAMES = [ + 'questionToken', 'typeDeclaration', 'byRefToken', 'dotDotDotToken', diff --git a/src/Node/Statement/FunctionDeclaration.php b/src/Node/Statement/FunctionDeclaration.php index a7236651..a0877eca 100644 --- a/src/Node/Statement/FunctionDeclaration.php +++ b/src/Node/Statement/FunctionDeclaration.php @@ -28,6 +28,7 @@ class FunctionDeclaration extends StatementNode implements NamespacedNameInterfa // FunctionReturnType 'colonToken', + 'questionToken', 'returnType', // FunctionBody diff --git a/src/Parser.php b/src/Parser.php index fee62bc9..ee100824 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -598,6 +598,7 @@ private function parseParameterFn() { return function ($parentNode) { $parameter = new Parameter(); $parameter->parent = $parentNode; + $parameter->questionToken = $this->eatOptional(TokenKind::QuestionToken); $parameter->typeDeclaration = $this->tryParseParameterTypeDeclaration($parameter); $parameter->byRefToken = $this->eatOptional(TokenKind::AmpersandToken); // TODO add post-parse rule that prevents assignment @@ -1086,6 +1087,10 @@ private function isParameterStartFn() { case TokenKind::VariableName: return true; + + // nullable-type + case TokenKind::QuestionToken: + return true; } // scalar-type @@ -1195,45 +1200,46 @@ private function parseRelativeSpecifier($parentNode) { return null; } - private function parseFunctionType(Node $functionDefinition, $canBeAbstract = false, $isAnonymous = false) { - $functionDefinition->functionKeyword = $this->eat(TokenKind::FunctionKeyword); - $functionDefinition->byRefToken = $this->eatOptional(TokenKind::AmpersandToken); - $functionDefinition->name = $isAnonymous + private function parseFunctionType(Node $functionDeclaration, $canBeAbstract = false, $isAnonymous = false) { + $functionDeclaration->functionKeyword = $this->eat(TokenKind::FunctionKeyword); + $functionDeclaration->byRefToken = $this->eatOptional(TokenKind::AmpersandToken); + $functionDeclaration->name = $isAnonymous ? $this->eatOptional($this->nameOrKeywordOrReservedWordTokens) : $this->eat($this->nameOrKeywordOrReservedWordTokens); - if (isset($functionDefinition->name)) { - $functionDefinition->name->kind = TokenKind::Name; + if (isset($functionDeclaration->name)) { + $functionDeclaration->name->kind = TokenKind::Name; } - if ($isAnonymous && isset($functionDefinition->name)) { + if ($isAnonymous && isset($functionDeclaration->name)) { // Anonymous functions should not have names - $functionDefinition->name = new SkippedToken($functionDefinition->name); // TODO instaed handle this during post-walk + $functionDeclaration->name = new SkippedToken($functionDeclaration->name); // TODO instaed handle this during post-walk } - $functionDefinition->openParen = $this->eat(TokenKind::OpenParenToken); - $functionDefinition->parameters = $this->parseDelimitedList( + $functionDeclaration->openParen = $this->eat(TokenKind::OpenParenToken); + $functionDeclaration->parameters = $this->parseDelimitedList( DelimitedList\ParameterDeclarationList::class, TokenKind::CommaToken, $this->isParameterStartFn(), $this->parseParameterFn(), - $functionDefinition); - $functionDefinition->closeParen = $this->eat(TokenKind::CloseParenToken); + $functionDeclaration); + $functionDeclaration->closeParen = $this->eat(TokenKind::CloseParenToken); if ($isAnonymous) { - $functionDefinition->anonymousFunctionUseClause = $this->parseAnonymousFunctionUseClause($functionDefinition); + $functionDeclaration->anonymousFunctionUseClause = $this->parseAnonymousFunctionUseClause($functionDeclaration); } if ($this->checkToken(TokenKind::ColonToken)) { - $functionDefinition->colonToken = $this->eat(TokenKind::ColonToken); - $functionDefinition->returnType = $this->parseReturnTypeDeclaration($functionDefinition); + $functionDeclaration->colonToken = $this->eat(TokenKind::ColonToken); + $functionDeclaration->questionToken = $this->eatOptional(TokenKind::QuestionToken); + $functionDeclaration->returnType = $this->parseReturnTypeDeclaration($functionDeclaration); } if ($canBeAbstract) { - $functionDefinition->compoundStatementOrSemicolon = $this->eatOptional(TokenKind::SemicolonToken); + $functionDeclaration->compoundStatementOrSemicolon = $this->eatOptional(TokenKind::SemicolonToken); } - if (!isset($functionDefinition->compoundStatementOrSemicolon)) { - $functionDefinition->compoundStatementOrSemicolon = $this->parseCompoundStatement($functionDefinition); + if (!isset($functionDeclaration->compoundStatementOrSemicolon)) { + $functionDeclaration->compoundStatementOrSemicolon = $this->parseCompoundStatement($functionDeclaration); } } diff --git a/tests/cases/parser/abstractMethodDeclaration1.php.tree b/tests/cases/parser/abstractMethodDeclaration1.php.tree index b8ed01bc..0c511a5c 100644 --- a/tests/cases/parser/abstractMethodDeclaration1.php.tree +++ b/tests/cases/parser/abstractMethodDeclaration1.php.tree @@ -65,6 +65,7 @@ "textLength": 1 }, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "kind": "SemicolonToken", diff --git a/tests/cases/parser/abstractMethodDeclaration2.php.tree b/tests/cases/parser/abstractMethodDeclaration2.php.tree index 93fe25f0..107b3c9b 100644 --- a/tests/cases/parser/abstractMethodDeclaration2.php.tree +++ b/tests/cases/parser/abstractMethodDeclaration2.php.tree @@ -62,6 +62,7 @@ "textLength": 1 }, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "kind": "SemicolonToken", diff --git a/tests/cases/parser/abstractMethodDeclaration3.php.tree b/tests/cases/parser/abstractMethodDeclaration3.php.tree index 1e90cdbe..7bf85dea 100644 --- a/tests/cases/parser/abstractMethodDeclaration3.php.tree +++ b/tests/cases/parser/abstractMethodDeclaration3.php.tree @@ -65,6 +65,7 @@ "textLength": 1 }, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/abstractMethodDeclaration4.php.tree b/tests/cases/parser/abstractMethodDeclaration4.php.tree index a447c71f..cf35faf8 100644 --- a/tests/cases/parser/abstractMethodDeclaration4.php.tree +++ b/tests/cases/parser/abstractMethodDeclaration4.php.tree @@ -32,6 +32,7 @@ "textLength": 1 }, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/abstractMethodDeclaration5.php.tree b/tests/cases/parser/abstractMethodDeclaration5.php.tree index 206c6d09..df5460e0 100644 --- a/tests/cases/parser/abstractMethodDeclaration5.php.tree +++ b/tests/cases/parser/abstractMethodDeclaration5.php.tree @@ -64,6 +64,7 @@ "kind": "ColonToken", "textLength": 1 }, + "questionToken": null, "returnType": { "kind": "BoolReservedWord", "textLength": 4 diff --git a/tests/cases/parser/abstractMethodDeclaration6.php.tree b/tests/cases/parser/abstractMethodDeclaration6.php.tree index cfd3b21a..a44a0484 100644 --- a/tests/cases/parser/abstractMethodDeclaration6.php.tree +++ b/tests/cases/parser/abstractMethodDeclaration6.php.tree @@ -69,6 +69,7 @@ "kind": "ColonToken", "textLength": 1 }, + "questionToken": null, "returnType": { "kind": "BoolReservedWord", "textLength": 4 diff --git a/tests/cases/parser/abstractMethodDeclaration7.php.tree b/tests/cases/parser/abstractMethodDeclaration7.php.tree index f706de0b..de44e545 100644 --- a/tests/cases/parser/abstractMethodDeclaration7.php.tree +++ b/tests/cases/parser/abstractMethodDeclaration7.php.tree @@ -61,6 +61,7 @@ "textLength": 1 }, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/anonymousFunctionCreationExpression1.php.tree b/tests/cases/parser/anonymousFunctionCreationExpression1.php.tree index 0c9dd3d2..32bda5b4 100644 --- a/tests/cases/parser/anonymousFunctionCreationExpression1.php.tree +++ b/tests/cases/parser/anonymousFunctionCreationExpression1.php.tree @@ -39,6 +39,7 @@ }, "anonymousFunctionUseClause": null, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/anonymousFunctionCreationExpression10.php.tree b/tests/cases/parser/anonymousFunctionCreationExpression10.php.tree index 37b82b87..a64e6eca 100644 --- a/tests/cases/parser/anonymousFunctionCreationExpression10.php.tree +++ b/tests/cases/parser/anonymousFunctionCreationExpression10.php.tree @@ -82,6 +82,7 @@ "kind": "ColonToken", "textLength": 1 }, + "questionToken": null, "returnType": { "kind": "VoidReservedWord", "textLength": 4 diff --git a/tests/cases/parser/anonymousFunctionCreationExpression2.php.tree b/tests/cases/parser/anonymousFunctionCreationExpression2.php.tree index ee78add8..314b229b 100644 --- a/tests/cases/parser/anonymousFunctionCreationExpression2.php.tree +++ b/tests/cases/parser/anonymousFunctionCreationExpression2.php.tree @@ -36,6 +36,7 @@ }, "anonymousFunctionUseClause": null, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/anonymousFunctionCreationExpression3.php.tree b/tests/cases/parser/anonymousFunctionCreationExpression3.php.tree index ced6862a..d6ec1e68 100644 --- a/tests/cases/parser/anonymousFunctionCreationExpression3.php.tree +++ b/tests/cases/parser/anonymousFunctionCreationExpression3.php.tree @@ -33,6 +33,7 @@ }, "anonymousFunctionUseClause": null, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/anonymousFunctionCreationExpression4.php.tree b/tests/cases/parser/anonymousFunctionCreationExpression4.php.tree index c3c0a8be..067bf294 100644 --- a/tests/cases/parser/anonymousFunctionCreationExpression4.php.tree +++ b/tests/cases/parser/anonymousFunctionCreationExpression4.php.tree @@ -40,6 +40,7 @@ }, "anonymousFunctionUseClause": null, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/anonymousFunctionCreationExpression5.php.tree b/tests/cases/parser/anonymousFunctionCreationExpression5.php.tree index 8d7d05a6..6706ed04 100644 --- a/tests/cases/parser/anonymousFunctionCreationExpression5.php.tree +++ b/tests/cases/parser/anonymousFunctionCreationExpression5.php.tree @@ -53,6 +53,7 @@ }, "anonymousFunctionUseClause": null, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/anonymousFunctionCreationExpression6.php.tree b/tests/cases/parser/anonymousFunctionCreationExpression6.php.tree index 43468e7d..6a03b0ff 100644 --- a/tests/cases/parser/anonymousFunctionCreationExpression6.php.tree +++ b/tests/cases/parser/anonymousFunctionCreationExpression6.php.tree @@ -53,6 +53,7 @@ }, "anonymousFunctionUseClause": null, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/anonymousFunctionCreationExpression7.php.tree b/tests/cases/parser/anonymousFunctionCreationExpression7.php.tree index 81fda948..1978f2dc 100644 --- a/tests/cases/parser/anonymousFunctionCreationExpression7.php.tree +++ b/tests/cases/parser/anonymousFunctionCreationExpression7.php.tree @@ -92,6 +92,7 @@ } }, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/classMethods1.php.tree b/tests/cases/parser/classMethods1.php.tree index 1dde7bb2..9094978e 100644 --- a/tests/cases/parser/classMethods1.php.tree +++ b/tests/cases/parser/classMethods1.php.tree @@ -62,6 +62,7 @@ "textLength": 1 }, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/classMethods3.php.tree b/tests/cases/parser/classMethods3.php.tree index 429677db..b9ce660d 100644 --- a/tests/cases/parser/classMethods3.php.tree +++ b/tests/cases/parser/classMethods3.php.tree @@ -57,6 +57,7 @@ "children": [ { "Parameter": { + "questionToken": null, "typeDeclaration": null, "byRefToken": null, "dotDotDotToken": null, @@ -74,6 +75,7 @@ }, { "Parameter": { + "questionToken": null, "typeDeclaration": null, "byRefToken": null, "dotDotDotToken": null, @@ -93,6 +95,7 @@ "textLength": 1 }, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/classMethods4.php.tree b/tests/cases/parser/classMethods4.php.tree index 48b85a8b..98cbebda 100644 --- a/tests/cases/parser/classMethods4.php.tree +++ b/tests/cases/parser/classMethods4.php.tree @@ -57,6 +57,7 @@ "children": [ { "Parameter": { + "questionToken": null, "typeDeclaration": null, "byRefToken": null, "dotDotDotToken": null, @@ -74,6 +75,7 @@ }, { "Parameter": { + "questionToken": null, "typeDeclaration": null, "byRefToken": null, "dotDotDotToken": null, @@ -96,6 +98,7 @@ "kind": "ColonToken", "textLength": 1 }, + "questionToken": null, "returnType": { "kind": "IntReservedWord", "textLength": 3 diff --git a/tests/cases/parser/classMethods5.php.tree b/tests/cases/parser/classMethods5.php.tree index fbcc6903..30f97842 100644 --- a/tests/cases/parser/classMethods5.php.tree +++ b/tests/cases/parser/classMethods5.php.tree @@ -58,6 +58,7 @@ "textLength": 1 }, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { @@ -102,6 +103,7 @@ "textLength": 1 }, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/constructorDeclaration1.php.tree b/tests/cases/parser/constructorDeclaration1.php.tree index c09b3870..1802ead2 100644 --- a/tests/cases/parser/constructorDeclaration1.php.tree +++ b/tests/cases/parser/constructorDeclaration1.php.tree @@ -61,6 +61,7 @@ "textLength": 1 }, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/functions1.php.tree b/tests/cases/parser/functions1.php.tree index 0e5664de..d6a982be 100644 --- a/tests/cases/parser/functions1.php.tree +++ b/tests/cases/parser/functions1.php.tree @@ -32,6 +32,7 @@ "textLength": 1 }, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/functions10.php.tree b/tests/cases/parser/functions10.php.tree index d575411d..9a67d11f 100644 --- a/tests/cases/parser/functions10.php.tree +++ b/tests/cases/parser/functions10.php.tree @@ -31,6 +31,7 @@ "children": [ { "Parameter": { + "questionToken": null, "typeDeclaration": null, "byRefToken": null, "dotDotDotToken": null, @@ -48,6 +49,7 @@ }, { "Parameter": { + "questionToken": null, "typeDeclaration": null, "byRefToken": { "kind": "AmpersandToken", @@ -73,6 +75,7 @@ "textLength": 1 }, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/functions11.php.tree b/tests/cases/parser/functions11.php.tree index d439a171..b1aa2347 100644 --- a/tests/cases/parser/functions11.php.tree +++ b/tests/cases/parser/functions11.php.tree @@ -31,6 +31,7 @@ "children": [ { "Parameter": { + "questionToken": null, "typeDeclaration": null, "byRefToken": null, "dotDotDotToken": null, @@ -48,6 +49,7 @@ }, { "Parameter": { + "questionToken": null, "typeDeclaration": { "QualifiedName": { "globalSpecifier": null, @@ -84,6 +86,7 @@ "textLength": 1 }, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/functions12.php.tree b/tests/cases/parser/functions12.php.tree index ff6003c9..56545062 100644 --- a/tests/cases/parser/functions12.php.tree +++ b/tests/cases/parser/functions12.php.tree @@ -31,6 +31,7 @@ "children": [ { "Parameter": { + "questionToken": null, "typeDeclaration": null, "byRefToken": null, "dotDotDotToken": null, @@ -48,6 +49,7 @@ }, { "Parameter": { + "questionToken": null, "typeDeclaration": { "QualifiedName": { "globalSpecifier": null, @@ -81,6 +83,7 @@ "textLength": 1 }, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/functions13.php.tree b/tests/cases/parser/functions13.php.tree index a055b44f..e1895c48 100644 --- a/tests/cases/parser/functions13.php.tree +++ b/tests/cases/parser/functions13.php.tree @@ -31,6 +31,7 @@ "children": [ { "Parameter": { + "questionToken": null, "typeDeclaration": null, "byRefToken": null, "dotDotDotToken": null, @@ -48,6 +49,7 @@ }, { "Parameter": { + "questionToken": null, "typeDeclaration": { "QualifiedName": { "globalSpecifier": null, @@ -85,6 +87,7 @@ "textLength": 1 }, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/functions14.php.tree b/tests/cases/parser/functions14.php.tree index 30bb085b..ddb1c05c 100644 --- a/tests/cases/parser/functions14.php.tree +++ b/tests/cases/parser/functions14.php.tree @@ -31,6 +31,7 @@ "children": [ { "Parameter": { + "questionToken": null, "typeDeclaration": null, "byRefToken": null, "dotDotDotToken": null, @@ -48,6 +49,7 @@ }, { "Parameter": { + "questionToken": null, "typeDeclaration": { "QualifiedName": { "globalSpecifier": null, @@ -79,6 +81,7 @@ }, { "Parameter": { + "questionToken": null, "typeDeclaration": null, "byRefToken": null, "dotDotDotToken": null, @@ -98,6 +101,7 @@ "textLength": 1 }, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/functions15.php.tree b/tests/cases/parser/functions15.php.tree index 58f6877b..62f1d75a 100644 --- a/tests/cases/parser/functions15.php.tree +++ b/tests/cases/parser/functions15.php.tree @@ -31,6 +31,7 @@ "children": [ { "Parameter": { + "questionToken": null, "typeDeclaration": null, "byRefToken": null, "dotDotDotToken": null, @@ -48,6 +49,7 @@ }, { "Parameter": { + "questionToken": null, "typeDeclaration": { "QualifiedName": { "globalSpecifier": null, @@ -79,6 +81,7 @@ }, { "Parameter": { + "questionToken": null, "typeDeclaration": null, "byRefToken": null, "dotDotDotToken": { @@ -101,6 +104,7 @@ "textLength": 1 }, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/functions16.php.tree b/tests/cases/parser/functions16.php.tree index a0e10723..4190cf83 100644 --- a/tests/cases/parser/functions16.php.tree +++ b/tests/cases/parser/functions16.php.tree @@ -31,6 +31,7 @@ "children": [ { "Parameter": { + "questionToken": null, "typeDeclaration": null, "byRefToken": null, "dotDotDotToken": null, @@ -50,6 +51,7 @@ "textLength": 1 }, "colonToken": null, + "questionToken": null, "returnType": null, "compoundStatementOrSemicolon": { "CompoundStatementNode": { diff --git a/tests/cases/parser/functions17.php b/tests/cases/parser/functions17.php new file mode 100644 index 00000000..983f6132 --- /dev/null +++ b/tests/cases/parser/functions17.php @@ -0,0 +1,5 @@ +