Skip to content

Commit fbfd02a

Browse files
committed
Fix phpGH-14874: report correct line for property/const variance errors
Captures the AST lineno for typed properties, class constants, and enum cases into a per-request side hashtable on the compiler globals (CG(typed_member_lines)), keyed by member pointer. The inheritance variance emitters look up the line via zend_get_typed_member_line and route through zend_error_at_noreturn so the error points at the offending child member rather than the class declaration. Internal classes and trait-copied properties are not entered in the table and fall back to zend_error_noreturn (legacy behavior, no regression). Mirrors the line tracking zend_function::line_start already provides for the parallel incompatible-method error path, without growing zend_property_info or zend_class_constant. Fixes phpGH-14874
1 parent fe52e5b commit fbfd02a

8 files changed

Lines changed: 114 additions & 25 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
GH-14874 (Incorrect lineno for property and class const variance check)
3+
--FILE--
4+
<?php
5+
6+
class C extends P {
7+
public const int X = 42;
8+
}
9+
10+
class P {
11+
public const string X = 'X';
12+
}
13+
14+
?>
15+
--EXPECTF--
16+
Fatal error: Type of C::X must be compatible with P::X of type string in %s on line 4

Zend/tests/type_declarations/typed_properties_006.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ class Bar extends Foo {
1111
}
1212
?>
1313
--EXPECTF--
14-
Fatal error: Type of Bar::$qux must be int (as in class Foo) in %s on line 6
14+
Fatal error: Type of Bar::$qux must be int (as in class Foo) in %s on line 7

Zend/tests/type_declarations/typed_properties_007.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ class Bar extends Foo {
1414
}
1515
?>
1616
--EXPECTF--
17-
Fatal error: Type of Bar::$qux must be Whatever (as in class Foo) in %s on line 9
17+
Fatal error: Type of Bar::$qux must be Whatever (as in class Foo) in %s on line 10

Zend/tests/type_declarations/typed_properties_008.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ class Bar extends Foo {
1111
}
1212
?>
1313
--EXPECTF--
14-
Fatal error: Type of Bar::$qux must be int (as in class Foo) in %s on line 6
14+
Fatal error: Type of Bar::$qux must be int (as in class Foo) in %s on line 7

Zend/zend_compile.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ void init_compiler(void) /* {{{ */
463463
CG(delayed_variance_obligations) = NULL;
464464
CG(delayed_autoloads) = NULL;
465465
CG(unlinked_uses) = NULL;
466+
CG(typed_member_lines) = NULL;
466467
CG(current_linking_class) = NULL;
467468
}
468469
/* }}} */
@@ -491,10 +492,40 @@ void shutdown_compiler(void) /* {{{ */
491492
FREE_HASHTABLE(CG(unlinked_uses));
492493
CG(unlinked_uses) = NULL;
493494
}
495+
if (CG(typed_member_lines)) {
496+
zend_hash_destroy(CG(typed_member_lines));
497+
FREE_HASHTABLE(CG(typed_member_lines));
498+
CG(typed_member_lines) = NULL;
499+
}
494500
CG(current_linking_class) = NULL;
495501
}
496502
/* }}} */
497503

504+
ZEND_API void zend_set_typed_member_line(const void *member, uint32_t lineno) /* {{{ */
505+
{
506+
if (lineno == 0) {
507+
return;
508+
}
509+
if (!CG(typed_member_lines)) {
510+
ALLOC_HASHTABLE(CG(typed_member_lines));
511+
zend_hash_init(CG(typed_member_lines), 0, NULL, NULL, 0);
512+
}
513+
zval zv;
514+
ZVAL_LONG(&zv, lineno);
515+
zend_hash_index_update(CG(typed_member_lines), (zend_ulong)(uintptr_t)member, &zv);
516+
}
517+
/* }}} */
518+
519+
ZEND_API uint32_t zend_get_typed_member_line(const void *member) /* {{{ */
520+
{
521+
if (!CG(typed_member_lines)) {
522+
return 0;
523+
}
524+
zval *zv = zend_hash_index_find(CG(typed_member_lines), (zend_ulong)(uintptr_t)member);
525+
return zv ? (uint32_t)Z_LVAL_P(zv) : 0;
526+
}
527+
/* }}} */
528+
498529
ZEND_API zend_string *zend_set_compiled_filename(zend_string *new_compiled_filename) /* {{{ */
499530
{
500531
CG(compiled_filename) = zend_string_copy(new_compiled_filename);
@@ -8264,6 +8295,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32
82648295
scope, name, &default_value,
82658296
property_flags | (zend_property_is_virtual(scope, name, hooks_ast) ? ZEND_ACC_VIRTUAL : 0) | ZEND_ACC_PROMOTED,
82668297
doc_comment, type);
8298+
zend_set_typed_member_line(prop, param_ast->lineno);
82678299
if (hooks_ast) {
82688300
const zend_ast_list *hooks = zend_ast_get_list(hooks_ast);
82698301
zend_compile_property_hooks(prop, name, type_ast, hooks);
@@ -9253,6 +9285,7 @@ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t f
92539285
}
92549286

