Skip to content

Commit 8b3a81e

Browse files
committed
feat: add support for literal scalar types
Signed-off-by: azjezz <azjezz@protonmail.com>
1 parent 92128ac commit 8b3a81e

36 files changed

Lines changed: 1071 additions & 20 deletions

Zend/Optimizer/zend_inference.c

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2382,15 +2382,29 @@ static uint32_t zend_convert_type(const zend_script *script, zend_type type, zen
23822382

23832383
uint32_t tmp = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(type));
23842384
if (ZEND_TYPE_IS_COMPLEX(type)) {
2385-
tmp |= MAY_BE_OBJECT;
2386-
if (pce) {
2387-
/* As we only have space to store one CE,
2388-
* we use a plain object type for class unions. */
2389-
if (ZEND_TYPE_HAS_NAME(type)) {
2390-
zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(type));
2391-
// TODO: Pass through op_array.
2392-
*pce = zend_optimizer_get_class_entry(script, NULL, lcname);
2393-
zend_string_release_ex(lcname, 0);
2385+
/* A complex type is a class/object type, unless it is made up solely of
2386+
* literal types, which contribute their base scalar type instead. */
2387+
bool has_class = false;
2388+
const zend_type *single_type;
2389+
ZEND_TYPE_FOREACH(type, single_type) {
2390+
if (ZEND_TYPE_HAS_LITERAL(*single_type)) {
2391+
/* int/float/string literal -> MAY_BE_LONG/DOUBLE/STRING */
2392+
tmp |= 1u << Z_TYPE_P(ZEND_TYPE_LITERAL_VALUE(*single_type));
2393+
} else {
2394+
has_class = true;
2395+
}
2396+
} ZEND_TYPE_FOREACH_END();
2397+
if (has_class) {
2398+
tmp |= MAY_BE_OBJECT;
2399+
if (pce) {
2400+
/* As we only have space to store one CE,
2401+
* we use a plain object type for class unions. */
2402+
if (ZEND_TYPE_HAS_NAME(type)) {
2403+
zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(type));
2404+
// TODO: Pass through op_array.
2405+
*pce = zend_optimizer_get_class_entry(script, NULL, lcname);
2406+
zend_string_release_ex(lcname, 0);
2407+
}
23942408
}
23952409
}
23962410
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
--TEST--
2+
Literal types: basic accept and reject (int, float, string)
3+
--FILE--
4+
<?php
5+
function i(1|2|3 $x): int { return $x; }
6+
function f(1.5|2.5 $x): float { return $x; }
7+
function s('a'|'b' $x): string { return $x; }
8+
9+
var_dump(i(1), i(2), i(3));
10+
var_dump(f(1.5), f(2.5));
11+
var_dump(s('a'), s('b'));
12+
13+
try {
14+
i(4);
15+
} catch (TypeError $e) {
16+
echo $e->getMessage(), "\n";
17+
}
18+
try {
19+
f(3.5);
20+
} catch (TypeError $e) {
21+
echo $e->getMessage(), "\n";
22+
}
23+
try {
24+
s('c');
25+
} catch (TypeError $e) {
26+
echo $e->getMessage(), "\n";
27+
}
28+
?>
29+
--EXPECTF--
30+
int(1)
31+
int(2)
32+
int(3)
33+
float(1.5)
34+
float(2.5)
35+
string(1) "a"
36+
string(1) "b"
37+
i(): Argument #1 ($x) must be of type 1|2|3, int given, called in %s on line %d
38+
f(): Argument #1 ($x) must be of type 1.5|2.5, float given, called in %s on line %d
39+
s(): Argument #1 ($x) must be of type 'a'|'b', string given, called 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+
Literal types: a default value not equal to any literal is rejected
3+
--FILE--
4+
<?php
5+
function f(1|2 $x = 3): void {}
6+
?>
7+
--EXPECTF--
8+
Fatal error: Cannot use int as default value for parameter $x of type 1|2 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+
Literal types: interpolated double-quoted string is not a valid literal type
3+
--FILE--
4+
<?php
5+
function f("foo $bar" $x): void {}
6+
?>
7+
--EXPECTF--
8+
Parse error: syntax error, %s in %s on line %d
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
--TEST--
2+
Literal types: literals may not appear in intersection types
3+
--FILE--
4+
<?php
5+
interface Foo {}
6+
function f(1&Foo $x): void {}
7+
?>
8+
--EXPECTF--
9+
Parse error: syntax error, %s in %s on line %d
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
Literal types: negative int and float literals
3+
--FILE--
4+
<?php
5+
function i(-1|-2 $x): int { return $x; }
6+
function f(-1.5|2.5 $x): float { return $x; }
7+
8+
var_dump(i(-1), i(-2));
9+
var_dump(f(-1.5), f(2.5));
10+
11+
try {
12+
i(1);
13+
} catch (TypeError $e) {
14+
echo $e->getMessage(), "\n";
15+
}
16+
?>
17+
--EXPECTF--
18+
int(-1)
19+
int(-2)
20+
float(-1.5)
21+
float(2.5)
22+
i(): Argument #1 ($x) must be of type -1|-2, int given, called in %s on line %d
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
--TEST--
2+
Literal types: typed properties with literal types
3+
--FILE--
4+
<?php
5+
class C {
6+
public 1|2|3 $p = 1;
7+
public 'on'|'off' $state = 'off';
8+
}
9+
10+
$c = new C();
11+
var_dump($c->p, $c->state);
12+
13+
$c->p = 3;
14+
$c->state = 'on';
15+
var_dump($c->p, $c->state);
16+
17+
$c->p = "2";
18+
var_dump($c->p);
19+
20+
try {
21+
$c->p = 9;
22+
} catch (TypeError $e) {
23+
echo $e->getMessage(), "\n";
24+
}
25+
try {
26+
$c->state = 'maybe';
27+
} catch (TypeError $e) {
28+
echo $e->getMessage(), "\n";
29+
}
30+
?>
31+
--EXPECTF--
32+
int(1)
33+
string(3) "off"
34+
int(3)
35+
string(2) "on"
36+
int(2)
37+
Cannot assign int to property C::$p of type 1|2|3
38+
Cannot assign string to property C::$state of type 'on'|'off'
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
--TEST--
2+
Literal types: duplicate literal value is redundant
3+
--FILE--
4+
<?php
5+
function f(1|1 $x): void {}
6+
?>
7+
--EXPECTF--
8+
Fatal error: Literal type 1 is redundant as it is already present in the union 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+
Literal types: literal is redundant when the base scalar type is also present (literal first)
3+
--FILE--
4+
<?php
5+
function f(1|int $x): void {}
6+
?>
7+
--EXPECTF--
8+
Fatal error: Literal type 1 is redundant as the union already allows its base type 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+
Literal types: literal is redundant when the base scalar type is also present (scalar first)
3+
--FILE--
4+
<?php
5+
function f(string|'foo' $x): void {}
6+
?>
7+
--EXPECTF--
8+
Fatal error: Literal type 'foo' is redundant as the union already allows its base type in %s on line %d

0 commit comments

Comments
 (0)