Skip to content

Commit 7a3dcc3

Browse files
committed
Treat namespaced names as single token
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.
1 parent acbf780 commit 7a3dcc3

20 files changed

+182
-41
lines changed

UPGRADING

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@ PHP 8.0 UPGRADE NOTES
204204
numbers and numeric strings continue to work as before. Notably, this means
205205
that `0 == "not-a-number"` is considered false now.
206206
RFC: https://wiki.php.net/rfc/string_to_number_comparison
207+
. Namespaced names can no longer contain whitespace: While `Foo\Bar` will be
208+
recognized as a namespaced name, `Foo \ Bar` will not. Conversely, reserved
209+
keywords are now permitted as namespace segments.
210+
RFC: https://wiki.php.net/rfc/namespaced_names_as_token
207211

208212
- COM:
209213
. Removed the ability to import case-insensitive constants from type
@@ -509,6 +513,11 @@ PHP 8.0 UPGRADE NOTES
509513
instead be part of a following T_WHITESPACE token. It should be noted that
510514
T_COMMENT is not always followed by whitespace, it may also be followed by
511515
T_CLOSE_TAG or end-of-file.
516+
. Namespaced names are now represented using the T_NAME_QUALIFIED (Foo\Bar),
517+
T_NAME_FULLY_QUALIFIED (\Foo\Bar) and T_NAME_RELATIVE (namespace\Foo\Bar)
518+
tokens. T_NS_SEPARATOR is only used for standalone namespace separators,
519+
and only syntactially valid in conjunction with group use declarations.
520+
RFC: https://wiki.php.net/rfc/namespaced_names_as_token
512521

513522
- XML:
514523
. xml_parser_create(_ns) will now return an XmlParser object rather than a

Zend/tests/attributes/019_variable_attribute_name.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ class A {}
88

99
?>
1010
--EXPECTF--
11-
Parse error: syntax error, unexpected variable "$x", expecting identifier or "static" or "namespace" or "\" in %s on line %d
11+
Parse error: syntax error, unexpected variable "$x" in %s on line %d

Zend/tests/bug43343.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ $foo = 'bar';
88
var_dump(new namespace::$foo);
99
?>
1010
--EXPECTF--
11-
Parse error: %s error%sexpecting%s"\"%sin %sbug43343.php on line 5
11+
Parse error: syntax error, unexpected token "namespace" in %s on line %d

Zend/tests/bug55086.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ namespace N2 {
2626
echo $a->hello(), PHP_EOL;
2727
echo $a->foo(), PHP_EOL;
2828
try {
29-
} catch(namespace \Foo $e)
29+
} catch (namespace\Foo $e)
3030
{
3131
}
3232
}

Zend/tests/grammar/regression_010.phpt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ Test to check regressions on T_IMPLEMENTS followed by a T_NS_SEPARATOR
55

66
interface A{}
77

8+
// No longer considered legal in PHP 8.
89
class B implements\A {}
910

1011
echo "Done", PHP_EOL;
11-
--EXPECT--
12-
Done
12+
13+
?>
14+
--EXPECTF--
15+
Parse error: syntax error, unexpected namespaced name "implements\A", expecting "{" in %s on line %d
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Cannot use "namespace" as namespace name, due to conflict with ns-relative names
3+
--FILE--
4+
<?php
5+
6+
namespace NAMEspace;
7+
8+
?>
9+
--EXPECTF--
10+
Fatal error: Cannot use 'NAMEspace' as namespace name in %s on line %d
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Cannot use "namespace\xyz" as namespace name, due to conflict with ns-relative names
3+
--FILE--
4+
<?php
5+
6+
namespace NAMEspace\xyz;
7+
8+
?>
9+
--EXPECTF--
10+
Parse error: syntax error, unexpected namespace-relative name "NAMEspace\xyz", expecting "{" in %s on line %d
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
--TEST--
2+
Reserved keywords in namespace name
3+
--FILE--
4+
<?php
5+
6+
namespace iter\fn {
7+
function test() {
8+
echo __FUNCTION__, "\n";
9+
}
10+
}
11+
12+
namespace fn {
13+
function test() {
14+
echo __FUNCTION__, "\n";
15+
}
16+
}
17+
18+
namespace self {
19+
function test() {
20+
echo __FUNCTION__, "\n";
21+
}
22+
}
23+
24+
namespace {
25+
use iter\fn;
26+
use function fn\test as test2;
27+
use function self\test as test3;
28+
fn\test();
29+
test2();
30+
test3();
31+
}
32+
33+
?>
34+
--EXPECT--
35+
iter\fn\test
36+
fn\test
37+
self\test
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Whitespace between namespace separators is no longer allowed
3+
--FILE--
4+
<?php
5+
6+
Foo \ Bar \ Baz;
7+
8+
?>
9+
--EXPECTF--
10+
Parse error: syntax error, unexpected token "\" in %s on line %d

