Skip to content

Commit ccb0747

Browse files
committed
Add support for the mixed type
1 parent 51a305d commit ccb0747

19 files changed

+299
-4
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Test that a mixed casting is not supported
3+
--FILE--
4+
<?php
5+
6+
$foo = (mixed) 12;
7+
8+
?>
9+
--EXPECTF--
10+
Parse error: syntax error, unexpected '12' (T_LNUMBER) in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Test that the mixed parameter type is not valid with union types
3+
--FILE--
4+
<?php
5+
6+
function foo(mixed|int|null $a)
7+
{
8+
}
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Duplicate type mixed is redundant in %s on line %d
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--TEST--
2+
Test that mixed is a valid parameter type
3+
--FILE--
4+
<?php
5+
6+
function foo(mixed $a)
7+
{
8+
}
9+
10+
?>
11+
--EXPECT--
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
Test that the mixed return type is not valid with union types
3+
--FILE--
4+
<?php
5+
6+
function foo(): mixed|string|null
7+
{
8+
return null;
9+
}
10+
11+
?>
12+
--EXPECTF--
13+
Fatal error: Duplicate type mixed is redundant in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Test that mixed is a valid return type
3+
--FILE--
4+
<?php
5+
6+
function foo(): mixed
7+
{
8+
return null;
9+
}
10+
11+
?>
12+
--EXPECT--
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
Test that the mixed parameter type accepts any kind of arguments in strict mode
3+
--FILE--
4+
<?php
5+
declare(strict_types=1);
6+
7+
function foo(mixed $a)
8+
{
9+
}
10+
11+
foo(null);
12+
foo(false);
13+
foo(1);
14+
foo(3.14);
15+
foo("");
16+
foo([]);
17+
foo(new stdClass());
18+
19+
?>
20+
--EXPECT--
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
Test that the mixed parameter type accepts any kind of arguments in weak mode
3+
--FILE--
4+
<?php
5+
6+
function foo(mixed $a)
7+
{
8+
}
9+
10+
foo(null);
11+
foo(false);
12+
foo(1);
13+
foo(3.14);
14+
foo("");
15+
foo([]);
16+
foo(new stdClass());
17+
18+
?>
19+
--EXPECT--
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--TEST--
2+
Test that the mixed property type accepts any kind of value in strict mode
3+
--FILE--
4+
<?php
5+
declare(strict_types=1);
6+
7+
class Foo
8+
{
9+
public mixed $property1;
10+
public mixed $property2 = null;
11+
public mixed $property3 = false;
12+
public mixed $property4 = true;
13+
public mixed $property5 = 1;
14+
public mixed $property6 = 3.14;
15+
public mixed $property7 = "foo";
16+
public mixed $property8 = [];
17+
public mixed $property9;
18+
19+
public function __construct()
20+
{
21+
$this->property9 = fopen(__FILE__, "r");
22+
$this->property9 = new stdClass();
23+
}
24+
}
25+
26+
$foo = new Foo();
27+
28+
try {
29+
$foo->property1;
30+
} catch (Error $exception) {
31+
echo $exception->getMessage() . "\n";
32+
}
33+
34+
?>
35+
--EXPECT--
36+
Typed property Foo::$property1 must not be accessed before initialization
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
Test that the mixed property type accepts any kind of value
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public mixed $property1;
9+
public mixed $property2 = null;
10+
public mixed $property3 = false;
11+
public mixed $property4 = true;
12+
public mixed $property5 = 1;
13+
public mixed $property6 = 3.14;
14+
public mixed $property7 = "foo";
15+
public mixed $property8 = [];
16+
public mixed $property9;
17+
18+
public function __construct()
19+
{
20+
$this->property9 = fopen(__FILE__, "r");
21+
$this->property9 = new stdClass();
22+
}
23+
}
24+
25+
$foo = new Foo();
26+
27+
try {
28+
$foo->property1;
29+
} catch (Error $exception) {
30+
echo $exception->getMessage() . "\n";
31+
}
32+
33+
?>
34+
--EXPECT--
35+
Typed property Foo::$property1 must not be accessed before initialization
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
Test that the mixed property type accepts any kind of value in weak mode
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public mixed $property1;
9+
public mixed $property2 = null;
10+
public mixed $property3 = false;
11+
public mixed $property4 = true;
12+
public mixed $property5 = 1;
13+
public mixed $property6 = 3.14;
14+
public mixed $property7 = "foo";
15+
public mixed $property8 = [];
16+
public mixed $property9;
17+
18+
public function __construct()
19+
{
20+
$this->property9 = fopen(__FILE__, "r");
21+
$this->property9 = new stdClass();
22+
}
23+
}
24+
25+
$foo = new Foo();
26+
27+
try {
28+
$foo->property1;
29+
} catch (Error $exception) {
30+
echo $exception->getMessage() . "\n";
31+
}
32+
33+
?>
34+
--EXPECT--
35+
Typed property Foo::$property1 must not be accessed before initialization
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
Test that the mixed return type is not compatible with a void return value in strict mode
3+
--FILE--
4+
<?php
5+
declare(strict_types=1);
6+
7+
function foo(): mixed
8+
{
9+
}
10+
11+
try {
12+
foo();
13+
} catch (TypeError $exception) {
14+
echo $exception->getMessage() . "\n";
15+
}
16+
17+
?>
18+
--EXPECTF--
19+
Return value of foo() must be of type mixed, none returned
20+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
Test that the mixed return type is compatible with any kind of return value in strict mode
3+
--FILE--
4+
<?php
5+
declare(strict_types=1);
6+
7+
function foo($a): mixed
8+
{
9+
return $a;
10+
}
11+
12+
foo(null);
13+
foo(false);
14+
foo(1);
15+
foo("");
16+
foo([]);
17+
foo(new stdClass());
18+
19+
?>
20+
--EXPECT--
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
Test that the mixed return type is not compatible with a void return value
3+
--FILE--
4+
<?php
5+
6+
function foo(): mixed
7+
{
8+
}
9+
10+
try {
11+
foo();
12+
} catch (TypeError $exception) {
13+
echo $exception->getMessage() . "\n";
14+
}
15+
16+
?>
17+
--EXPECT--
18+
Return value of foo() must be of type mixed, none returned
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
Test that the mixed return type is compatible with any kind of return value in weak mode
3+
--FILE--
4+
<?php
5+
6+
function foo($a): mixed
7+
{
8+
return $a;
9+
}
10+
11+
foo(null);
12+
foo(false);
13+
foo(1);
14+
foo("");
15+
foo([]);
16+
foo(new stdClass());
17+
18+
?>
19+
--EXPECT--

