diff --git a/Zend/tests/ctor_promotion_abstract.phpt b/Zend/tests/ctor_promotion_abstract.phpt new file mode 100644 index 0000000000000..937e247acde2d --- /dev/null +++ b/Zend/tests/ctor_promotion_abstract.phpt @@ -0,0 +1,12 @@ +--TEST-- +Constructor promotion cannot be used inside an abstract constructor +--FILE-- + +--EXPECTF-- +Fatal error: Cannot declare promoted property in an abstract constructor in %s on line %d diff --git a/Zend/tests/ctor_promotion_additional_modifiers.phpt b/Zend/tests/ctor_promotion_additional_modifiers.phpt new file mode 100644 index 0000000000000..fb3b092d66f50 --- /dev/null +++ b/Zend/tests/ctor_promotion_additional_modifiers.phpt @@ -0,0 +1,12 @@ +--TEST-- +Constructor promotion only permits visibility modifiers +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected 'static' (T_STATIC), expecting variable (T_VARIABLE) in %s on line %d diff --git a/Zend/tests/ctor_promotion_basic.phpt b/Zend/tests/ctor_promotion_basic.phpt new file mode 100644 index 0000000000000..206f99fd40942 --- /dev/null +++ b/Zend/tests/ctor_promotion_basic.phpt @@ -0,0 +1,21 @@ +--TEST-- +Constructor promotion (basic example) +--FILE-- +x = "foo"; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Cannot assign string to property Point::$x of type int diff --git a/Zend/tests/ctor_promotion_by_ref.phpt b/Zend/tests/ctor_promotion_by_ref.phpt new file mode 100644 index 0000000000000..4b07149c579ef --- /dev/null +++ b/Zend/tests/ctor_promotion_by_ref.phpt @@ -0,0 +1,20 @@ +--TEST-- +Constructor promotion of by-ref parameter +--FILE-- +array); + +?> +--EXPECT-- +array(1) { + [0]=> + int(42) +} diff --git a/Zend/tests/ctor_promotion_callable_type.phpt b/Zend/tests/ctor_promotion_callable_type.phpt new file mode 100644 index 0000000000000..ae10512ad54a3 --- /dev/null +++ b/Zend/tests/ctor_promotion_callable_type.phpt @@ -0,0 +1,12 @@ +--TEST-- +Type of promoted property may not be callable +--FILE-- + +--EXPECTF-- +Fatal error: Property Test::$callable cannot have type callable in %s on line %d diff --git a/Zend/tests/ctor_promotion_defaults.phpt b/Zend/tests/ctor_promotion_defaults.phpt new file mode 100644 index 0000000000000..9999e8a539ce8 --- /dev/null +++ b/Zend/tests/ctor_promotion_defaults.phpt @@ -0,0 +1,43 @@ +--TEST-- +Constructor promotion with default values +--FILE-- + +--EXPECT-- +object(Point)#1 (3) { + ["x"]=> + float(10) + ["y"]=> + float(1) + ["z"]=> + float(2) +} +object(Point)#1 (3) { + ["x"]=> + float(10) + ["y"]=> + float(11) + ["z"]=> + float(2) +} +object(Point)#1 (3) { + ["x"]=> + float(10) + ["y"]=> + float(11) + ["z"]=> + float(12) +} diff --git a/Zend/tests/ctor_promotion_interface.phpt b/Zend/tests/ctor_promotion_interface.phpt new file mode 100644 index 0000000000000..7cc856b93a3af --- /dev/null +++ b/Zend/tests/ctor_promotion_interface.phpt @@ -0,0 +1,12 @@ +--TEST-- +Constructor promotion cannot be used inside an abstract constructor (interface variant) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot declare promoted property in an abstract constructor in %s on line %d diff --git a/Zend/tests/ctor_promotion_mixing.phpt b/Zend/tests/ctor_promotion_mixing.phpt new file mode 100644 index 0000000000000..cdaa718394d10 --- /dev/null +++ b/Zend/tests/ctor_promotion_mixing.phpt @@ -0,0 +1,54 @@ +--TEST-- +Constructor promotiong mixed with other properties, parameters and code +--FILE-- +prop2 = $prop1 . $param2; + } +} + +var_dump(new Test("Foo", "Bar")); +echo "\n"; +echo new ReflectionClass(Test::class), "\n"; + +?> +--EXPECTF-- +object(Test)#1 (2) { + ["prop2"]=> + string(6) "FooBar" + ["prop1"]=> + string(3) "Foo" +} + +Class [ class Test ] { + @@ %s + + - Constants [0] { + } + + - Static properties [0] { + } + + - Static methods [0] { + } + + - Properties [2] { + Property [ public $prop2 ] + Property [ public $prop1 ] + } + + - Methods [1] { + Method [ public method __construct ] { + @@ %s + + - Parameters [2] { + Parameter #0 [ string $prop1 = '' ] + Parameter #1 [ $param2 = '' ] + } + } + } +} diff --git a/Zend/tests/ctor_promotion_not_a_ctor.phpt b/Zend/tests/ctor_promotion_not_a_ctor.phpt new file mode 100644 index 0000000000000..110ee8a5ce756 --- /dev/null +++ b/Zend/tests/ctor_promotion_not_a_ctor.phpt @@ -0,0 +1,12 @@ +--TEST-- +Constructor promotion can only be used in constructors ... duh +--FILE-- + +--EXPECTF-- +Fatal error: Cannot declare promoted property outside a constructor in %s on line %d diff --git a/Zend/tests/ctor_promotion_null_default.phpt b/Zend/tests/ctor_promotion_null_default.phpt new file mode 100644 index 0000000000000..61c7b2d10c04b --- /dev/null +++ b/Zend/tests/ctor_promotion_null_default.phpt @@ -0,0 +1,12 @@ +--TEST-- +Constructor promotion with null default, requires an explicitly nullable type +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use null as default value for parameter $x of type int in %s on line %d diff --git a/Zend/tests/ctor_promotion_repeated_prop.phpt b/Zend/tests/ctor_promotion_repeated_prop.phpt new file mode 100644 index 0000000000000..6f4a9ffd02d1e --- /dev/null +++ b/Zend/tests/ctor_promotion_repeated_prop.phpt @@ -0,0 +1,14 @@ +--TEST-- +Clash between promoted and explicit property +--FILE-- + +--EXPECTF-- +Fatal error: Cannot redeclare Test::$prop in %s on line %d diff --git a/Zend/tests/ctor_promotion_trait.phpt b/Zend/tests/ctor_promotion_trait.phpt new file mode 100644 index 0000000000000..4c109157ae7ee --- /dev/null +++ b/Zend/tests/ctor_promotion_trait.phpt @@ -0,0 +1,21 @@ +--TEST-- +Constructor promotion can be used inside a trait +--FILE-- + +--EXPECT-- +object(Test2)#1 (1) { + ["prop"]=> + int(42) +} diff --git a/Zend/tests/ctor_promotion_variadic.phpt b/Zend/tests/ctor_promotion_variadic.phpt new file mode 100644 index 0000000000000..7e1e81cad832d --- /dev/null +++ b/Zend/tests/ctor_promotion_variadic.phpt @@ -0,0 +1,12 @@ +--TEST-- +Cannot use constructor promotion with variadic parameter +--FILE-- + +--EXPECTF-- +Fatal error: Cannot declare variadic promoted property in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index ff466c5ef5740..ea2f713583770 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5761,6 +5761,8 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall zend_string *name = zval_make_interned_string(zend_ast_get_zval(var_ast)); zend_bool is_ref = (param_ast->attr & ZEND_PARAM_REF) != 0; zend_bool is_variadic = (param_ast->attr & ZEND_PARAM_VARIADIC) != 0; + uint32_t visibility = + param_ast->attr & (ZEND_ACC_PUBLIC|ZEND_ACC_PROTECTED|ZEND_ACC_PRIVATE); znode var_node, default_node; zend_uchar opcode; @@ -5829,16 +5831,16 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall if (type_ast) { uint32_t default_type = default_ast ? Z_TYPE(default_node.u.constant) : IS_UNDEF; + zend_bool force_nullable = default_type == IS_NULL && !visibility; op_array->fn_flags |= ZEND_ACC_HAS_TYPE_HINTS; - arg_info->type = zend_compile_typename( - type_ast, default_type == IS_NULL, /* use_arena */ 0); + arg_info->type = zend_compile_typename(type_ast, force_nullable, /* use_arena */ 0); if (ZEND_TYPE_FULL_MASK(arg_info->type) & MAY_BE_VOID) { zend_error_noreturn(E_COMPILE_ERROR, "void cannot be used as a parameter type"); } - if (default_type > IS_NULL && default_type != IS_CONSTANT_AST + if (default_type != IS_UNDEF && default_type != IS_CONSTANT_AST && !force_nullable && !zend_is_valid_default_value(arg_info->type, &default_node.u.constant)) { zend_string *type_str = zend_type_to_string(arg_info->type); zend_error_noreturn(E_COMPILE_ERROR, @@ -5863,6 +5865,49 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall opline->op2.num = type_ast ? ZEND_TYPE_FULL_MASK(arg_info->type) : MAY_BE_ANY; } + + if (visibility) { + zend_op_array *op_array = CG(active_op_array); + zend_class_entry *scope = op_array->scope; + zend_bool is_ctor = + scope && zend_string_equals_literal_ci(op_array->function_name, "__construct"); + if (!is_ctor) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot declare promoted property outside a constructor"); + } + if ((op_array->fn_flags & ZEND_ACC_ABSTRACT) + || (scope->ce_flags & ZEND_ACC_INTERFACE)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot declare promoted property in an abstract constructor"); + } + if (is_variadic) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot declare variadic promoted property"); + } + if (zend_hash_exists(&scope->properties_info, name)) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s::$%s", + ZSTR_VAL(scope->name), ZSTR_VAL(name)); + } + if (ZEND_TYPE_FULL_MASK(arg_info->type) & MAY_BE_CALLABLE) { + zend_string *str = zend_type_to_string(arg_info->type); + zend_error_noreturn(E_COMPILE_ERROR, + "Property %s::$%s cannot have type %s", + ZSTR_VAL(scope->name), ZSTR_VAL(name), ZSTR_VAL(str)); + } + + /* Always use uninitialized as the default. */ + zval default_value; + ZVAL_UNDEF(&default_value); + + /* Recompile the type, as it has different memory management requirements. */ + zend_type type = ZEND_TYPE_INIT_NONE(0); + if (type_ast) { + type = zend_compile_typename(type_ast, /* force_allow_null */ 0, /* use_arena */ 1); + } + + zend_declare_typed_property( + scope, name, &default_value, visibility, /* doc_comment */ NULL, type); + } } /* These are assigned at the end to avoid uninitialized memory in case of an error */ @@ -5874,6 +5919,29 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32_t fall op_array->num_args--; } zend_set_function_arg_flags((zend_function*)op_array); + + for (i = 0; i < list->children; i++) { + zend_ast *param_ast = list->child[i]; + zend_bool is_ref = (param_ast->attr & ZEND_PARAM_REF) != 0; + uint32_t visibility = + param_ast->attr & (ZEND_ACC_PUBLIC|ZEND_ACC_PROTECTED|ZEND_ACC_PRIVATE); + if (!visibility) { + continue; + } + + /* Emit $this->prop = $prop for promoted properties. */ + zend_string *name = zend_ast_get_str(param_ast->child[1]); + znode name_node, value_node; + name_node.op_type = IS_CONST; + ZVAL_STR_COPY(&name_node.u.constant, name); + value_node.op_type = IS_CV; + value_node.u.op.var = lookup_cv(name); + + zend_op *opline = zend_emit_op(NULL, + is_ref ? ZEND_ASSIGN_OBJ_REF : ZEND_ASSIGN_OBJ, NULL, &name_node); + opline->extended_value = zend_alloc_cache_slots(3); + zend_emit_op_data(&value_node); + } } /* }}} */ diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 8a64ed86c3d03..990e86e68e708 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -863,8 +863,9 @@ ZEND_API zend_string *zend_type_to_string(zend_type type); #define ZEND_FETCH_CLASS_ALLOW_UNLINKED 0x0400 #define ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED 0x0800 -#define ZEND_PARAM_REF (1<<0) -#define ZEND_PARAM_VARIADIC (1<<1) +/* These should not clash with ZEND_ACC_(PUBLIC|PROTECTED|PRIVATE) */ +#define ZEND_PARAM_REF (1<<3) +#define ZEND_PARAM_VARIADIC (1<<4) #define ZEND_NAME_FQ 0 #define ZEND_NAME_NOT_FQ 1 diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index c1ced9a35aa21..76e8398702530 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -259,7 +259,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type inline_function union_type %type returns_ref function fn is_reference is_variadic variable_modifiers -%type method_modifiers non_empty_member_modifiers member_modifier +%type method_modifiers non_empty_member_modifiers member_modifier optional_visibility_modifier %type class_modifiers class_modifier use_type backup_fn_flags %type backup_lex_pos @@ -645,11 +645,20 @@ non_empty_parameter_list: { $$ = zend_ast_list_add($1, $3); } ; +optional_visibility_modifier: + %empty { $$ = 0; } + | T_PUBLIC { $$ = ZEND_ACC_PUBLIC; } + | T_PROTECTED { $$ = ZEND_ACC_PROTECTED; } + | T_PRIVATE { $$ = ZEND_ACC_PRIVATE; } +; + parameter: - optional_type_without_static is_reference is_variadic T_VARIABLE - { $$ = zend_ast_create_ex(ZEND_AST_PARAM, $2 | $3, $1, $4, NULL); } - | optional_type_without_static is_reference is_variadic T_VARIABLE '=' expr - { $$ = zend_ast_create_ex(ZEND_AST_PARAM, $2 | $3, $1, $4, $6); } + optional_visibility_modifier optional_type_without_static + is_reference is_variadic T_VARIABLE + { $$ = zend_ast_create_ex(ZEND_AST_PARAM, $1 | $3 | $4, $2, $5, NULL); } + | optional_visibility_modifier optional_type_without_static + is_reference is_variadic T_VARIABLE '=' expr + { $$ = zend_ast_create_ex(ZEND_AST_PARAM, $1 | $3 | $4, $2, $5, $7); } ;