Zend/tests/ns_096.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ use Foo\Bar\{\Baz};
77

88
?>
99
--EXPECTF--
10-
Parse error: syntax error, unexpected token "\", expecting identifier or "function" or "const" in %s on line %d
10+
Parse error: syntax error, unexpected fully qualified name "\Baz", expecting identifier or namespaced name or "function" or "const" in %s on line %d

Zend/tests/ns_trailing_comma_error_01.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ Group use declarations mustn't be empty
55
use Baz\{};
66
?>
77
--EXPECTF--
8-
Parse error: syntax error, unexpected token "}", expecting identifier or "function" or "const" in %s on line %d
8+
Parse error: syntax error, unexpected token "}", expecting identifier or namespaced name or "function" or "const" in %s on line %d

Zend/tests/ns_trailing_comma_error_02.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ Group use declarations mustn't contain just a comma
55
use Baz\{,};
66
?>
77
--EXPECTF--
8-
Parse error: syntax error, unexpected token ",", expecting identifier or "function" or "const" in %s on line %d
8+
Parse error: syntax error, unexpected token ",", expecting identifier or namespaced name or "function" or "const" in %s on line %d

Zend/tests/ns_trailing_comma_error_04.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ Group use declarations mustn't begin with a comma
55
use Baz\{,Foo};
66
?>
77
--EXPECTF--
8-
Parse error: syntax error, unexpected token ",", expecting identifier or "function" or "const" in %s on line %d
8+
Parse error: syntax error, unexpected token ",", expecting identifier or namespaced name or "function" or "const" in %s on line %d

Zend/tests/ns_trailing_comma_error_07.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ Unmixed group use declarations mustn't begin with a comma
55
use function Baz\{,Foo};
66
?>
77
--EXPECTF--
8-
Parse error: syntax error, unexpected token ",", expecting identifier in %s on line %d
8+
Parse error: syntax error, unexpected token ",", expecting identifier or namespaced name in %s on line %d

Zend/tests/special_name_error1.phpt

Lines changed: 0 additions & 10 deletions
This file was deleted.

