Repository: microsoft/tolerant-php-parser (php)

Issue:
Support php 8.1 `never` return type, add to returnTypeDeclarationTokens
https://wiki.php.net/rfc/noreturn_type#vote (secondary vote was for `never`)


Using `void` as a parameter/property type is a compilation error, not a parse error
```php
php > function foo(void $x){ }

Fatal error: void cannot be used as a parameter type in php shell code on line 1
```

Using void in parameter names and php 7.4 property types parses, but causes a CompileError

However, `static` is a parse error in parameter types

Low priority since valid code wouldn't do that

Pull request description:
Support php 'never' type, change parsing of 'void'
Similar to 'void', it's a compile time error to use void as a
parameter/property type, not a parse time error.
But `static` is a parse error.
(It can also be used in union types)

Remove redundant tokens added to merged returnTypeDeclarationTokens

Closes #373
Closes #372

Patch:
diff --git a/src/Parser.php b/src/Parser.php
index 4f1a2b0..6198364 100644
--- a/src/Parser.php
+++ b/src/Parser.php
@@ -144,9 +144,9 @@ class Parser {
             [TokenKind::ArrayKeyword, TokenKind::CallableKeyword, TokenKind::BoolReservedWord,
             TokenKind::FloatReservedWord, TokenKind::IntReservedWord, TokenKind::StringReservedWord,
             TokenKind::ObjectReservedWord, TokenKind::NullReservedWord, TokenKind::FalseReservedWord,
-            TokenKind::TrueReservedWord,
-            TokenKind::IterableReservedWord, TokenKind::MixedReservedWord]; // TODO update spec
-        $this->returnTypeDeclarationTokens = \array_merge([TokenKind::VoidReservedWord, TokenKind::NullReservedWord, TokenKind::FalseReservedWord, TokenKind::TrueReservedWord, TokenKind::StaticKeyword], $this->parameterTypeDeclarationTokens);
+            TokenKind::TrueReservedWord, TokenKind::IterableReservedWord, TokenKind::MixedReservedWord,
+            TokenKind::VoidReservedWord, TokenKind::NeverReservedWord]; // TODO update spec
+        $this->returnTypeDeclarationTokens = \array_merge([TokenKind::StaticKeyword], $this->parameterTypeDeclarationTokens);
     }
 
     /**
diff --git a/src/TokenKind.php b/src/TokenKind.php
index 2905805..49caca9 100644
--- a/src/TokenKind.php
+++ b/src/TokenKind.php
@@ -175,6 +175,7 @@ class TokenKind {
     const NullReservedWord = 322;
     const MixedReservedWord = 340;
     const IterableReservedWord = 170;
+    const NeverReservedWord = 341;
 
     const ScriptSectionStartTag = 323;
     const ScriptSectionEndTag = 324;
diff --git a/src/TokenStringMaps.php b/src/TokenStringMaps.php
index 8a90652..15249ed 100644
--- a/src/TokenStringMaps.php
+++ b/src/TokenStringMaps.php
@@ -109,6 +109,7 @@ class TokenStringMaps {
         "void" => TokenKind::VoidReservedWord,
         "iterable" => TokenKind::IterableReservedWord,
         "mixed" => TokenKind::MixedReservedWord,
+        "never" => TokenKind::NeverReservedWord,
     ];
 
     const OPERATORS_AND_PUNCTUATORS = [


Test patch:
diff --git a/tests/cases/parser/neverReturnType.php b/tests/cases/parser/neverReturnType.php
new file mode 100644
index 0000000..55f0941
--- /dev/null
+++ b/tests/cases/parser/neverReturnType.php
@@ -0,0 +1,2 @@
+<?php
+function example() : never {}
diff --git a/tests/cases/parser/neverReturnType.php.diag b/tests/cases/parser/neverReturnType.php.diag
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/cases/parser/neverReturnType.php.diag
@@ -0,0 +1,1 @@
+[]
\ No newline at end of file
diff --git a/tests/cases/parser/neverReturnType.php.tree b/tests/cases/parser/neverReturnType.php.tree
new file mode 100644
index 0000000..9c7020e
--- /dev/null
+++ b/tests/cases/parser/neverReturnType.php.tree
@@ -0,0 +1,71 @@
+{
+    "SourceFileNode": {
+        "statementList": [
+            {
+                "InlineHtml": {
+                    "scriptSectionEndTag": null,
+                    "text": null,
+                    "scriptSectionStartTag": {
+                        "kind": "ScriptSectionStartTag",
+                        "textLength": 6
+                    }
+                }
+            },
+            {
+                "FunctionDeclaration": {
+                    "attributes": null,
+                    "functionKeyword": {
+                        "kind": "FunctionKeyword",
+                        "textLength": 8
+                    },
+                    "byRefToken": null,
+                    "name": {
+                        "kind": "Name",
+                        "textLength": 7
+                    },
+                    "openParen": {
+                        "kind": "OpenParenToken",
+                        "textLength": 1
+                    },
+                    "parameters": null,
+                    "closeParen": {
+                        "kind": "CloseParenToken",
+                        "textLength": 1
+                    },
+                    "colonToken": {
+                        "kind": "ColonToken",
+                        "textLength": 1
+                    },
+                    "questionToken": null,
+                    "returnTypeList": {
+                        "QualifiedNameList": {
+                            "children": [
+                                {
+                                    "kind": "NeverReservedWord",
+                                    "textLength": 5
+                                }
+                            ]
+                        }
+                    },
+                    "compoundStatementOrSemicolon": {
+                        "CompoundStatementNode": {
+                            "openBrace": {
+                                "kind": "OpenBraceToken",
+                                "textLength": 1
+                            },
+                            "statements": [],
+                            "closeBrace": {
+                                "kind": "CloseBraceToken",
+                                "textLength": 1
+                            }
+                        }
+                    }
+                }
+            }
+        ],
+        "endOfFileToken": {
+            "kind": "EndOfFileToken",
+            "textLength": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/cases/parser/parameterTypes1.php b/tests/cases/parser/parameterTypes1.php
new file mode 100644
index 0000000..f0725ef
--- /dev/null
+++ b/tests/cases/parser/parameterTypes1.php
@@ -0,0 +1,3 @@
+<?php
+// These are fatal compile errors, not parse errors
+function u(never $g, void $u) {}
diff --git a/tests/cases/parser/parameterTypes1.php.diag b/tests/cases/parser/parameterTypes1.php.diag
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/tests/cases/parser/parameterTypes1.php.diag
@@ -0,0 +1,1 @@
+[]
\ No newline at end of file
diff --git a/tests/cases/parser/parameterTypes1.php.tree b/tests/cases/parser/parameterTypes1.php.tree
new file mode 100644
index 0000000..55f2f7c
--- /dev/null
+++ b/tests/cases/parser/parameterTypes1.php.tree
@@ -0,0 +1,120 @@
+{
+    "SourceFileNode": {
+        "statementList": [
+            {
+                "InlineHtml": {
+                    "scriptSectionEndTag": null,
+                    "text": null,
+                    "scriptSectionStartTag": {
+                        "kind": "ScriptSectionStartTag",
+                        "textLength": 6
+                    }
+                }
+            },
+            {
+                "FunctionDeclaration": {
+                    "attributes": null,
+                    "functionKeyword": {
+                        "kind": "FunctionKeyword",
+                        "textLength": 8
+                    },
+                    "byRefToken": null,
+                    "name": {
+                        "kind": "Name",
+                        "textLength": 1
+                    },
+                    "openParen": {
+                        "kind": "OpenParenToken",
+                        "textLength": 1
+                    },
+                    "parameters": {
+                        "ParameterDeclarationList": {
+                            "children": [
+                                {
+                                    "Parameter": {
+                                        "attributes": null,
+                                        "visibilityToken": null,
+                                        "modifiers": null,
+                                        "questionToken": null,
+                                        "typeDeclarationList": {
+                                            "QualifiedNameList": {
+                                                "children": [
+                                                    {
+                                                        "kind": "NeverReservedWord",
+                                                        "textLength": 5
+                                                    }
+                                                ]
+                                            }
+                                        },
+                                        "byRefToken": null,
+                                        "dotDotDotToken": null,
+                                        "variableName": {
+                                            "kind": "VariableName",
+                                            "textLength": 2
+                                        },
+                                        "equalsToken": null,
+                                        "default": null
+                                    }
+                                },
+                                {
+                                    "kind": "CommaToken",
+                                    "textLength": 1
+                                },
+                                {
+                                    "Parameter": {
+                                        "attributes": null,
+                                        "visibilityToken": null,
+                                        "modifiers": null,
+                                        "questionToken": null,
+                                        "typeDeclarationList": {
+                                            "QualifiedNameList": {
+                                                "children": [
+                                                    {
+                                                        "kind": "VoidReservedWord",
+                                                        "textLength": 4
+                                                    }
+                                                ]
+                                            }
+                                        },
+                                        "byRefToken": null,
+                                        "dotDotDotToken": null,
+                                        "variableName": {
+                                            "kind": "VariableName",
+                                            "textLength": 2
+                                        },
+                                        "equalsToken": null,
+                                        "default": null
+                                    }
+                                }
+                            ]
+                        }
+                    },
+                    "closeParen": {
+                        "kind": "CloseParenToken",
+                        "textLength": 1
+                    },
+                    "colonToken": null,
+                    "questionToken": null,
+                    "returnTypeList": null,
+                    "compoundStatementOrSemicolon": {
+                        "CompoundStatementNode": {
+                            "openBrace": {
+                                "kind": "OpenBraceToken",
+                                "textLength": 1
+                            },
+                            "statements": [],
+                            "closeBrace": {
+                                "kind": "CloseBraceToken",
+                                "textLength": 1
+                            }
+                        }
+                    }
+                }
+            }
+        ],
+        "endOfFileToken": {
+            "kind": "EndOfFileToken",
+            "textLength": 0
+        }
+    }
+}
\ No newline at end of file