92559287
info = zend_declare_typed_property(ce, name, &value_zv, flags, doc_comment, type);
9288+
zend_set_typed_member_line(info, prop_ast->lineno);
92569289

92579290
if (hooks_ast) {
92589291
zend_compile_property_hooks(info, name, type_ast, zend_ast_get_list(hooks_ast));
@@ -9339,6 +9372,7 @@ static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_as
93399372
}
93409373

93419374
c = zend_declare_typed_class_constant(ce, name, &value_zv, flags, doc_comment, type);
9375+
zend_set_typed_member_line(c, const_ast->lineno);
93429376

93439377
if (attr_ast) {
93449378
zend_compile_attributes(&c->attributes, attr_ast, 0, ZEND_ATTRIBUTE_TARGET_CLASS_CONST, 0);
@@ -9800,6 +9834,7 @@ static void zend_compile_enum_case(zend_ast *ast)
98009834

98019835
zend_class_constant *c = zend_declare_class_constant_ex(enum_class, enum_case_name, &value_zv, ZEND_ACC_PUBLIC, doc_comment);
98029836
ZEND_CLASS_CONST_FLAGS(c) |= ZEND_CLASS_CONST_IS_CASE;
9837+
zend_set_typed_member_line(c, ast->lineno);
98039838
zend_ast_destroy(const_enum_init_ast);
98049839

98059840
zend_ast *attr_ast = ast->child[3];

Zend/zend_compile.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,9 @@ ZEND_API zend_string *zend_get_compiled_filename(void);
904904
ZEND_API uint32_t zend_get_compiled_lineno(void);
905905
ZEND_API size_t zend_get_scanned_file_offset(void);
906906

907+
ZEND_API void zend_set_typed_member_line(const void *member, uint32_t lineno);
908+
ZEND_API uint32_t zend_get_typed_member_line(const void *member);
909+
907910
ZEND_API zend_string *zend_get_compiled_variable_name(const zend_op_array *op_array, uint32_t var);
908911

909912
#ifdef ZTS

Zend/zend_globals.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ struct _zend_compiler_globals {
156156
HashTable *delayed_variance_obligations;
157157
HashTable *delayed_autoloads;
158158
HashTable *unlinked_uses;
159+
HashTable *typed_member_lines;
159160
zend_class_entry *current_linking_class;
160161

161162
uint32_t rtd_key_counter;

Zend/zend_inheritance.c

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,27 +1308,50 @@ static inheritance_status full_property_types_compatible(
13081308
static ZEND_COLD void emit_incompatible_property_error(
13091309
const zend_property_info *child, const zend_property_info *parent, prop_variance variance) {
13101310
zend_string *type_str = zend_type_to_string_resolved(parent->type, parent->ce);
1311-
zend_error_noreturn(E_COMPILE_ERROR,
1312-
"Type of %s::$%s must be %s%s (as in class %s)",
1313-
ZSTR_VAL(child->ce->name),
1314-
zend_get_unmangled_property_name(child->name),
1315-
variance == PROP_INVARIANT ? "" :
1316-
variance == PROP_COVARIANT ? "subtype of " : "supertype of ",
1317-
ZSTR_VAL(type_str),
1318-
ZSTR_VAL(parent->ce->name));
1311+
uint32_t line = zend_get_typed_member_line(child);
1312+
if (line && child->ce->type == ZEND_USER_CLASS) {
1313+
zend_error_at_noreturn(E_COMPILE_ERROR, child->ce->info.user.filename, line,
1314+
"Type of %s::$%s must be %s%s (as in class %s)",
1315+
ZSTR_VAL(child->ce->name),
1316+
zend_get_unmangled_property_name(child->name),
1317+
variance == PROP_INVARIANT ? "" :
1318+
variance == PROP_COVARIANT ? "subtype of " : "supertype of ",
1319+
ZSTR_VAL(type_str),
1320+
ZSTR_VAL(parent->ce->name));
1321+
} else {
1322+
zend_error_noreturn(E_COMPILE_ERROR,
1323+
"Type of %s::$%s must be %s%s (as in class %s)",
1324+
ZSTR_VAL(child->ce->name),
1325+
zend_get_unmangled_property_name(child->name),
1326+
variance == PROP_INVARIANT ? "" :
1327+
variance == PROP_COVARIANT ? "subtype of " : "supertype of ",
1328+
ZSTR_VAL(type_str),
1329+
ZSTR_VAL(parent->ce->name));
1330+
}
13191331
}
13201332

13211333
static ZEND_COLD void emit_set_hook_type_error(const zend_property_info *child, const zend_property_info *parent)
13221334
{
13231335
zend_type set_type = parent->hooks[ZEND_PROPERTY_HOOK_SET]->common.arg_info[0].type;
13241336
zend_string *type_str = zend_type_to_string_resolved(set_type, parent->ce);
1325-
zend_error_noreturn(E_COMPILE_ERROR,
1326-
"Set type of %s::$%s must be supertype of %s (as in %s %s)",
1327-
ZSTR_VAL(child->ce->name),
1328-
zend_get_unmangled_property_name(child->name),
1329-
ZSTR_VAL(type_str),
1330-
zend_get_object_type_case(parent->ce, false),
1331-
ZSTR_VAL(parent->ce->name));
1337+
uint32_t line = zend_get_typed_member_line(child);
1338+
if (line && child->ce->type == ZEND_USER_CLASS) {
1339+
zend_error_at_noreturn(E_COMPILE_ERROR, child->ce->info.user.filename, line,
1340+
"Set type of %s::$%s must be supertype of %s (as in %s %s)",
1341+
ZSTR_VAL(child->ce->name),
1342+
zend_get_unmangled_property_name(child->name),
1343+
ZSTR_VAL(type_str),
1344+
zend_get_object_type_case(parent->ce, false),
1345+
ZSTR_VAL(parent->ce->name));
1346+
} else {
1347+
zend_error_noreturn(E_COMPILE_ERROR,
1348+
"Set type of %s::$%s must be supertype of %s (as in %s %s)",
1349+
ZSTR_VAL(child->ce->name),
1350+
zend_get_unmangled_property_name(child->name),
1351+
ZSTR_VAL(type_str),
1352+
zend_get_object_type_case(parent->ce, false),
1353+
ZSTR_VAL(parent->ce->name));
1354+
}
13321355
}
13331356

13341357
static inheritance_status verify_property_type_compatibility(
@@ -1621,13 +1644,24 @@ static void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_en
16211644
static void emit_incompatible_class_constant_error(
16221645
const zend_class_constant *child, const zend_class_constant *parent, const zend_string *const_name) {
16231646
zend_string *type_str = zend_type_to_string_resolved(parent->type, parent->ce);
1624-
zend_error_noreturn(E_COMPILE_ERROR,
1625-
"Type of %s::%s must be compatible with %s::%s of type %s",
1626-
ZSTR_VAL(child->ce->name),
1627-
ZSTR_VAL(const_name),
1628-
ZSTR_VAL(parent->ce->name),
1629-
ZSTR_VAL(const_name),
1630-
ZSTR_VAL(type_str));
1647+
uint32_t line = zend_get_typed_member_line(child);
1648+
if (line && child->ce->type == ZEND_USER_CLASS) {
1649+
zend_error_at_noreturn(E_COMPILE_ERROR, child->ce->info.user.filename, line,
1650+
"Type of %s::%s must be compatible with %s::%s of type %s",
1651+
ZSTR_VAL(child->ce->name),
1652+
ZSTR_VAL(const_name),
1653+
ZSTR_VAL(parent->ce->name),
1654+
ZSTR_VAL(const_name),
1655+
ZSTR_VAL(type_str));
1656+
} else {
1657+
zend_error_noreturn(E_COMPILE_ERROR,
1658+
"Type of %s::%s must be compatible with %s::%s of type %s",
1659+
ZSTR_VAL(child->ce->name),
1660+
ZSTR_VAL(const_name),
1661+
ZSTR_VAL(parent->ce->name),
1662+
ZSTR_VAL(const_name),
1663+
ZSTR_VAL(type_str));
1664+
}
16311665
}
16321666

16331667
static inheritance_status class_constant_types_compatible(const zend_class_constant *parent, const zend_class_constant *child)

0 commit comments

Comments
 (0)