Zend/zend_compile.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7432,7 +7432,7 @@ void zend_compile_namespace(zend_ast *ast) /* {{{ */
74327432
if (name_ast) {
74337433
name = zend_ast_get_str(name_ast);
74347434

7435-
if (ZEND_FETCH_CLASS_DEFAULT != zend_get_class_fetch_type(name)) {
7435+
if (zend_string_equals_literal_ci(name, "namespace")) {
74367436
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use '%s' as namespace name", ZSTR_VAL(name));
74377437
}
74387438

Zend/zend_language_parser.y

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
8989
%token <ast> T_LNUMBER "integer"
9090
%token <ast> T_DNUMBER "floating-point number"
9191
%token <ast> T_STRING "identifier"
92+
%token <ast> T_NAME_FULLY_QUALIFIED "fully qualified name"
93+
%token <ast> T_NAME_RELATIVE "namespace-relative name"
94+
%token <ast> T_NAME_QUALIFIED "namespaced name"
9295
%token <ast> T_VARIABLE "variable"
9396
%token <ast> T_INLINE_HTML
9497
%token <ast> T_ENCAPSED_AND_WHITESPACE "string content"
@@ -231,7 +234,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
231234
%token T_ERROR
232235

233236
%type <ast> top_statement namespace_name name statement function_declaration_statement
234-
%type <ast> class_declaration_statement trait_declaration_statement
237+
%type <ast> class_declaration_statement trait_declaration_statement legacy_namespace_name
235238
%type <ast> interface_declaration_statement interface_extends_list
236239
%type <ast> group_use_declaration inline_use_declarations inline_use_declaration
237240
%type <ast> mixed_group_use_declaration use_declaration unprefixed_use_declaration
@@ -261,7 +264,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
261264
%type <ast> identifier type_expr_without_static union_type_without_static
262265
%type <ast> inline_function union_type
263266
%type <ast> attributed_statement attributed_class_statement attributed_parameter
264-
%type <ast> attribute_decl attribute attributes
267+
%type <ast> attribute_decl attribute attributes namespace_declaration_name
265268
%type <ast> match match_arm_list non_empty_match_arm_list match_arm match_arm_cond_list
266269

267270
%type <num> returns_ref function fn is_reference is_variadic variable_modifiers
@@ -308,15 +311,29 @@ top_statement_list:
308311
| %empty { $$ = zend_ast_create_list(0, ZEND_AST_STMT_LIST); }
309312
;
310313

314+
/* Name usable in a namespace declaration. */
315+
namespace_declaration_name:
316+
identifier { $$ = $1; }
317+
| T_NAME_QUALIFIED { $$ = $1; }
318+
;
319+
320+
/* Name usable in "use" declarations (loading separator forbidden). */
311321
namespace_name:
312322
T_STRING { $$ = $1; }
313-
| namespace_name T_NS_SEPARATOR T_STRING { $$ = zend_ast_append_str($1, $3); }
323+
| T_NAME_QUALIFIED { $$ = $1; }
324+
;
325+
326+
/* Name usable in "use" declarations (leading separator allowed). */
327+
legacy_namespace_name:
328+
namespace_name { $$ = $1; }
329+
| T_NAME_FULLY_QUALIFIED { $$ = $1; }
314330
;
315331

316332
name:
317-
namespace_name { $$ = $1; $$->attr = ZEND_NAME_NOT_FQ; }
318-
| T_NAMESPACE T_NS_SEPARATOR namespace_name { $$ = $3; $$->attr = ZEND_NAME_RELATIVE; }
319-
| T_NS_SEPARATOR namespace_name { $$ = $2; $$->attr = ZEND_NAME_FQ; }
333+
T_STRING { $$ = $1; $$->attr = ZEND_NAME_NOT_FQ; }
334+
| T_NAME_QUALIFIED { $$ = $1; $$->attr = ZEND_NAME_NOT_FQ; }
335+
| T_NAME_FULLY_QUALIFIED { $$ = $1; $$->attr = ZEND_NAME_FQ; }
336+
| T_NAME_RELATIVE { $$ = $1; $$->attr = ZEND_NAME_RELATIVE; }
320337
;
321338

322339
attribute_decl:
@@ -350,10 +367,10 @@ top_statement:
350367
{ $$ = zend_ast_create(ZEND_AST_HALT_COMPILER,
351368
zend_ast_create_zval_from_long(zend_get_scanned_file_offset()));
352369
zend_stop_lexing(); }
353-
| T_NAMESPACE namespace_name ';'
370+
| T_NAMESPACE namespace_declaration_name ';'
354371
{ $$ = zend_ast_create(ZEND_AST_NAMESPACE, $2, NULL);
355372
RESET_DOC_COMMENT(); }
356-
| T_NAMESPACE namespace_name { RESET_DOC_COMMENT(); }
373+
| T_NAMESPACE namespace_declaration_name { RESET_DOC_COMMENT(); }
357374
'{' top_statement_list '}'
358375
{ $$ = zend_ast_create(ZEND_AST_NAMESPACE, $2, $5); }
359376
| T_NAMESPACE { RESET_DOC_COMMENT(); }
@@ -372,17 +389,13 @@ use_type:
372389
;
373390

374391
group_use_declaration:
375-
namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations possible_comma '}'
392+
legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations possible_comma '}'
376393
{ $$ = zend_ast_create(ZEND_AST_GROUP_USE, $1, $4); }
377-
| T_NS_SEPARATOR namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations possible_comma '}'
378-
{ $$ = zend_ast_create(ZEND_AST_GROUP_USE, $2, $5); }
379394
;
380395

381396
mixed_group_use_declaration:
382-
namespace_name T_NS_SEPARATOR '{' inline_use_declarations possible_comma '}'
397+
legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations possible_comma '}'
383398
{ $$ = zend_ast_create(ZEND_AST_GROUP_USE, $1, $4);}
384-
| T_NS_SEPARATOR namespace_name T_NS_SEPARATOR '{' inline_use_declarations possible_comma '}'
385-
{ $$ = zend_ast_create(ZEND_AST_GROUP_USE, $2, $5); }
386399
;
387400

388401
possible_comma:
@@ -424,8 +437,10 @@ unprefixed_use_declaration:
424437
;
425438

426439
use_declaration:
427-
unprefixed_use_declaration { $$ = $1; }
428-
| T_NS_SEPARATOR unprefixed_use_declaration { $$ = $2; }
440+
legacy_namespace_name
441+
{ $$ = zend_ast_create(ZEND_AST_USE_ELEM, $1, NULL); }
442+
| legacy_namespace_name T_AS T_STRING
443+
{ $$ = zend_ast_create(ZEND_AST_USE_ELEM, $1, $3); }
429444
;
430445

431446
const_list:

Zend/zend_language_scanner.l

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,10 +1596,6 @@ NEWLINE ("\r"|"\n"|"\r\n")
15961596
RETURN_TOKEN(T_PAAMAYIM_NEKUDOTAYIM);
15971597
}
15981598

1599-
<ST_IN_SCRIPTING>"\\" {
1600-
RETURN_TOKEN(T_NS_SEPARATOR);
1601-
}
1602-
16031599
<ST_IN_SCRIPTING>"..." {
16041600
RETURN_TOKEN(T_ELLIPSIS);
16051601
}
@@ -2288,6 +2284,22 @@ inline_char_handler:
22882284
RETURN_TOKEN_WITH_VAL(T_ENCAPSED_AND_WHITESPACE);
22892285
}
22902286
2287+
<ST_IN_SCRIPTING>"namespace"("\\"{LABEL})+ {
2288+
RETURN_TOKEN_WITH_STR(T_NAME_RELATIVE, sizeof("namespace\\") - 1);
2289+
}
2290+
2291+
<ST_IN_SCRIPTING>{LABEL}("\\"{LABEL})+ {
2292+
RETURN_TOKEN_WITH_STR(T_NAME_QUALIFIED, 0);
2293+
}
2294+
2295+
<ST_IN_SCRIPTING>"\\"{LABEL}("\\"{LABEL})* {
2296+
RETURN_TOKEN_WITH_STR(T_NAME_FULLY_QUALIFIED, 1);
2297+
}
2298+
2299+
<ST_IN_SCRIPTING>"\\" {
2300+
RETURN_TOKEN(T_NS_SEPARATOR);
2301+
}
2302+
22912303
<ST_IN_SCRIPTING,ST_VAR_OFFSET>{LABEL} {
22922304
RETURN_TOKEN_WITH_STR(T_STRING, 0);
22932305
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
--TEST--
2+
Tokenization of namespaced names
3+
--FILE--
4+
<?php
5+
6+
$code = <<<'CODE'
7+
<?php
8+
Foo
9+
Foo\Bar
10+
\Foo\Bar
11+
namespace\Foo
12+
Foo \ Bar
13+
CODE;
14+
15+
foreach (PhpToken::getAll($code) as $token) {
16+
echo "{$token->getTokenName()}: \"$token->text\"\n";
17+
}
18+
19+
?>
20+
--EXPECT--
21+
T_OPEN_TAG: "<?php
22+
"
23+
T_STRING: "Foo"
24+
T_WHITESPACE: "
25+
"
26+
T_NAME_QUALIFIED: "Foo\Bar"
27+
T_WHITESPACE: "
28+
"
29+
T_NAME_FULLY_QUALIFIED: "\Foo\Bar"
30+
T_WHITESPACE: "
31+
"
32+
T_NAME_RELATIVE: "namespace\Foo"
33+
T_WHITESPACE: "
34+
"
35+
T_STRING: "Foo"
36+
T_WHITESPACE: " "
37+
T_NS_SEPARATOR: "\"
38+
T_WHITESPACE: " "
39+
T_STRING: "Bar"

ext/tokenizer/tokenizer_data.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) {
7777
REGISTER_LONG_CONSTANT("T_LNUMBER", T_LNUMBER, CONST_CS | CONST_PERSISTENT);
7878
REGISTER_LONG_CONSTANT("T_DNUMBER", T_DNUMBER, CONST_CS | CONST_PERSISTENT);
7979
REGISTER_LONG_CONSTANT("T_STRING", T_STRING, CONST_CS | CONST_PERSISTENT);
80+
REGISTER_LONG_CONSTANT("T_NAME_FULLY_QUALIFIED", T_NAME_FULLY_QUALIFIED, CONST_CS | CONST_PERSISTENT);
81+
REGISTER_LONG_CONSTANT("T_NAME_RELATIVE", T_NAME_RELATIVE, CONST_CS | CONST_PERSISTENT);
82+
REGISTER_LONG_CONSTANT("T_NAME_QUALIFIED", T_NAME_QUALIFIED, CONST_CS | CONST_PERSISTENT);
8083
REGISTER_LONG_CONSTANT("T_VARIABLE", T_VARIABLE, CONST_CS | CONST_PERSISTENT);
8184
REGISTER_LONG_CONSTANT("T_INLINE_HTML", T_INLINE_HTML, CONST_CS | CONST_PERSISTENT);
8285
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)
221224
case T_LNUMBER: return "T_LNUMBER";
222225
case T_DNUMBER: return "T_DNUMBER";
223226
case T_STRING: return "T_STRING";
227+
case T_NAME_FULLY_QUALIFIED: return "T_NAME_FULLY_QUALIFIED";
228+
case T_NAME_RELATIVE: return "T_NAME_RELATIVE";
229+
case T_NAME_QUALIFIED: return "T_NAME_QUALIFIED";
224230
case T_VARIABLE: return "T_VARIABLE";
225231
case T_INLINE_HTML: return "T_INLINE_HTML";
226232
case T_ENCAPSED_AND_WHITESPACE: return "T_ENCAPSED_AND_WHITESPACE";

0 commit comments

Comments
 (0)