Skip to content

Commit 4832ef6

Browse files
committed
Add bound resolved type to string for error messages
1 parent 1679e6d commit 4832ef6

File tree

5 files changed

+65
-15
lines changed

5 files changed

+65
-15
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
Multiple abstract generic type incorrect bound types in implementation
3+
--FILE--
4+
<?php
5+
6+
interface I<K, V> {
7+
public function set(K $key, V $value): void;
8+
public function get(K $key): V;
9+
}
10+
11+
class C implements I<string, int> {
12+
public array $a = [];
13+
public function set(int $key, string $value): void {
14+
$this->a[$key] = $value . '!';
15+
}
16+
public function get(int $key): string {
17+
return $this->a[$key];
18+
}
19+
}
20+
21+
?>
22+
--EXPECTF--
23+
Fatal error: Declaration of C::set(int $key, string $value): void must be compatible with I::set(<K : string> $key, <V : int> $value): void in %s on line %d

Zend/zend_compile.c

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1434,10 +1434,31 @@ static zend_string *add_intersection_type(zend_string *str,
14341434
return str;
14351435
}
14361436

1437-
zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry *scope) {
1437+
static zend_string* resolve_bound_generic_type(zend_string *type_name, zend_class_entry *scope, const HashTable *bound_types) {
1438+
const zend_type *constraint = zend_hash_find_ptr(bound_types, type_name);
1439+
ZEND_ASSERT(constraint != NULL);
1440+
1441+
zend_string *constraint_type_str = zend_type_to_string_resolved(*constraint, scope, /* need the bound types? */NULL);
1442+
1443+
size_t len = ZSTR_LEN(type_name) + ZSTR_LEN(constraint_type_str) + strlen("< : >");
1444+
zend_string *result = zend_string_alloc(len, 0);
1445+
1446+
ZSTR_VAL(result)[0] = '<';
1447+
memcpy(ZSTR_VAL(result) + strlen("<"), ZSTR_VAL(type_name), ZSTR_LEN(type_name));
1448+
ZSTR_VAL(result)[ZSTR_LEN(type_name) + 1] = ' ';
1449+
ZSTR_VAL(result)[ZSTR_LEN(type_name) + 2] = ':';
1450+
ZSTR_VAL(result)[ZSTR_LEN(type_name) + 3] = ' ';
1451+
memcpy(ZSTR_VAL(result) + ZSTR_LEN(type_name) + strlen("< : "), ZSTR_VAL(constraint_type_str), ZSTR_LEN(constraint_type_str));
1452+
ZSTR_VAL(result)[len-1] = '>';
1453+
ZSTR_VAL(result)[len] = '\0';
1454+
1455+
zend_string_release(constraint_type_str);
1456+
return result;
1457+
}
1458+
1459+
zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry *scope, const HashTable *bound_types_to_scope) {
14381460
zend_string *str = NULL;
14391461

1440-
ZEND_ASSERT(!ZEND_TYPE_IS_GENERIC_PARAM_NAME(type) && "Generic type declarations do not exist yet");
14411462
/* Pure intersection type */
14421463
if (ZEND_TYPE_IS_INTERSECTION(type)) {
14431464
ZEND_ASSERT(!ZEND_TYPE_IS_UNION(type));
@@ -1458,6 +1479,8 @@ zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry
14581479
str = add_type_string(str, resolved, /* is_intersection */ false);
14591480
zend_string_release(resolved);
14601481
} ZEND_TYPE_LIST_FOREACH_END();
1482+
} else if (ZEND_TYPE_IS_GENERIC_PARAM_NAME(type)) {
1483+
str = resolve_bound_generic_type(ZEND_TYPE_NAME(type), scope, bound_types_to_scope);
14611484
} else if (ZEND_TYPE_HAS_NAME(type)) {
14621485
str = resolve_class_name(ZEND_TYPE_NAME(type), scope);
14631486
}
@@ -1527,7 +1550,7 @@ zend_string *zend_type_to_string_resolved(const zend_type type, zend_class_entry
15271550
}
15281551

15291552
ZEND_API zend_string *zend_type_to_string(zend_type type) {
1530-
return zend_type_to_string_resolved(type, NULL);
1553+
return zend_type_to_string_resolved(type, NULL, NULL);
15311554
}
15321555

15331556
static bool is_generator_compatible_class_type(const zend_string *name) {

Zend/zend_compile.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1014,7 +1014,7 @@ int ZEND_FASTCALL zendlex(zend_parser_stack_elem *elem);
10141014

10151015
void zend_assert_valid_class_name(const zend_string *const_name, const char *type);
10161016

1017-
zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scope);
1017+
zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scope, const HashTable *bound_types_to_scope);
10181018
ZEND_API zend_string *zend_type_to_string(zend_type type);
10191019

10201020
/* BEGIN: OPCODES */

Zend/zend_execute.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,7 @@ static ZEND_COLD void zend_verify_type_error_common(
686686
*fclass = "";
687687
}
688688

689-
*need_msg = zend_type_to_string_resolved(arg_info->type, zf->common.scope);
689+
*need_msg = zend_type_to_string_resolved(arg_info->type, zf->common.scope, /* TODO? */ NULL);
690690

