Skip to content
Permalink
Browse files

Introduced "zend_type" - an abstraction for type-hinting representation.

  • Loading branch information...
dstogov committed Jan 13, 2017
1 parent 28391c3 commit 141d1ba9801f742dc5d9ccd06e02b94284c4deb7
@@ -2187,11 +2187,15 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio
/* Don't count the variadic argument */
internal_function->num_args--;
}
if (info->type_hint) {
if (info->class_name) {
ZEND_ASSERT(info->type_hint == IS_OBJECT);
if (!scope && (!strcasecmp(info->class_name, "self") || !strcasecmp(info->class_name, "parent"))) {
zend_error_noreturn(E_CORE_ERROR, "Cannot declare a return type of %s outside of a class scope", info->class_name);
if (ZEND_TYPE_IS_SET(info->type)) {
if (ZEND_TYPE_IS_CLASS(info->type)) {
const char *type_name = (const char*)info->type;

if (type_name[0] == '?') {
type_name++;
}
if (!scope && (!strcasecmp(type_name, "self") || !strcasecmp(type_name, "parent"))) {
zend_error_noreturn(E_CORE_ERROR, "Cannot declare a return type of %s outside of a class scope", type_name);
}
}

@@ -2248,14 +2252,43 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio
if (reg_function->common.arg_info && reg_function->common.num_args) {
uint32_t i;
for (i = 0; i < reg_function->common.num_args; i++) {
if (reg_function->common.arg_info[i].class_name ||
reg_function->common.arg_info[i].type_hint) {
if (ZEND_TYPE_IS_SET(reg_function->common.arg_info[i].type)) {
reg_function->common.fn_flags |= ZEND_ACC_HAS_TYPE_HINTS;
break;
}
}
}

if (reg_function->common.arg_info &&
(reg_function->common.fn_flags & (ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_HAS_TYPE_HINTS))) {
/* convert "const char*" class type names into "zend_string*" */
uint32_t i;
uint32_t num_args = reg_function->common.num_args + 1;
zend_arg_info *arg_info = reg_function->common.arg_info - 1;
zend_arg_info *new_arg_info;

if (reg_function->common.fn_flags & ZEND_ACC_VARIADIC) {
num_args++;
}
new_arg_info = malloc(sizeof(zend_arg_info) * num_args);
memcpy(new_arg_info, arg_info, sizeof(zend_arg_info) * num_args);
reg_function->common.arg_info = new_arg_info + 1;
for (i = 0; i < num_args; i++) {
if (ZEND_TYPE_IS_CLASS(new_arg_info[i].type)) {
const char *class_name = (const char*)new_arg_info[i].type;
zend_bool allow_null = 0;
zend_string *str;

if (class_name[0] == '?') {
class_name++;
allow_null = 1;
}
str = zend_new_interned_string(zend_string_init(class_name, strlen(class_name), 1));
new_arg_info[i].type = ZEND_TYPE_ENCODE_CLASS(str, allow_null);
}
}
}

if (scope) {
/* Look for ctor, dtor, clone
* If it's an old-style constructor, store it only if we don't have
@@ -97,24 +97,30 @@ typedef struct _zend_fcall_info_cache {

#define ZEND_FE_END { NULL, NULL, NULL, 0, 0 }

#define ZEND_ARG_INFO(pass_by_ref, name) { #name, NULL, 0, pass_by_ref, 0, 0 },
#define ZEND_ARG_PASS_INFO(pass_by_ref) { NULL, NULL, 0, pass_by_ref, 0, 0 },
#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, #classname, IS_OBJECT, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) { #name, NULL, IS_ARRAY, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_CALLABLE_INFO(pass_by_ref, name, allow_null) { #name, NULL, IS_CALLABLE, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) { #name, NULL, type_hint, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) { #name, NULL, 0, pass_by_ref, 0, 1 },
#define ZEND_ARG_INFO(pass_by_ref, name) { #name, 0, pass_by_ref, 0},
#define ZEND_ARG_PASS_INFO(pass_by_ref) { NULL, 0, pass_by_ref, 0},
#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, ZEND_TYPE_ENCODE_CLASS_CONST(#classname, allow_null), pass_by_ref, 0 },
#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) { #name, ZEND_TYPE_ENCODE(IS_ARRAY, allow_null), pass_by_ref, 0 },
#define ZEND_ARG_CALLABLE_INFO(pass_by_ref, name, allow_null) { #name, ZEND_TYPE_ENCODE(IS_CALLABLE, allow_null), pass_by_ref, 0 },
#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) { #name, ZEND_TYPE_ENCODE(type_hint, allow_null), pass_by_ref, 0 },
#define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) { #name, 0, pass_by_ref, 1 },


#define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, return_reference, required_num_args, type, class_name, allow_null) \
#define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(name, return_reference, required_num_args, classname, allow_null) \
static const zend_internal_arg_info name[] = { \
{ (const char*)(zend_uintptr_t)(required_num_args), class_name, type, return_reference, allow_null, 0 },
#define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(name, type, class_name, allow_null) \
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, 0, -1, type, class_name, allow_null)
{ (const char*)(zend_uintptr_t)(required_num_args), ZEND_TYPE_ENCODE_CLASS_CONST(#classname, allow_null), return_reference, 0 },
#define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO(name, class_name, allow_null) \
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, 0, -1, class_name, allow_null)

#define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, return_reference, required_num_args, type, allow_null) \
static const zend_internal_arg_info name[] = { \
{ (const char*)(zend_uintptr_t)(required_num_args), ZEND_TYPE_ENCODE(type, allow_null), return_reference, 0 },
#define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(name, type, allow_null) \
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, 0, -1, type, allow_null)

#define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args) \
static const zend_internal_arg_info name[] = { \
{ (const char*)(zend_uintptr_t)(required_num_args), NULL, 0, return_reference, 0, 0 },
{ (const char*)(zend_uintptr_t)(required_num_args), 0, return_reference, 0 },
#define ZEND_BEGIN_ARG_INFO(name, _unused) \
ZEND_BEGIN_ARG_INFO_EX(name, 0, ZEND_RETURN_VALUE, -1)
#define ZEND_END_ARG_INFO() };
@@ -256,9 +256,9 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_extension_loaded, 0, 0, 1)
ZEND_END_ARG_INFO()

#if ZEND_DEBUG
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(arginfo_zend_test_func, IS_ARRAY, NULL, 0)
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(arginfo_zend_test_func, IS_ARRAY, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(arginfo_zend_test_func2, IS_ARRAY, NULL, 1)
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(arginfo_zend_test_func2, IS_ARRAY, 1)
ZEND_END_ARG_INFO()
#endif

@@ -1274,17 +1274,17 @@ static void zend_mark_function_as_generator() /* {{{ */
if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
zend_arg_info return_info = CG(active_op_array)->arg_info[-1];

if (return_info.type_hint != IS_ITERABLE) {
if (ZEND_TYPE_CODE(return_info.type) != IS_ITERABLE) {
const char *msg = "Generators may only declare a return type of Generator, Iterator, Traversable, or iterable, %s is not permitted";

if (!return_info.class_name) {
zend_error_noreturn(E_COMPILE_ERROR, msg, zend_get_type_by_const(return_info.type_hint));
if (!ZEND_TYPE_IS_CLASS(return_info.type)) {
zend_error_noreturn(E_COMPILE_ERROR, msg, zend_get_type_by_const(ZEND_TYPE_CODE(return_info.type)));
}

if (!zend_string_equals_literal_ci(return_info.class_name, "Traversable")
&& !zend_string_equals_literal_ci(return_info.class_name, "Iterator")
&& !zend_string_equals_literal_ci(return_info.class_name, "Generator")) {
zend_error_noreturn(E_COMPILE_ERROR, msg, ZSTR_VAL(return_info.class_name));
if (!zend_string_equals_literal_ci(ZEND_TYPE_NAME(return_info.type), "Traversable")
&& !zend_string_equals_literal_ci(ZEND_TYPE_NAME(return_info.type), "Iterator")
&& !zend_string_equals_literal_ci(ZEND_TYPE_NAME(return_info.type), "Generator")) {
zend_error_noreturn(E_COMPILE_ERROR, msg, ZSTR_VAL(ZEND_TYPE_NAME(return_info.type)));
}
}
}
@@ -2323,26 +2323,26 @@ static zend_op *zend_delayed_compile_end(uint32_t offset) /* {{{ */
static void zend_emit_return_type_check(
znode *expr, zend_arg_info *return_info, zend_bool implicit) /* {{{ */
{
/* `return ...;` is illegal in a void function (but `return;` isn't) */
if (return_info->type_hint == IS_VOID) {
if (expr) {
if (expr->op_type == IS_CONST && Z_TYPE(expr->u.constant) == IS_NULL) {
zend_error_noreturn(E_COMPILE_ERROR,
"A void function must not return a value "
"(did you mean \"return;\" instead of \"return null;\"?)");
} else {
zend_error_noreturn(E_COMPILE_ERROR, "A void function must not return a value");
if (ZEND_TYPE_IS_SET(return_info->type)) {
zend_op *opline;

/* `return ...;` is illegal in a void function (but `return;` isn't) */
if (ZEND_TYPE_CODE(return_info->type) == IS_VOID) {
if (expr) {
if (expr->op_type == IS_CONST && Z_TYPE(expr->u.constant) == IS_NULL) {
zend_error_noreturn(E_COMPILE_ERROR,
"A void function must not return a value "
"(did you mean \"return;\" instead of \"return null;\"?)");
} else {
zend_error_noreturn(E_COMPILE_ERROR, "A void function must not return a value");
}
}
/* we don't need run-time check */
return;
}
/* we don't need run-time check */
return;
}

if (return_info->type_hint != IS_UNDEF) {
zend_op *opline;

if (!expr && !implicit) {
if (return_info->allow_null) {
if (ZEND_TYPE_ALLOW_NULL(return_info->type)) {
zend_error_noreturn(E_COMPILE_ERROR,
"A function with return type must return a value "
"(did you mean \"return null;\" instead of \"return;\"?)");
@@ -2353,11 +2353,11 @@ static void zend_emit_return_type_check(
}

if (expr && expr->op_type == IS_CONST) {
if ((return_info->type_hint == Z_TYPE(expr->u.constant))
||((return_info->type_hint == _IS_BOOL)
if ((ZEND_TYPE_CODE(return_info->type) == Z_TYPE(expr->u.constant))
||((ZEND_TYPE_CODE(return_info->type) == _IS_BOOL)
&& (Z_TYPE(expr->u.constant) == IS_FALSE
|| Z_TYPE(expr->u.constant) == IS_TRUE))
|| (return_info->allow_null
|| (ZEND_TYPE_ALLOW_NULL(return_info->type)
&& Z_TYPE(expr->u.constant) == IS_NULL)) {
/* we don't need run-time check */
return;
@@ -2369,7 +2369,7 @@ static void zend_emit_return_type_check(
opline->result_type = expr->op_type = IS_TMP_VAR;
opline->result.var = expr->u.op.var = get_temporary_variable(CG(active_op_array));
}
if (return_info->class_name) {
if (ZEND_TYPE_IS_CLASS(return_info->type)) {
opline->op2.num = CG(active_op_array)->cache_size;
CG(active_op_array)->cache_size += sizeof(void*);
} else {
@@ -5038,10 +5038,10 @@ ZEND_API void zend_set_function_arg_flags(zend_function *func) /* {{{ */
}
/* }}} */

static void zend_compile_typename(zend_ast *ast, zend_arg_info *arg_info) /* {{{ */
static void zend_compile_typename(zend_ast *ast, zend_arg_info *arg_info, zend_bool allow_null) /* {{{ */
{
if (ast->kind == ZEND_AST_TYPE) {
arg_info->type_hint = ast->attr;
arg_info->type = ZEND_TYPE_ENCODE(ast->attr, allow_null);
} else {
zend_string *class_name = zend_ast_get_str(ast);
zend_uchar type = zend_lookup_builtin_type_by_name(class_name);
@@ -5052,7 +5052,7 @@ static void zend_compile_typename(zend_ast *ast, zend_arg_info *arg_info) /* {{{
"Scalar type declaration '%s' must be unqualified",
ZSTR_VAL(zend_string_tolower(class_name)));
}
arg_info->type_hint = type;
arg_info->type = ZEND_TYPE_ENCODE(type, allow_null);
} else {
uint32_t fetch_type = zend_get_class_fetch_type_ast(ast);
if (fetch_type == ZEND_FETCH_CLASS_DEFAULT) {
@@ -5063,8 +5063,7 @@ static void zend_compile_typename(zend_ast *ast, zend_arg_info *arg_info) /* {{{
zend_string_addref(class_name);
}

arg_info->type_hint = IS_OBJECT;
arg_info->class_name = class_name;
arg_info->type = ZEND_TYPE_ENCODE_CLASS(class_name, allow_null);
}
}
}
@@ -5078,23 +5077,23 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */
zend_arg_info *arg_infos;

if (return_type_ast) {
zend_bool allow_null = 0;

/* Use op_array->arg_info[-1] for return type */
arg_infos = safe_emalloc(sizeof(zend_arg_info), list->children + 1, 0);
arg_infos->name = NULL;
arg_infos->pass_by_reference = (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0;
arg_infos->is_variadic = 0;
arg_infos->type_hint = 0;
arg_infos->allow_null = 0;
arg_infos->class_name = NULL;
arg_infos->type = 0;

if (return_type_ast->attr & ZEND_TYPE_NULLABLE) {
arg_infos->allow_null = 1;
allow_null = 1;
return_type_ast->attr &= ~ZEND_TYPE_NULLABLE;
}

zend_compile_typename(return_type_ast, arg_infos);
zend_compile_typename(return_type_ast, arg_infos, allow_null);

if (arg_infos->type_hint == IS_VOID && arg_infos->allow_null) {
if (ZEND_TYPE_CODE(arg_infos->type) == IS_VOID && ZEND_TYPE_ALLOW_NULL(arg_infos->type)) {
zend_error_noreturn(E_COMPILE_ERROR, "Void type cannot be nullable");
}

@@ -5171,48 +5170,48 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */
arg_info->name = zend_string_copy(name);
arg_info->pass_by_reference = is_ref;
arg_info->is_variadic = is_variadic;
arg_info->type_hint = 0;
arg_info->allow_null = 1;
arg_info->class_name = NULL;
/* TODO: Keep compatibility, but may be better reset "allow_null" ??? */
arg_info->type = ZEND_TYPE_ENCODE(0, 1);

if (type_ast) {
zend_bool allow_null;
zend_bool has_null_default = default_ast
&& (Z_TYPE(default_node.u.constant) == IS_NULL
|| (Z_TYPE(default_node.u.constant) == IS_CONSTANT
&& strcasecmp(Z_STRVAL(default_node.u.constant), "NULL") == 0));
zend_bool is_explicitly_nullable = (type_ast->attr & ZEND_TYPE_NULLABLE) == ZEND_TYPE_NULLABLE;

op_array->fn_flags |= ZEND_ACC_HAS_TYPE_HINTS;
arg_info->allow_null = has_null_default || is_explicitly_nullable;
allow_null = has_null_default || is_explicitly_nullable;

type_ast->attr &= ~ZEND_TYPE_NULLABLE;
zend_compile_typename(type_ast, arg_info);
zend_compile_typename(type_ast, arg_info, allow_null);

if (arg_info->type_hint == IS_VOID) {
if (ZEND_TYPE_CODE(arg_info->type) == IS_VOID) {
zend_error_noreturn(E_COMPILE_ERROR, "void cannot be used as a parameter type");
}

if (type_ast->kind == ZEND_AST_TYPE) {
if (arg_info->type_hint == IS_ARRAY) {
if (ZEND_TYPE_CODE(arg_info->type) == IS_ARRAY) {
if (default_ast && !has_null_default
&& Z_TYPE(default_node.u.constant) != IS_ARRAY
&& !Z_CONSTANT(default_node.u.constant)
) {
zend_error_noreturn(E_COMPILE_ERROR, "Default value for parameters "
"with array type can only be an array or NULL");
}
} else if (arg_info->type_hint == IS_CALLABLE && default_ast) {
} else if (ZEND_TYPE_CODE(arg_info->type) == IS_CALLABLE && default_ast) {
if (!has_null_default && !Z_CONSTANT(default_node.u.constant)) {
zend_error_noreturn(E_COMPILE_ERROR, "Default value for parameters "
"with callable type can only be NULL");
}
}
} else {
if (default_ast && !has_null_default && !Z_CONSTANT(default_node.u.constant)) {
if (arg_info->class_name) {
if (ZEND_TYPE_IS_CLASS(arg_info->type)) {
zend_error_noreturn(E_COMPILE_ERROR, "Default value for parameters "
"with a class type can only be NULL");
} else switch (arg_info->type_hint) {
} else switch (ZEND_TYPE_CODE(arg_info->type)) {
case IS_DOUBLE:
if (Z_TYPE(default_node.u.constant) != IS_DOUBLE && Z_TYPE(default_node.u.constant) != IS_LONG) {
zend_error_noreturn(E_COMPILE_ERROR, "Default value for parameters "
@@ -5228,10 +5227,10 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */
break;

default:
if (!ZEND_SAME_FAKE_TYPE(arg_info->type_hint, Z_TYPE(default_node.u.constant))) {
if (!ZEND_SAME_FAKE_TYPE(ZEND_TYPE_CODE(arg_info->type), Z_TYPE(default_node.u.constant))) {
zend_error_noreturn(E_COMPILE_ERROR, "Default value for parameters "
"with a %s type can only be %s or NULL",
zend_get_type_by_const(arg_info->type_hint), zend_get_type_by_const(arg_info->type_hint));
zend_get_type_by_const(ZEND_TYPE_CODE(arg_info->type)), zend_get_type_by_const(ZEND_TYPE_CODE(arg_info->type)));
}
break;
}
@@ -5240,13 +5239,13 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */

/* Allocate cache slot to speed-up run-time class resolution */
if (opline->opcode == ZEND_RECV_INIT) {
if (arg_info->class_name) {
if (ZEND_TYPE_IS_CLASS(arg_info->type)) {
zend_alloc_cache_slot(opline->op2.constant);
} else {
Z_CACHE_SLOT(op_array->literals[opline->op2.constant]) = -1;
}
} else {
if (arg_info->class_name) {
if (ZEND_TYPE_IS_CLASS(arg_info->type)) {
opline->op2.num = op_array->cache_size;
op_array->cache_size += sizeof(void*);
} else {
@@ -322,20 +322,16 @@ typedef struct _zend_class_constant {
/* arg_info for internal functions */
typedef struct _zend_internal_arg_info {
const char *name;
const char *class_name;
zend_uchar type_hint;
zend_type type;
zend_uchar pass_by_reference;
zend_bool allow_null;
zend_bool is_variadic;
} zend_internal_arg_info;

/* arg_info for user functions */
typedef struct _zend_arg_info {
zend_string *name;
zend_string *class_name;
zend_uchar type_hint;
zend_type type;
zend_uchar pass_by_reference;
zend_bool allow_null;
zend_bool is_variadic;
} zend_arg_info;

@@ -346,10 +342,8 @@ typedef struct _zend_arg_info {
*/
typedef struct _zend_internal_function_info {
zend_uintptr_t required_num_args;
const char *class_name;
zend_uchar type_hint;
zend_type type;
zend_bool return_reference;
zend_bool allow_null;
zend_bool _is_variadic;
} zend_internal_function_info;

Oops, something went wrong.

0 comments on commit 141d1ba

Please sign in to comment.
You can’t perform that action at this time.