]> granicus.if.org Git - php/commitdiff
Treat namespaced names as single token
authorNikita Popov <nikita.ppv@gmail.com>
Mon, 15 Jun 2020 09:50:55 +0000 (11:50 +0200)
committerNikita Popov <nikita.ppv@gmail.com>
Wed, 22 Jul 2020 10:36:05 +0000 (12:36 +0200)
Namespace names are now lexed as single tokens of type
T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED or T_NAME_RELATIVE.

RFC: https://wiki.php.net/rfc/namespaced_names_as_token

Closes GH-5827.

20 files changed:
UPGRADING
Zend/tests/attributes/019_variable_attribute_name.phpt
Zend/tests/bug43343.phpt
Zend/tests/bug55086.phpt
Zend/tests/grammar/regression_010.phpt
Zend/tests/namespace_name_namespace.phpt [new file with mode: 0644]
Zend/tests/namespace_name_namespace_start.phpt [new file with mode: 0644]
Zend/tests/namespace_name_reserved_keywords.phpt [new file with mode: 0644]
Zend/tests/namespaced_name_whitespace.phpt [new file with mode: 0644]
Zend/tests/ns_096.phpt
Zend/tests/ns_trailing_comma_error_01.phpt
Zend/tests/ns_trailing_comma_error_02.phpt
Zend/tests/ns_trailing_comma_error_04.phpt
Zend/tests/ns_trailing_comma_error_07.phpt
Zend/tests/special_name_error1.phpt [deleted file]
Zend/zend_compile.c
Zend/zend_language_parser.y
Zend/zend_language_scanner.l
ext/tokenizer/tests/namespaced_names.phpt [new file with mode: 0644]
ext/tokenizer/tokenizer_data.c

index eed08ad8effcc628d8e53e9c5da80848e5e113f4..8c75319664bd0c40f3166891d3d1b6dfb89849a7 100644 (file)
--- a/UPGRADING
+++ b/UPGRADING
@@ -204,6 +204,10 @@ PHP 8.0 UPGRADE NOTES
     numbers and numeric strings continue to work as before. Notably, this means
     that `0 == "not-a-number"` is considered false now.
     RFC: https://wiki.php.net/rfc/string_to_number_comparison
+  . Namespaced names can no longer contain whitespace: While `Foo\Bar` will be
+    recognized as a namespaced name, `Foo \ Bar` will not. Conversely, reserved
+    keywords are now permitted as namespace segments.
+    RFC: https://wiki.php.net/rfc/namespaced_names_as_token
 
 - COM:
   . Removed the ability to import case-insensitive constants from type
@@ -509,6 +513,11 @@ PHP 8.0 UPGRADE NOTES
     instead be part of a following T_WHITESPACE token. It should be noted that
     T_COMMENT is not always followed by whitespace, it may also be followed by
     T_CLOSE_TAG or end-of-file.
+  . Namespaced names are now represented using the T_NAME_QUALIFIED (Foo\Bar),
+    T_NAME_FULLY_QUALIFIED (\Foo\Bar) and T_NAME_RELATIVE (namespace\Foo\Bar)
+    tokens. T_NS_SEPARATOR is only used for standalone namespace separators,
+    and only syntactially valid in conjunction with group use declarations.
+    RFC: https://wiki.php.net/rfc/namespaced_names_as_token
 
 - XML:
   . xml_parser_create(_ns) will now return an XmlParser object rather than a
index 2710abbed9f9439c6e53e3c05f2981d2914f4c04..a259c06f3fe77c0ba502ebd1e72a1e974c33857a 100644 (file)
@@ -8,4 +8,4 @@ class A {}
 
 ?>
 --EXPECTF--
-Parse error: syntax error, unexpected variable "$x", expecting identifier or "static" or "namespace" or "\" in %s on line %d
+Parse error: syntax error, unexpected variable "$x" in %s on line %d
index db72fe98ecfa7459aaeb9212d3fb99a929559810..c277ccccddcdb3b33d449222d3be6beeb8bed95c 100644 (file)
@@ -8,4 +8,4 @@ $foo = 'bar';
 var_dump(new namespace::$foo);
 ?>
 --EXPECTF--
-Parse error: %s error%sexpecting%s"\"%sin %sbug43343.php on line 5
+Parse error: syntax error, unexpected token "namespace" in %s on line %d
index 9a5c747b9f007b3546f0ada7f2e838ac9ed8dc87..10be0d1ef512a34050b94e9a59efbaa2c360f6d4 100644 (file)
@@ -26,7 +26,7 @@ namespace N2 {
     echo $a->hello(), PHP_EOL;
     echo $a->foo(), PHP_EOL;
     try {
-    } catch(namespace \Foo $e)
+    } catch (namespace\Foo $e)
     {
     }
 }