Zend/zend_API.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ ZEND_API const char *zend_get_type_by_const(int type) /* {{{ */
123123
return "array";
124124
case IS_VOID:
125125
return "void";
126+
case IS_MIXED:
127+
return "mixed";
126128
case _IS_NUMBER:
127129
return "number";
128130
default:

Zend/zend_ast.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1572,6 +1572,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
15721572
case IS_ARRAY: APPEND_STR("array");
15731573
case IS_CALLABLE: APPEND_STR("callable");
15741574
case IS_STATIC: APPEND_STR("static");
1575+
case IS_MIXED: APPEND_STR("mixed");
15751576
EMPTY_SWITCH_DEFAULT_CASE();
15761577
}
15771578
break;

Zend/zend_compile.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ static const struct reserved_class_name reserved_class_names[] = {
172172
{ZEND_STRL("void")},
173173
{ZEND_STRL("iterable")},
174174
{ZEND_STRL("object")},
175+
{ZEND_STRL("mixed")},
175176
{NULL, 0}
176177
};
177178

@@ -220,6 +221,7 @@ static const builtin_type_info builtin_types[] = {
220221
{ZEND_STRL("void"), IS_VOID},
221222
{ZEND_STRL("iterable"), IS_ITERABLE},
222223
{ZEND_STRL("object"), IS_OBJECT},
224+
{ZEND_STRL("mixed"), IS_MIXED},
223225
{NULL, 0, IS_UNDEF}
224226
};
225227

@@ -1190,6 +1192,15 @@ static zend_string *resolve_class_name(zend_string *name, zend_class_entry *scop
11901192

11911193
zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scope) {
11921194
zend_string *str = NULL;
1195+
1196+
uint32_t type_mask = ZEND_TYPE_FULL_MASK(type);
1197+
1198+
if (type_mask & MAY_BE_ANY) {
1199+
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_MIXED));
1200+
1201+
return str;
1202+
}
1203+
11931204
if (ZEND_TYPE_HAS_LIST(type)) {
11941205
zend_type *list_type;
11951206
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type) {
@@ -1205,7 +1216,6 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop
12051216
str = zend_string_copy(ZEND_TYPE_CE(type)->name);
12061217
}
12071218

1208-
uint32_t type_mask = ZEND_TYPE_FULL_MASK(type);
12091219
if (type_mask & MAY_BE_STATIC) {
12101220
zend_string *name = ZSTR_KNOWN(ZEND_STR_STATIC);
12111221
if (scope) {

Zend/zend_string.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,7 @@ EMPTY_SWITCH_DEFAULT_CASE()
514514
_(ZEND_STR_VOID, "void") \
515515
_(ZEND_STR_FALSE, "false") \
516516
_(ZEND_STR_NULL_LOWERCASE, "null") \
517+
_(ZEND_STR_MIXED, "mixed") \
517518

518519

519520
typedef enum _zend_known_string_id {

Zend/zend_types.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ typedef struct {
257257
{ NULL, (_type_mask) }
258258

259259
#define ZEND_TYPE_INIT_CODE(code, allow_null, extra_flags) \
260-
ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? MAY_BE_BOOL : (1 << (code))) \
260+
ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? MAY_BE_BOOL : ((code) == IS_MIXED ? MAY_BE_ANY : (1 << (code)))) \
261261
| ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags))
262262

263263
#define ZEND_TYPE_INIT_PTR(ptr, type_kind, allow_null, extra_flags) \
@@ -534,6 +534,7 @@ struct _zend_ast_ref {
534534
#define IS_ITERABLE 13
535535
#define IS_VOID 14
536536
#define IS_STATIC 15
537+
#define IS_MIXED 16
537538

538539
/* internal types */
539540
#define IS_INDIRECT 12
@@ -542,8 +543,8 @@ struct _zend_ast_ref {
542543
#define _IS_ERROR 15
543544

544545
/* used for casts */
545-
#define _IS_BOOL 16
546-
#define _IS_NUMBER 17
546+
#define _IS_BOOL 17
547+
#define _IS_NUMBER 18
547548

548549
static zend_always_inline zend_uchar zval_get_type(const zval* pz) {
549550
return pz->u1.v.type;

0 commit comments

Comments
 (0)