Skip to content

Commit 987a7eb

Browse files
committed
feat(zend): reject required type parameter after an optional one
Signed-off-by: azjezz <azjezz@protonmail.com>
1 parent e833b03 commit 987a7eb

6 files changed

Lines changed: 56 additions & 1 deletion

Zend/tests/generics/errors/forward_reference_in_default.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Errors: forward reference in default of earlier parameter
33
--FILE--
44
<?php
5-
class C<U = T, T> {}
5+
class C<U = T, T = int> {}
66
?>
77
--EXPECTF--
88
Fatal error: Type parameter T referenced before declaration in %s on line %d
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Errors: optional type parameters at the tail are accepted
3+
--FILE--
4+
<?php
5+
class A<T> {}
6+
class B<T = int> {}
7+
class C<T, U = int> {}
8+
class D<T, U = int, V = string> {}
9+
class E<T = int, U = string> {}
10+
function f<T, U = int>(): void {}
11+
function g<T = int, U = string>(): void {}
12+
echo "ok\n";
13+
?>
14+
--EXPECT--
15+
ok
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
--TEST--
2+
Errors: required type parameter cannot follow an optional one
3+
--FILE--
4+
<?php
5+
class C<T = int, U> {}
6+
?>
7+
--EXPECTF--
8+
Fatal error: Optional type parameter T cannot be declared before required type parameter U in %s on line %d
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
--TEST--
2+
Errors: required type parameter cannot follow an optional one
3+
--FILE--
4+
<?php
5+
function f<T = int, U>(): void {}
6+
?>
7+
--EXPECTF--
8+
Fatal error: Optional type parameter T cannot be declared before required type parameter U in %s on line %d
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
--TEST--
2+
Errors: required type parameter following two optional ones names the first optional
3+
--FILE--
4+
<?php
5+
class C<T = int, U = string, V> {}
6+
?>
7+
--EXPECTF--
8+
Fatal error: Optional type parameter T cannot be declared before required type parameter V in %s on line %d

Zend/zend_compile.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,10 +512,13 @@ static zend_generic_parameter_list *zend_compile_generic_type_parameter_list(zen
512512
zend_generic_parameter_list *params =
513513
zend_generic_parameter_list_alloc(list->children, /* persistent */ false);
514514

515+
zend_string *prev_optional_name = NULL;
516+
515517
for (uint32_t i = 0; i < list->children; i++) {
516518
zend_ast *param_ast = list->child[i];
517519
ZEND_ASSERT(param_ast->kind == ZEND_AST_GENERIC_TYPE_PARAMETER);
518520
zend_string *name = zval_make_interned_string(zend_ast_get_zval(param_ast->child[0]));
521+
bool has_default = param_ast->child[2] != NULL;
519522

520523
for (uint32_t j = 0; j < i; j++) {
521524
if (zend_string_equals(params->parameters[j].name, name)) {
@@ -533,6 +536,19 @@ static zend_generic_parameter_list *zend_compile_generic_type_parameter_list(zen
533536
"Type parameter %s shadows enclosing type parameter", ZSTR_VAL(dup));
534537
}
535538

539+
if (!has_default && prev_optional_name != NULL) {
540+
zend_string *cur = zend_string_copy(name);
541+
zend_string *prev = zend_string_copy(prev_optional_name);
542+
zend_generic_parameter_list_destroy(params);
543+
zend_error_noreturn(E_COMPILE_ERROR,
544+
"Optional type parameter %s cannot be declared before required type parameter %s",
545+
ZSTR_VAL(prev), ZSTR_VAL(cur));
546+
}
547+
548+
if (has_default && prev_optional_name == NULL) {
549+
prev_optional_name = name;
550+
}
551+
536552
params->parameters[i].name = zend_string_copy(name);
537553
params->parameters[i].variance = (uint8_t) param_ast->attr;
538554
}

0 commit comments

Comments
 (0)