index e5e65e3a85d8d379616083e931da2f5d41ce65e6..0e535aad6b87fe8a14383794556f0906fc7d3fac 100644 (file)
@@ -5,8 +5,11 @@ Test to check regressions on T_IMPLEMENTS followed by a T_NS_SEPARATOR
 
 interface A{}
 
+// No longer considered legal in PHP 8.
 class B implements\A {}
 
 echo "Done", PHP_EOL;
---EXPECT--
-Done
+
+?>
+--EXPECTF--
+Parse error: syntax error, unexpected namespaced name "implements\A", expecting "{" in %s on line %d
diff --git a/Zend/tests/namespace_name_namespace.phpt b/Zend/tests/namespace_name_namespace.phpt
new file mode 100644 (file)
index 0000000..311705a
--- /dev/null
@@ -0,0 +1,10 @@
+--TEST--
+Cannot use "namespace" as namespace name, due to conflict with ns-relative names
+--FILE--
+<?php
+
+namespace NAMEspace;
+
+?>
+--EXPECTF--
+Fatal error: Cannot use 'NAMEspace' as namespace name in %s on line %d
diff --git a/Zend/tests/namespace_name_namespace_start.phpt b/Zend/tests/namespace_name_namespace_start.phpt
new file mode 100644 (file)
index 0000000..bf812fe
--- /dev/null
@@ -0,0 +1,10 @@
+--TEST--
+Cannot use "namespace\xyz" as namespace name, due to conflict with ns-relative names
+--FILE--
+<?php
+
+namespace NAMEspace\xyz;
+
+?>
+--EXPECTF--
+Parse error: syntax error, unexpected namespace-relative name "NAMEspace\xyz", expecting "{" in %s on line %d
diff --git a/Zend/tests/namespace_name_reserved_keywords.phpt b/Zend/tests/namespace_name_reserved_keywords.phpt
new file mode 100644 (file)
index 0000000..9374ef6
--- /dev/null
@@ -0,0 +1,37 @@
+--TEST--
+Reserved keywords in namespace name
+--FILE--
+<?php
+
+namespace iter\fn {
+    function test() {
+        echo __FUNCTION__, "\n";
+    }
+}
+
+namespace fn {
+    function test() {
+        echo __FUNCTION__, "\n";
+    }
+}
+
+namespace self {
+    function test() {
+        echo __FUNCTION__, "\n";
+    }
+}
+
+namespace {
+    use iter\fn;
+    use function fn\test as test2;
+    use function self\test as test3;
+    fn\test();
+    test2();
+    test3();
+}
+
+?>
+--EXPECT--
+iter\fn\test
+fn\test
+self\test
diff --git a/Zend/tests/namespaced_name_whitespace.phpt b/Zend/tests/namespaced_name_whitespace.phpt
new file mode 100644 (file)
index 0000000..746b329
--- /dev/null
@@ -0,0 +1,10 @@
+--TEST--
+Whitespace between namespace separators is no longer allowed
+--FILE--
+<?php
+
+Foo \ Bar \ Baz;
+
+?>
+--EXPECTF--
+Parse error: syntax error, unexpected token "\" in %s on line %d
index f72e37f7d3b1f3dc69f25f709e59b6b9ee1e81bf..03b09047ff49b0c546398af6107c36c375d0ba59 100644 (file)
@@ -7,4 +7,4 @@ use Foo\Bar\{\Baz};
 
 ?>
 --EXPECTF--
-Parse error: syntax error, unexpected token "\", expecting identifier or "function" or "const" in %s on line %d
+Parse error: syntax error, unexpected fully qualified name "\Baz", expecting identifier or namespaced name or "function" or "const" in %s on line %d
index 7b9c5177bf1dcbbc88050b97b0761ddb1759eaea..f8fa5dbf1218cc5204ae5b957b5b1829caa9cd8e 100644 (file)
@@ -5,4 +5,4 @@ Group use declarations mustn't be empty
 use Baz\{};
 ?>
 --EXPECTF--
-Parse error: syntax error, unexpected token "}", expecting identifier or "function" or "const" in %s on line %d
+Parse error: syntax error, unexpected token "}", expecting identifier or namespaced name or "function" or "const" in %s on line %d
index 91b70d3bd640f2547fb2a6959aeee6b206a0f615..f73ffe748d94b4ee034a61e814492c95ea75372a 100644 (file)
@@ -5,4 +5,4 @@ Group use declarations mustn't contain just a comma
 use Baz\{,};
 ?>
 --EXPECTF--
