diff --git a/README.md b/README.md index 1017536..adbe819 100644 --- a/README.md +++ b/README.md @@ -256,9 +256,9 @@ ast\flags\CLASS_ANONYMOUS // Used by ast\AST_PARAM (combinable) ast\flags\PARAM_REF ast\flags\PARAM_VARIADIC -ast\flags\MODIFIER_PUBLIC (only in php 8.0+) -ast\flags\MODIFIER_PROTECTED (only in php 8.0+) -ast\flags\MODIFIER_PRIVATE (only in php 8.0+) +ast\flags\PARAM_MODIFIER_PUBLIC (available since 1.0.8, same as ast\flags\MODIFIER_* in PHP >= 8.0) +ast\flags\PARAM_MODIFIER_PROTECTED (available since 1.0.8) +ast\flags\PARAM_MODIFIER_PRIVATE (available since 1.0.8) // Used by ast\AST_TYPE (exclusive) ast\flags\TYPE_ARRAY @@ -412,6 +412,7 @@ AST_METHOD: name, docComment, params, stmts, returnType, attributes AST_METHOD_CALL: expr, method, args AST_METHOD_REFERENCE: class, method AST_NAME: name +AST_NAMED_ARG: name, expr // php 8.0 named parameters AST_NAMESPACE: name, stmts AST_NEW: class, args AST_NULLABLE_TYPE: type // Used only since PHP 7.1 diff --git a/ast.c b/ast.c index 540a8d8..eeb6b5a 100644 --- a/ast.c +++ b/ast.c @@ -65,6 +65,14 @@ #if PHP_VERSION_ID < 80000 # define IS_STATIC 20 # define IS_MIXED 21 +/* In PHP 7.0-7.4, PARAM_REF and PARAM_VARIADIC were 1 and 2. */ +# define PARAM_MODIFIER_PUBLIC (1 << 2) +# define PARAM_MODIFIER_PROTECTED (1 << 3) +# define PARAM_MODIFIER_PRIVATE (1 << 4) +#else +# define PARAM_MODIFIER_PUBLIC ZEND_ACC_PUBLIC +# define PARAM_MODIFIER_PROTECTED ZEND_ACC_PROTECTED +# define PARAM_MODIFIER_PRIVATE ZEND_ACC_PRIVATE #endif /* This contains state of the ast Node creator. */ @@ -107,11 +115,9 @@ static const char *class_flags[] = { static const char *param_flags[] = { AST_FLAG(PARAM_REF), AST_FLAG(PARAM_VARIADIC), -#if PHP_VERSION_ID >= 80000 - AST_FLAG(MODIFIER_PUBLIC), - AST_FLAG(MODIFIER_PROTECTED), - AST_FLAG(MODIFIER_PRIVATE), -#endif + AST_FLAG(PARAM_MODIFIER_PUBLIC), + AST_FLAG(PARAM_MODIFIER_PROTECTED), + AST_FLAG(PARAM_MODIFIER_PRIVATE), NULL }; @@ -1368,6 +1374,10 @@ PHP_MINIT_FUNCTION(ast) { ast_register_flag_constant("MODIFIER_ABSTRACT", ZEND_ACC_ABSTRACT); ast_register_flag_constant("MODIFIER_FINAL", ZEND_ACC_FINAL); + ast_register_flag_constant("PARAM_MODIFIER_PUBLIC", PARAM_MODIFIER_PUBLIC); + ast_register_flag_constant("PARAM_MODIFIER_PROTECTED", PARAM_MODIFIER_PROTECTED); + ast_register_flag_constant("PARAM_MODIFIER_PRIVATE", PARAM_MODIFIER_PRIVATE); + ast_register_flag_constant("RETURNS_REF", ZEND_ACC_RETURN_REFERENCE); ast_register_flag_constant("FUNC_RETURNS_REF", ZEND_ACC_RETURN_REFERENCE); ast_register_flag_constant("FUNC_GENERATOR", ZEND_ACC_GENERATOR); diff --git a/ast_data.c b/ast_data.c index 9b6ce55..8538572 100644 --- a/ast_data.c +++ b/ast_data.c @@ -63,6 +63,7 @@ const zend_ast_kind ast_kinds[] = { ZEND_AST_CLASS_CONST_GROUP, ZEND_AST_DIM, ZEND_AST_PROP, + ZEND_AST_NULLSAFE_PROP, ZEND_AST_STATIC_PROP, ZEND_AST_CALL, ZEND_AST_CLASS_CONST, @@ -94,7 +95,9 @@ const zend_ast_kind ast_kinds[] = { ZEND_AST_ATTRIBUTE, ZEND_AST_MATCH, ZEND_AST_MATCH_ARM, + ZEND_AST_NAMED_ARG, ZEND_AST_METHOD_CALL, + ZEND_AST_NULLSAFE_METHOD_CALL, ZEND_AST_STATIC_CALL, ZEND_AST_CONDITIONAL, ZEND_AST_TRY, @@ -170,6 +173,7 @@ const char *ast_kind_to_name(zend_ast_kind kind) { case ZEND_AST_CLASS_CONST_GROUP: return "AST_CLASS_CONST_GROUP"; case ZEND_AST_DIM: return "AST_DIM"; case ZEND_AST_PROP: return "AST_PROP"; + case ZEND_AST_NULLSAFE_PROP: return "AST_NULLSAFE_PROP"; case ZEND_AST_STATIC_PROP: return "AST_STATIC_PROP"; case ZEND_AST_CALL: return "AST_CALL"; case ZEND_AST_CLASS_CONST: return "AST_CLASS_CONST"; @@ -201,7 +205,9 @@ const char *ast_kind_to_name(zend_ast_kind kind) { case ZEND_AST_ATTRIBUTE: return "AST_ATTRIBUTE"; case ZEND_AST_MATCH: return "AST_MATCH"; case ZEND_AST_MATCH_ARM: return "AST_MATCH_ARM"; + case ZEND_AST_NAMED_ARG: return "AST_NAMED_ARG"; case ZEND_AST_METHOD_CALL: return "AST_METHOD_CALL"; + case ZEND_AST_NULLSAFE_METHOD_CALL: return "AST_NULLSAFE_METHOD_CALL"; case ZEND_AST_STATIC_CALL: return "AST_STATIC_CALL"; case ZEND_AST_CONDITIONAL: return "AST_CONDITIONAL"; case ZEND_AST_TRY: return "AST_TRY"; @@ -442,6 +448,12 @@ zend_string *ast_kind_child_name(zend_ast_kind kind, uint32_t child) { case 1: return AST_STR(str_prop); } return NULL; + case ZEND_AST_NULLSAFE_PROP: + switch (child) { + case 0: return AST_STR(str_expr); + case 1: return AST_STR(str_prop); + } + return NULL; case ZEND_AST_STATIC_PROP: switch (child) { case 0: return AST_STR(str_class); @@ -631,6 +643,12 @@ zend_string *ast_kind_child_name(zend_ast_kind kind, uint32_t child) { case 1: return AST_STR(str_expr); } return NULL; + case ZEND_AST_NAMED_ARG: + switch (child) { + case 0: return AST_STR(str_name); + case 1: return AST_STR(str_expr); + } + return NULL; case ZEND_AST_METHOD_CALL: switch (child) { case 0: return AST_STR(str_expr); @@ -638,6 +656,13 @@ zend_string *ast_kind_child_name(zend_ast_kind kind, uint32_t child) { case 2: return AST_STR(str_args); } return NULL; + case ZEND_AST_NULLSAFE_METHOD_CALL: + switch (child) { + case 0: return AST_STR(str_expr); + case 1: return AST_STR(str_method); + case 2: return AST_STR(str_args); + } + return NULL; case ZEND_AST_STATIC_CALL: switch (child) { case 0: return AST_STR(str_class); @@ -759,6 +784,7 @@ void ast_register_kind_constants(INIT_FUNC_ARGS) { REGISTER_NS_LONG_CONSTANT("ast", "AST_CLASS_CONST_GROUP", ZEND_AST_CLASS_CONST_GROUP, CONST_CS | CONST_PERSISTENT); REGISTER_NS_LONG_CONSTANT("ast", "AST_DIM", ZEND_AST_DIM, CONST_CS | CONST_PERSISTENT); REGISTER_NS_LONG_CONSTANT("ast", "AST_PROP", ZEND_AST_PROP, CONST_CS | CONST_PERSISTENT); + REGISTER_NS_LONG_CONSTANT("ast", "AST_NULLSAFE_PROP", ZEND_AST_NULLSAFE_PROP, CONST_CS | CONST_PERSISTENT); REGISTER_NS_LONG_CONSTANT("ast", "AST_STATIC_PROP", ZEND_AST_STATIC_PROP, CONST_CS | CONST_PERSISTENT); REGISTER_NS_LONG_CONSTANT("ast", "AST_CALL", ZEND_AST_CALL, CONST_CS | CONST_PERSISTENT); REGISTER_NS_LONG_CONSTANT("ast", "AST_CLASS_CONST", ZEND_AST_CLASS_CONST, CONST_CS | CONST_PERSISTENT); @@ -790,7 +816,9 @@ void ast_register_kind_constants(INIT_FUNC_ARGS) { REGISTER_NS_LONG_CONSTANT("ast", "AST_ATTRIBUTE", ZEND_AST_ATTRIBUTE, CONST_CS | CONST_PERSISTENT); REGISTER_NS_LONG_CONSTANT("ast", "AST_MATCH", ZEND_AST_MATCH, CONST_CS | CONST_PERSISTENT); REGISTER_NS_LONG_CONSTANT("ast", "AST_MATCH_ARM", ZEND_AST_MATCH_ARM, CONST_CS | CONST_PERSISTENT); + REGISTER_NS_LONG_CONSTANT("ast", "AST_NAMED_ARG", ZEND_AST_NAMED_ARG, CONST_CS | CONST_PERSISTENT); REGISTER_NS_LONG_CONSTANT("ast", "AST_METHOD_CALL", ZEND_AST_METHOD_CALL, CONST_CS | CONST_PERSISTENT); + REGISTER_NS_LONG_CONSTANT("ast", "AST_NULLSAFE_METHOD_CALL", ZEND_AST_NULLSAFE_METHOD_CALL, CONST_CS | CONST_PERSISTENT); REGISTER_NS_LONG_CONSTANT("ast", "AST_STATIC_CALL", ZEND_AST_STATIC_CALL, CONST_CS | CONST_PERSISTENT); REGISTER_NS_LONG_CONSTANT("ast", "AST_CONDITIONAL", ZEND_AST_CONDITIONAL, CONST_CS | CONST_PERSISTENT); REGISTER_NS_LONG_CONSTANT("ast", "AST_TRY", ZEND_AST_TRY, CONST_CS | CONST_PERSISTENT); diff --git a/ast_stub.php b/ast_stub.php index a38f0f6..fb71a54 100644 --- a/ast_stub.php +++ b/ast_stub.php @@ -66,45 +66,48 @@ const AST_GOTO = 285; const AST_BREAK = 286; const AST_CONTINUE = 287; const AST_CLASS_NAME = 276; -const AST_CLASS_CONST_GROUP = 545; +const AST_CLASS_CONST_GROUP = 546; const AST_DIM = 512; const AST_PROP = 513; -const AST_STATIC_PROP = 514; -const AST_CALL = 515; -const AST_CLASS_CONST = 516; -const AST_ASSIGN = 517; -const AST_ASSIGN_REF = 518; -const AST_ASSIGN_OP = 519; -const AST_BINARY_OP = 520; -const AST_ARRAY_ELEM = 525; -const AST_NEW = 526; -const AST_INSTANCEOF = 527; -const AST_YIELD = 528; -const AST_STATIC = 531; -const AST_WHILE = 532; -const AST_DO_WHILE = 533; -const AST_IF_ELEM = 534; -const AST_SWITCH = 535; -const AST_SWITCH_CASE = 536; -const AST_DECLARE = 537; -const AST_PROP_ELEM = 774; -const AST_PROP_GROUP = 773; -const AST_CONST_ELEM = 775; -const AST_USE_TRAIT = 538; -const AST_TRAIT_PRECEDENCE = 539; -const AST_METHOD_REFERENCE = 540; -const AST_NAMESPACE = 541; -const AST_USE_ELEM = 542; -const AST_TRAIT_ALIAS = 543; -const AST_GROUP_USE = 544; -const AST_ATTRIBUTE = 546; -const AST_MATCH = 547; -const AST_MATCH_ARM = 548; +const AST_NULLSAFE_PROP = 514; +const AST_STATIC_PROP = 515; +const AST_CALL = 516; +const AST_CLASS_CONST = 517; +const AST_ASSIGN = 518; +const AST_ASSIGN_REF = 519; +const AST_ASSIGN_OP = 520; +const AST_BINARY_OP = 521; +const AST_ARRAY_ELEM = 526; +const AST_NEW = 527; +const AST_INSTANCEOF = 528; +const AST_YIELD = 529; +const AST_STATIC = 532; +const AST_WHILE = 533; +const AST_DO_WHILE = 534; +const AST_IF_ELEM = 535; +const AST_SWITCH = 536; +const AST_SWITCH_CASE = 537; +const AST_DECLARE = 538; +const AST_PROP_ELEM = 775; +const AST_PROP_GROUP = 774; +const AST_CONST_ELEM = 776; +const AST_USE_TRAIT = 539; +const AST_TRAIT_PRECEDENCE = 540; +const AST_METHOD_REFERENCE = 541; +const AST_NAMESPACE = 542; +const AST_USE_ELEM = 543; +const AST_TRAIT_ALIAS = 544; +const AST_GROUP_USE = 545; +const AST_ATTRIBUTE = 547; +const AST_MATCH = 548; +const AST_MATCH_ARM = 549; +const AST_NAMED_ARG = 550; const AST_METHOD_CALL = 768; -const AST_STATIC_CALL = 769; -const AST_CONDITIONAL = 770; -const AST_TRY = 771; -const AST_CATCH = 772; +const AST_NULLSAFE_METHOD_CALL = 769; +const AST_STATIC_CALL = 770; +const AST_CONDITIONAL = 771; +const AST_TRY = 772; +const AST_CATCH = 773; const AST_FOR = 1024; const AST_FOREACH = 1025; const AST_PARAM = 1280; @@ -121,6 +124,9 @@ const MODIFIER_PRIVATE = 4; const MODIFIER_STATIC = 16; const MODIFIER_ABSTRACT = 64; const MODIFIER_FINAL = 32; +const PARAM_MODIFIER_PUBLIC = 1; +const PARAM_MODIFIER_PROTECTED = 2; +const PARAM_MODIFIER_PRIVATE = 4; const RETURNS_REF = 4096; const FUNC_RETURNS_REF = 4096; const FUNC_GENERATOR = 16777216; @@ -184,14 +190,14 @@ const EXEC_REQUIRE_ONCE = 16; const USE_NORMAL = 1; const USE_FUNCTION = 2; const USE_CONST = 4; -const MAGIC_LINE = 372; -const MAGIC_FILE = 373; -const MAGIC_DIR = 374; -const MAGIC_NAMESPACE = 379; -const MAGIC_FUNCTION = 378; -const MAGIC_METHOD = 377; -const MAGIC_CLASS = 375; -const MAGIC_TRAIT = 376; +const MAGIC_LINE = 375; +const MAGIC_FILE = 376; +const MAGIC_DIR = 377; +const MAGIC_NAMESPACE = 382; +const MAGIC_FUNCTION = 381; +const MAGIC_METHOD = 380; +const MAGIC_CLASS = 378; +const MAGIC_TRAIT = 379; const ARRAY_SYNTAX_LIST = 1; const ARRAY_SYNTAX_LONG = 2; const ARRAY_SYNTAX_SHORT = 3; diff --git a/php_ast.h b/php_ast.h index d3c0550..3324275 100644 --- a/php_ast.h +++ b/php_ast.h @@ -62,10 +62,15 @@ extern ast_str_globals str_globals; # define ZEND_AST_TYPE_UNION ((1 << (ZEND_AST_IS_LIST_SHIFT + 1)) - 2) # define ZEND_AST_ATTRIBUTE_LIST ((1 << (ZEND_AST_IS_LIST_SHIFT + 1)) - 3) # define ZEND_AST_MATCH_ARM_LIST ((1 << (ZEND_AST_IS_LIST_SHIFT + 1)) - 4) +/* 2 child nodes */ # define ZEND_AST_CLASS_CONST_GROUP 0x2fe # define ZEND_AST_ATTRIBUTE 0x2fd # define ZEND_AST_MATCH 0x2fc # define ZEND_AST_MATCH_ARM 0x2fb +# define ZEND_AST_NAMED_ARG 0x2fa +# define ZEND_AST_NULLSAFE_PROP 0x2f9 +/* 3 child nodes */ +# define ZEND_AST_NULLSAFE_METHOD_CALL 0x3ff // NOTE: The first hex digit is the number of child nodes a given kind has #endif diff --git a/tests/attributes_01.phpt b/tests/attributes_01.phpt index c66cc97..da04cb7 100644 --- a/tests/attributes_01.phpt +++ b/tests/attributes_01.phpt @@ -11,13 +11,13 @@ $code = <<<'PHP' > -function test(<> Type $arg) { +@@SomeAttribute +function test(@@namespace\SomeAttribute(2+2) Type $arg) { } -$x = <> function () {}; +$x = @@SomeAttribute function () {}; -$y = <> fn (<<\SomeAttribute>> $a) => $x; +$y = @@SomeAttribute fn (@@\SomeAttribute $a) => $x; PHP; echo ast_dump(ast\parse_code($code, $version=70)); diff --git a/tests/attributes_02.phpt b/tests/attributes_02.phpt index 117f1f0..c6c7c8c 100644 --- a/tests/attributes_02.phpt +++ b/tests/attributes_02.phpt @@ -11,16 +11,16 @@ $code = <<<'PHP' > +@@\SomeAttribute() class X { - <> - <> + @@Attr1 + @@Attr2(true) public $prop; - <> + @@Attr3 public const CONST_WITH_ATTRIBUTE = 123; - <> + @@Attr4 public static function hasAttribute() {} } PHP; diff --git a/tests/metadata.phpt b/tests/metadata.phpt index 6dead6e..8eee972 100644 --- a/tests/metadata.phpt +++ b/tests/metadata.phpt @@ -88,6 +88,7 @@ AST_CLASS_NAME: [] AST_CLASS_CONST_GROUP: (combinable) [MODIFIER_PUBLIC, MODIFIER_PROTECTED, MODIFIER_PRIVATE] AST_DIM: (combinable) [DIM_ALTERNATIVE_SYNTAX] AST_PROP: [] +AST_NULLSAFE_PROP: [] AST_STATIC_PROP: [] AST_CALL: [] AST_CLASS_CONST: [] @@ -119,11 +120,13 @@ AST_GROUP_USE: [USE_NORMAL, USE_FUNCTION, USE_CONST] AST_ATTRIBUTE: [] AST_MATCH: [] AST_MATCH_ARM: [] +AST_NAMED_ARG: [] AST_METHOD_CALL: [] +AST_NULLSAFE_METHOD_CALL: [] AST_STATIC_CALL: [] AST_CONDITIONAL: (combinable) [PARENTHESIZED_CONDITIONAL] AST_TRY: [] AST_CATCH: [] AST_FOR: [] AST_FOREACH: [] -AST_PARAM: (combinable) [PARAM_REF, PARAM_VARIADIC%S] +AST_PARAM: (combinable) [PARAM_REF, PARAM_VARIADIC, PARAM_MODIFIER_PUBLIC, PARAM_MODIFIER_PROTECTED, PARAM_MODIFIER_PRIVATE] diff --git a/tests/parse_code_parse_error.phpt b/tests/parse_code_parse_error.phpt index 57504cf..d82c043 100644 --- a/tests/parse_code_parse_error.phpt +++ b/tests/parse_code_parse_error.phpt @@ -18,11 +18,11 @@ try { ?> --EXPECTF-- -ParseError: syntax error, unexpected '&', expecting end of file in string code:1 +ParseError: syntax error, unexpected %s&%s expecting end of file in string code:1 Stack trace: #0 %s(%d): ast\parse_code('%s', %d) #1 {main} -ParseError: syntax error, unexpected '&', expecting end of file in file.php:1 +ParseError: syntax error, unexpected %s&%s expecting end of file in file.php:1 Stack trace: #0 %s(%d): ast\parse_code('%s', %d, 'file.php') #1 {main} diff --git a/tests/parse_file_parse_error.phpt b/tests/parse_file_parse_error.phpt index 88c0678..8e4a003 100644 --- a/tests/parse_file_parse_error.phpt +++ b/tests/parse_file_parse_error.phpt @@ -11,7 +11,7 @@ try { ?> --EXPECTF-- -ParseError: syntax error, unexpected ')' in %stests/invalid_file.php:3 +ParseError: syntax error, unexpected %s)%s in %stests/invalid_file.php:3 Stack trace: #0 %s(%d): ast\parse_file('%s', %d) #1 {main} diff --git a/tests/php80_named_params.phpt b/tests/php80_named_params.phpt new file mode 100644 index 0000000..8ecffaf --- /dev/null +++ b/tests/php80_named_params.phpt @@ -0,0 +1,52 @@ +--TEST-- +Named parameters in PHP 8.0 +--SKIPIF-- += 8.0 only'); ?> +--FILE-- +count(1, myVar:$foo, myVar: 1); // error +PHP; + +$node = ast\parse_code($code, $version=70); +echo ast_dump($node), "\n"; +--EXPECTF-- +AST_STMT_LIST + 0: AST_CALL + expr: AST_VAR + name: "foo" + args: AST_ARG_LIST + 0: AST_NAMED_ARG + name: "first" + expr: 1 + 1: AST_NAMED_ARG + name: "second" + expr: 2 + 1: AST_CALL + expr: AST_NAME + flags: NAME_NOT_FQ (%d) + name: "count" + args: AST_ARG_LIST + 0: AST_NAMED_ARG + name: "var" + expr: AST_VAR + name: "argv" + 2: AST_METHOD_CALL + expr: AST_VAR + name: "other" + method: "count" + args: AST_ARG_LIST + 0: 1 + 1: AST_NAMED_ARG + name: "myVar" + expr: AST_VAR + name: "foo" + 2: AST_NAMED_ARG + name: "myVar" + expr: 1 diff --git a/tests/php80_nullsafe_operator.phpt b/tests/php80_nullsafe_operator.phpt new file mode 100644 index 0000000..38e253f --- /dev/null +++ b/tests/php80_nullsafe_operator.phpt @@ -0,0 +1,42 @@ +--TEST-- +Nullsafe operator in PHP 8.0 +--SKIPIF-- += 8.0 only'); ?> +--FILE-- +bar(2); +$a = $b?->c; +$a = new $b?->c; +PHP; + +$node = ast\parse_code($code, $version=70); +echo ast_dump($node), "\n"; +--EXPECTF-- +AST_STMT_LIST + 0: AST_NULLSAFE_METHOD_CALL + expr: AST_VAR + name: "foo" + method: "bar" + args: AST_ARG_LIST + 0: 2 + 1: AST_ASSIGN + var: AST_VAR + name: "a" + expr: AST_NULLSAFE_PROP + expr: AST_VAR + name: "b" + prop: "c" + 2: AST_ASSIGN + var: AST_VAR + name: "a" + expr: AST_NEW + class: AST_NULLSAFE_PROP + expr: AST_VAR + name: "b" + prop: "c" + args: AST_ARG_LIST \ No newline at end of file diff --git a/tests/php80_promotion.phpt b/tests/php80_promotion.phpt index 5c58380..b9a91d6 100644 --- a/tests/php80_promotion.phpt +++ b/tests/php80_promotion.phpt @@ -38,13 +38,13 @@ AST_STMT_LIST docComment: "/** Doc comment for __construct */" params: AST_PARAM_LIST 0: AST_PARAM - flags: MODIFIER_PUBLIC (%d) + flags: PARAM_MODIFIER_PUBLIC (%d) type: AST_TYPE flags: TYPE_LONG (4) name: "a" default: null 1: AST_PARAM - flags: PARAM_REF | MODIFIER_PRIVATE (%d) + flags: PARAM_REF | PARAM_MODIFIER_PRIVATE (%d) type: AST_NAME flags: NAME_NOT_FQ (1) name: "stdClass" @@ -54,7 +54,7 @@ AST_STMT_LIST flags: NAME_NOT_FQ (1) name: "null" 2: AST_PARAM - flags: MODIFIER_PROTECTED (%d) + flags: PARAM_MODIFIER_PROTECTED (%d) type: AST_TYPE flags: TYPE_ITERABLE (13) name: "c"