691691
if (value) {
692692
*given_kind = zend_zval_value_name(value);

Zend/zend_inheritance.c

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -946,10 +946,10 @@ static inheritance_status zend_do_perform_implementation_check(
946946
/* }}} */
947947

948948
static ZEND_COLD void zend_append_type_hint(
949-
smart_str *str, zend_class_entry *scope, const zend_arg_info *arg_info, bool return_hint) /* {{{ */
949+
smart_str *str, zend_class_entry *scope, const HashTable *bound_types_to_scope, const zend_arg_info *arg_info, bool return_hint) /* {{{ */
950950
{
951951
if (ZEND_TYPE_IS_SET(arg_info->type)) {
952-
zend_string *type_str = zend_type_to_string_resolved(arg_info->type, scope);
952+
zend_string *type_str = zend_type_to_string_resolved(arg_info->type, scope, bound_types_to_scope);
953953
smart_str_append(str, type_str);
954954
zend_string_release(type_str);
955955
if (!return_hint) {
@@ -960,7 +960,7 @@ static ZEND_COLD void zend_append_type_hint(
960960
/* }}} */
961961

962962
static ZEND_COLD zend_string *zend_get_function_declaration(
963-
const zend_function *fptr, zend_class_entry *scope) /* {{{ */
963+
const zend_function *fptr, zend_class_entry *scope, const HashTable *bound_types_to_scope) /* {{{ */
964964
{
965965
smart_str str = {0};
966966

@@ -991,7 +991,7 @@ static ZEND_COLD zend_string *zend_get_function_declaration(
991991
num_args++;
992992
}
993993
for (uint32_t i = 0; i < num_args;) {
994-
zend_append_type_hint(&str, scope, arg_info, 0);
994+
zend_append_type_hint(&str, scope, bound_types_to_scope, arg_info, 0);
995995

996996
if (ZEND_ARG_SEND_MODE(arg_info)) {
997997
smart_str_appendc(&str, '&');
@@ -1088,7 +1088,7 @@ static ZEND_COLD zend_string *zend_get_function_declaration(
10881088

10891089
if (fptr->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
10901090
smart_str_appends(&str, ": ");
1091-
zend_append_type_hint(&str, scope, fptr->common.arg_info - 1, 1);
1091+
zend_append_type_hint(&str, scope, bound_types_to_scope, fptr->common.arg_info - 1, 1);
10921092
}
10931093
smart_str_0(&str);
10941094

@@ -1108,8 +1108,12 @@ static void ZEND_COLD emit_incompatible_method_error(
11081108
const zend_function *child, zend_class_entry *child_scope,
11091109
const zend_function *parent, zend_class_entry *parent_scope,
11101110
inheritance_status status) {
1111-
zend_string *parent_prototype = zend_get_function_declaration(parent, parent_scope);
1112-
zend_string *child_prototype = zend_get_function_declaration(child, child_scope);
1111+
const HashTable *bound_types_to_parent = NULL;
1112+
if (child_scope->bound_types) {
1113+
bound_types_to_parent = zend_hash_find_ptr_lc(child_scope->bound_types, parent_scope->name);
1114+
}
1115+
zend_string *parent_prototype = zend_get_function_declaration(parent, parent_scope, bound_types_to_parent);
1116+
zend_string *child_prototype = zend_get_function_declaration(child, child_scope, NULL);
11131117
if (status == INHERITANCE_UNRESOLVED) {
11141118
// TODO Improve error message if first unresolved class is present in child and parent?
11151119
/* Fetch the first unresolved class from registered autoloads */
@@ -1362,7 +1366,7 @@ static inheritance_status full_property_types_compatible(
13621366

13631367
static ZEND_COLD void emit_incompatible_property_error(
13641368
const zend_property_info *child, const zend_property_info *parent, prop_variance variance) {
1365-
zend_string *type_str = zend_type_to_string_resolved(parent->type, parent->ce);
1369+
zend_string *type_str = zend_type_to_string_resolved(parent->type, parent->ce, /* TODO? */ NULL);
13661370
zend_error_noreturn(E_COMPILE_ERROR,
13671371
"Type of %s::$%s must be %s%s (as in class %s)",
13681372
ZSTR_VAL(child->ce->name),
@@ -1376,7 +1380,7 @@ static ZEND_COLD void emit_incompatible_property_error(
13761380
static ZEND_COLD void emit_set_hook_type_error(const zend_property_info *child, const zend_property_info *parent)
13771381
{
13781382
zend_type set_type = parent->hooks[ZEND_PROPERTY_HOOK_SET]->common.arg_info[0].type;
1379-
zend_string *type_str = zend_type_to_string_resolved(set_type, parent->ce);
1383+
zend_string *type_str = zend_type_to_string_resolved(set_type, parent->ce, /* TODO? */ NULL);
13801384
zend_error_noreturn(E_COMPILE_ERROR,
13811385
"Set type of %s::$%s must be supertype of %s (as in %s %s)",
13821386
ZSTR_VAL(child->ce->name),
@@ -1677,7 +1681,7 @@ static void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_en
16771681

16781682
static void emit_incompatible_class_constant_error(
16791683
const zend_class_constant *child, const zend_class_constant *parent, const zend_string *const_name) {
1680-
zend_string *type_str = zend_type_to_string_resolved(parent->type, parent->ce);
1684+
zend_string *type_str = zend_type_to_string_resolved(parent->type, parent->ce, NULL);
16811685
zend_error_noreturn(E_COMPILE_ERROR,
16821686
"Type of %s::%s must be compatible with %s::%s of type %s",
16831687
ZSTR_VAL(child->ce->name),

0 commit comments

Comments
 (0)