-Parse error: syntax error, unexpected token ",", expecting identifier or "function" or "const" in %s on line %d
+Parse error: syntax error, unexpected token ",", expecting identifier or namespaced name or "function" or "const" in %s on line %d
index 2712c5aa115c4e7eab568640d5fdaf0b9ea2e6fd..18bbea745a323d53a420f506e175b7280e575d85 100644 (file)
@@ -5,4 +5,4 @@ Group use declarations mustn't begin with a comma
 use Baz\{,Foo};
 ?>
 --EXPECTF--
-Parse error: syntax error, unexpected token ",", expecting identifier or "function" or "const" in %s on line %d
+Parse error: syntax error, unexpected token ",", expecting identifier or namespaced name or "function" or "const" in %s on line %d
index 742d809bbc9cab13885a5d4bf18794730840c3e5..c7798da8f072e74e80d6ec067f6abd69d75d8fb6 100644 (file)
@@ -5,4 +5,4 @@ Unmixed group use declarations mustn't begin with a comma
 use function Baz\{,Foo};
 ?>
 --EXPECTF--
-Parse error: syntax error, unexpected token ",", expecting identifier in %s on line %d
+Parse error: syntax error, unexpected token ",", expecting identifier or namespaced name in %s on line %d
diff --git a/Zend/tests/special_name_error1.phpt b/Zend/tests/special_name_error1.phpt
deleted file mode 100644 (file)
index 63bf5a6..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
---TEST--
-Cannot use special class name as namespace
---FILE--
-<?php
-
-namespace self;
-
-?>
---EXPECTF--
-Fatal error: Cannot use 'self' as namespace name in %s on line %d
index aafe582f9f551ca9f393ce160943977d1ba64004..d555297c17d894b85e2dac43f3e8ba9620a9a60d 100644 (file)
@@ -7432,7 +7432,7 @@ void zend_compile_namespace(zend_ast *ast) /* {{{ */
        if (name_ast) {
                name = zend_ast_get_str(name_ast);
 
-               if (ZEND_FETCH_CLASS_DEFAULT != zend_get_class_fetch_type(name)) {
+               if (zend_string_equals_literal_ci(name, "namespace")) {
                        zend_error_noreturn(E_COMPILE_ERROR, "Cannot use '%s' as namespace name", ZSTR_VAL(name));
                }
 
index 891624c461f67daf809d3bd330a37c479afa5c03..acb7a3d954ee2cad20f00761ab8c3d49260e0def 100644 (file)
@@ -89,6 +89,9 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
 %token <ast> T_LNUMBER   "integer"
 %token <ast> T_DNUMBER   "floating-point number"
 %token <ast> T_STRING    "identifier"
+%token <ast> T_NAME_FULLY_QUALIFIED "fully qualified name"
+%token <ast> T_NAME_RELATIVE "namespace-relative name"
+%token <ast> T_NAME_QUALIFIED "namespaced name"
 %token <ast> T_VARIABLE  "variable"
 %token <ast> T_INLINE_HTML
 %token <ast> T_ENCAPSED_AND_WHITESPACE  "string content"
@@ -231,7 +234,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
 %token T_ERROR
 
 %type <ast> top_statement namespace_name name statement function_declaration_statement
-%type <ast> class_declaration_statement trait_declaration_statement
+%type <ast> class_declaration_statement trait_declaration_statement legacy_namespace_name
 %type <ast> interface_declaration_statement interface_extends_list
 %type <ast> group_use_declaration inline_use_declarations inline_use_declaration
 %type <ast> mixed_group_use_declaration use_declaration unprefixed_use_declaration
@@ -261,7 +264,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
 %type <ast> identifier type_expr_without_static union_type_without_static
 %type <ast> inline_function union_type
 %type <ast> attributed_statement attributed_class_statement attributed_parameter
-%type <ast> attribute_decl attribute attributes
+%type <ast> attribute_decl attribute attributes namespace_declaration_name
 %type <ast> match match_arm_list non_empty_match_arm_list match_arm match_arm_cond_list
 
 %type <num> returns_ref function fn is_reference is_variadic variable_modifiers
@@ -308,15 +311,29 @@ top_statement_list:
        |       %empty { $$ = zend_ast_create_list(0, ZEND_AST_STMT_LIST); }
 ;
 
+/* Name usable in a namespace declaration. */
+namespace_declaration_name:
+               identifier                                                              { $$ = $1; }
+       |       T_NAME_QUALIFIED                                                { $$ = $1; }
+;
+
+/* Name usable in "use" declarations (loading separator forbidden). */
 namespace_name:
                T_STRING                                                                { $$ = $1; }
-       |       namespace_name T_NS_SEPARATOR T_STRING  { $$ = zend_ast_append_str($1, $3); }
+       |       T_NAME_QUALIFIED                                                { $$ = $1; }
+;
+
+/* Name usable in "use" declarations (leading separator allowed). */
+legacy_namespace_name:
+               namespace_name                                                  { $$ = $1; }
+       |       T_NAME_FULLY_QUALIFIED                                  { $$ = $1; }
 ;
 
 name:
-               namespace_name                                                          { $$ = $1; $$->attr = ZEND_NAME_NOT_FQ; }
-       |       T_NAMESPACE T_NS_SEPARATOR namespace_name       { $$ = $3; $$->attr = ZEND_NAME_RELATIVE; }
-       |       T_NS_SEPARATOR namespace_name                           { $$ = $2; $$->attr = ZEND_NAME_FQ; }
+               T_STRING                                                                        { $$ = $1; $$->attr = ZEND_NAME_NOT_FQ; }
+       |       T_NAME_QUALIFIED                                                        { $$ = $1; $$->attr = ZEND_NAME_NOT_FQ; }
+       |       T_NAME_FULLY_QUALIFIED                                          { $$ = $1; $$->attr = ZEND_NAME_FQ; }
+       |       T_NAME_RELATIVE                                                         { $$ = $1; $$->attr = ZEND_NAME_RELATIVE; }
 ;
 
 attribute_decl:
@@ -350,10 +367,10 @@ top_statement:
                        { $$ = zend_ast_create(ZEND_AST_HALT_COMPILER,
                              zend_ast_create_zval_from_long(zend_get_scanned_file_offset()));
                          zend_stop_lexing(); }
-       |       T_NAMESPACE namespace_name ';'
+       |       T_NAMESPACE namespace_declaration_name ';'
                        { $$ = zend_ast_create(ZEND_AST_NAMESPACE, $2, NULL);
                          RESET_DOC_COMMENT(); }
-       |       T_NAMESPACE namespace_name { RESET_DOC_COMMENT(); }
+       |       T_NAMESPACE namespace_declaration_name { RESET_DOC_COMMENT(); }
                '{' top_statement_list '}'
                        { $$ = zend_ast_create(ZEND_AST_NAMESPACE, $2, $5); }
        |       T_NAMESPACE { RESET_DOC_COMMENT(); }
@@ -372,17 +389,13 @@ use_type:
 ;
 
 group_use_declaration:
-               namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations possible_comma '}'
+               legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations possible_comma '}'
                        { $$ = zend_ast_create(ZEND_AST_GROUP_USE, $1, $4); }
-       |       T_NS_SEPARATOR namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations possible_comma '}'
-                       { $$ = zend_ast_create(ZEND_AST_GROUP_USE, $2, $5); }
 ;
 
 mixed_group_use_declaration:
-               namespace_name T_NS_SEPARATOR '{' inline_use_declarations possible_comma '}'
+               legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations possible_comma '}'
                        { $$ = zend_ast_create(ZEND_AST_GROUP_USE, $1, $4);}
-       |       T_NS_SEPARATOR namespace_name T_NS_SEPARATOR '{' inline_use_declarations possible_comma '}'
-                       { $$ = zend_ast_create(ZEND_AST_GROUP_USE, $2, $5); }
 ;
 
 possible_comma:
@@ -424,8 +437,10 @@ unprefixed_use_declaration:
 ;
 
 use_declaration:
-               unprefixed_use_declaration                { $$ = $1; }
-       |       T_NS_SEPARATOR unprefixed_use_declaration { $$ = $2; }
+               legacy_namespace_name
+                       { $$ = zend_ast_create(ZEND_AST_USE_ELEM, $1, NULL); }
+       |       legacy_namespace_name T_AS T_STRING
+                       { $$ = zend_ast_create(ZEND_AST_USE_ELEM, $1, $3); }
 ;
 
 const_list:
index b6cd505a633aaff7d4835b8584f09f00d785bb83..2ebe92e6bd4bb1dbdf7c89ac8c927ab4720e561e 100644 (file)
@@ -1596,10 +1596,6 @@ NEWLINE ("\r"|"\n"|"\r\n")
        RETURN_TOKEN(T_PAAMAYIM_NEKUDOTAYIM);
 }
 
-<ST_IN_SCRIPTING>"\\" {
-       RETURN_TOKEN(T_NS_SEPARATOR);
-}
-
 <ST_IN_SCRIPTING>"..." {
        RETURN_TOKEN(T_ELLIPSIS);
 }
@@ -2288,6 +2284,22 @@ inline_char_handler:
        RETURN_TOKEN_WITH_VAL(T_ENCAPSED_AND_WHITESPACE);
 }
 
+<ST_IN_SCRIPTING>"namespace"("\\"{LABEL})+ {
+       RETURN_TOKEN_WITH_STR(T_NAME_RELATIVE, sizeof("namespace\\") - 1);
+}
+
+<ST_IN_SCRIPTING>{LABEL}("\\"{LABEL})+ {
+       RETURN_TOKEN_WITH_STR(T_NAME_QUALIFIED, 0);
+}
+
+<ST_IN_SCRIPTING>"\\"{LABEL}("\\"{LABEL})* {
+       RETURN_TOKEN_WITH_STR(T_NAME_FULLY_QUALIFIED, 1);
+}
+
+<ST_IN_SCRIPTING>"\\" {
+       RETURN_TOKEN(T_NS_SEPARATOR);
+}
+
 <ST_IN_SCRIPTING,ST_VAR_OFFSET>{LABEL} {
        RETURN_TOKEN_WITH_STR(T_STRING, 0);
 }
diff --git a/ext/tokenizer/tests/namespaced_names.phpt b/ext/tokenizer/tests/namespaced_names.phpt
new file mode 100644 (file)
index 0000000..34e947b
--- /dev/null
@@ -0,0 +1,39 @@
+--TEST--
+Tokenization of namespaced names
+--FILE--
+<?php
+
+$code = <<<'CODE'
+<?php
+Foo
+Foo\Bar
+\Foo\Bar
+namespace\Foo
+Foo \ Bar
+CODE;
+
+foreach (PhpToken::getAll($code) as $token) {
+    echo "{$token->getTokenName()}: \"$token->text\"\n";
+}
+
+?>
+--EXPECT--
+T_OPEN_TAG: "<?php
+"
+T_STRING: "Foo"
+T_WHITESPACE: "
+"
+T_NAME_QUALIFIED: "Foo\Bar"
+T_WHITESPACE: "
+"
+T_NAME_FULLY_QUALIFIED: "\Foo\Bar"
+T_WHITESPACE: "
+"
+T_NAME_RELATIVE: "namespace\Foo"
+T_WHITESPACE: "
+"
+T_STRING: "Foo"
+T_WHITESPACE: " "
+T_NS_SEPARATOR: "\"
+T_WHITESPACE: " "
+T_STRING: "Bar"
index 14630cdd4014c40229f63ed6894a97988d331e5a..9ed027920ccbbdb9fdcf32be37e03da693339a53 100644 (file)
@@ -77,6 +77,9 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) {
        REGISTER_LONG_CONSTANT("T_LNUMBER", T_LNUMBER, CONST_CS | CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("T_DNUMBER", T_DNUMBER, CONST_CS | CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("T_STRING", T_STRING, CONST_CS | CONST_PERSISTENT);
+       REGISTER_LONG_CONSTANT("T_NAME_FULLY_QUALIFIED", T_NAME_FULLY_QUALIFIED, CONST_CS | CONST_PERSISTENT);
+       REGISTER_LONG_CONSTANT("T_NAME_RELATIVE", T_NAME_RELATIVE, CONST_CS | CONST_PERSISTENT);
+       REGISTER_LONG_CONSTANT("T_NAME_QUALIFIED", T_NAME_QUALIFIED, CONST_CS | CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("T_VARIABLE", T_VARIABLE, CONST_CS | CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("T_INLINE_HTML", T_INLINE_HTML, CONST_CS | CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("T_ENCAPSED_AND_WHITESPACE", T_ENCAPSED_AND_WHITESPACE, CONST_CS | CONST_PERSISTENT);
@@ -221,6 +224,9 @@ char *get_token_type_name(int token_type)
                case T_LNUMBER: return "T_LNUMBER";
                case T_DNUMBER: return "T_DNUMBER";
                case T_STRING: return "T_STRING";
+               case T_NAME_FULLY_QUALIFIED: return "T_NAME_FULLY_QUALIFIED";
+               case T_NAME_RELATIVE: return "T_NAME_RELATIVE";
+               case T_NAME_QUALIFIED: return "T_NAME_QUALIFIED";
                case T_VARIABLE: return "T_VARIABLE";
                case T_INLINE_HTML: return "T_INLINE_HTML";
                case T_ENCAPSED_AND_WHITESPACE: return "T_ENCAPSED_AND_WHITESPACE";