Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -2382,15 +2382,29 @@ static uint32_t zend_convert_type(const zend_script *script, zend_type type, zen

uint32_t tmp = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(type));
if (ZEND_TYPE_IS_COMPLEX(type)) {
tmp |= MAY_BE_OBJECT;
if (pce) {
/* As we only have space to store one CE,
* we use a plain object type for class unions. */
if (ZEND_TYPE_HAS_NAME(type)) {
zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(type));
// TODO: Pass through op_array.
*pce = zend_optimizer_get_class_entry(script, NULL, lcname);
zend_string_release_ex(lcname, 0);
/* A complex type is a class/object type, unless it is made up solely of
* literal types, which contribute their base scalar type instead. */
bool has_class = false;
const zend_type *single_type;
ZEND_TYPE_FOREACH(type, single_type) {
if (ZEND_TYPE_HAS_LITERAL(*single_type)) {
/* int/float/string literal -> MAY_BE_LONG/DOUBLE/STRING */
tmp |= 1u << Z_TYPE_P(ZEND_TYPE_LITERAL_VALUE(*single_type));
} else {
has_class = true;
}
} ZEND_TYPE_FOREACH_END();
if (has_class) {
tmp |= MAY_BE_OBJECT;
if (pce) {
/* As we only have space to store one CE,
* we use a plain object type for class unions. */
if (ZEND_TYPE_HAS_NAME(type)) {
zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(type));
// TODO: Pass through op_array.
*pce = zend_optimizer_get_class_entry(script, NULL, lcname);
zend_string_release_ex(lcname, 0);
}
}
}
}
Expand Down
39 changes: 39 additions & 0 deletions Zend/tests/type_declarations/literal_types/basic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
--TEST--
Literal types: basic accept and reject (int, float, string)
--FILE--
<?php
function i(1|2|3 $x): int { return $x; }
function f(1.5|2.5 $x): float { return $x; }
function s('a'|'b' $x): string { return $x; }

var_dump(i(1), i(2), i(3));
var_dump(f(1.5), f(2.5));
var_dump(s('a'), s('b'));

try {
i(4);
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
try {
f(3.5);
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
try {
s('c');
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECTF--
int(1)
int(2)
int(3)
float(1.5)
float(2.5)
string(1) "a"
string(1) "b"
i(): Argument #1 ($x) must be of type 1|2|3, int given, called in %s on line %d
f(): Argument #1 ($x) must be of type 1.5|2.5, float given, called in %s on line %d
s(): Argument #1 ($x) must be of type 'a'|'b', string given, called in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
--TEST--
Literal types: a default value not equal to any literal is rejected
--FILE--
<?php
function f(1|2 $x = 3): void {}
?>
--EXPECTF--
Fatal error: Cannot use int as default value for parameter $x of type 1|2 in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
--TEST--
Literal types: interpolated double-quoted string is not a valid literal type
--FILE--
<?php
function f("foo $bar" $x): void {}
?>
--EXPECTF--
Parse error: syntax error, %s in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
--TEST--
Literal types: literals may not appear in intersection types
--FILE--
<?php
interface Foo {}
function f(1&Foo $x): void {}
?>
--EXPECTF--
Parse error: syntax error, %s in %s on line %d
22 changes: 22 additions & 0 deletions Zend/tests/type_declarations/literal_types/negative.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
Literal types: negative int and float literals
--FILE--
<?php
function i(-1|-2 $x): int { return $x; }
function f(-1.5|2.5 $x): float { return $x; }

var_dump(i(-1), i(-2));
var_dump(f(-1.5), f(2.5));

try {
i(1);
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECTF--
int(-1)
int(-2)
float(-1.5)
float(2.5)
i(): Argument #1 ($x) must be of type -1|-2, int given, called in %s on line %d
38 changes: 38 additions & 0 deletions Zend/tests/type_declarations/literal_types/property.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--TEST--
Literal types: typed properties with literal types
--FILE--
<?php
class C {
public 1|2|3 $p = 1;
public 'on'|'off' $state = 'off';
}

$c = new C();
var_dump($c->p, $c->state);

$c->p = 3;
$c->state = 'on';
var_dump($c->p, $c->state);

$c->p = "2";
var_dump($c->p);

try {
$c->p = 9;
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
try {
$c->state = 'maybe';
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECTF--
int(1)
string(3) "off"
int(3)
string(2) "on"
int(2)
Cannot assign int to property C::$p of type 1|2|3
Cannot assign string to property C::$state of type 'on'|'off'
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
--TEST--
Literal types: duplicate literal value is redundant
--FILE--
<?php
function f(1|1 $x): void {}
?>
--EXPECTF--
Fatal error: Literal type 1 is redundant as it is already present in the union in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
--TEST--
Literal types: literal is redundant when the base scalar type is also present (literal first)
--FILE--
<?php
function f(1|int $x): void {}
?>
--EXPECTF--
Fatal error: Literal type 1 is redundant as the union already allows its base type in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
--TEST--
Literal types: literal is redundant when the base scalar type is also present (scalar first)
--FILE--
<?php
function f(string|'foo' $x): void {}
?>
--EXPECTF--
Fatal error: Literal type 'foo' is redundant as the union already allows its base type in %s on line %d
31 changes: 31 additions & 0 deletions Zend/tests/type_declarations/literal_types/reflection.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
Literal types: reflection (union members, single literal, nullable)
--FILE--
<?php
function f(1|2|'foo' $a, 42 $b, 1|null $c): void {}

$params = (new ReflectionFunction('f'))->getParameters();

$union = $params[0]->getType();
printf("a: %s | %s\n", get_class($union), (string) $union);
foreach ($union->getTypes() as $m) {
printf(" %s value=%s\n", get_class($m), var_export($m->getValue(), true));
}

$single = $params[1]->getType();
printf("b: %s | %s | value=%s | nullable=%s\n",
get_class($single), (string) $single, var_export($single->getValue(), true),
var_export($single->allowsNull(), true));

$nullable = $params[2]->getType();
printf("c: %s | %s | value=%s | nullable=%s\n",
get_class($nullable), (string) $nullable, var_export($nullable->getValue(), true),
var_export($nullable->allowsNull(), true));
?>
--EXPECT--
a: ReflectionUnionType | 1|2|'foo'
ReflectionLiteralScalarType value=1
ReflectionLiteralScalarType value=2
ReflectionLiteralScalarType value='foo'
b: ReflectionLiteralScalarType | 42 | value=42 | nullable=false
c: ReflectionLiteralScalarType | ?1 | value=1 | nullable=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
--TEST--
Literal types: ReflectionLiteralScalarType::getValue()
--FILE--
<?php
class Foo {}

function f(1|2|'foo'|3.14 $union, 42 $int, -1 $neg, "x" $str, 1|null $nullable,
int $scalar, Foo $class): void {}

$params = (new ReflectionFunction('f'))->getParameters();

function dump(ReflectionType $t): void {
if ($t instanceof ReflectionLiteralScalarType) {
$v = $t->getValue();
printf("%-7s %s value=%s (%s)\n", (string) $t, get_class($t), var_export($v, true), gettype($v));
} else {
printf("%-7s %s\n", (string) $t, get_class($t));
}
}

foreach ($params[0]->getType()->getTypes() as $m) {
dump($m);
}
dump($params[1]->getType());
dump($params[2]->getType());
dump($params[3]->getType());
dump($params[4]->getType());
dump($params[5]->getType());
dump($params[6]->getType());
?>
--EXPECT--
1 ReflectionLiteralScalarType value=1 (integer)
2 ReflectionLiteralScalarType value=2 (integer)
'foo' ReflectionLiteralScalarType value='foo' (string)
3.14 ReflectionLiteralScalarType value=3.14 (double)
42 ReflectionLiteralScalarType value=42 (integer)
-1 ReflectionLiteralScalarType value=-1 (integer)
'x' ReflectionLiteralScalarType value='x' (string)
?1 ReflectionLiteralScalarType value=1 (integer)
int ReflectionNamedType
Foo ReflectionNamedType
36 changes: 36 additions & 0 deletions Zend/tests/type_declarations/literal_types/return_and_default.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
--TEST--
Literal types: return types, default values, single and nullable literal types
--FILE--
<?php
function r(int $x): 1|2|3 { return $x; }
var_dump(r(2));
try {
r(5);
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}

function d(1|2 $x = 2): int { return $x; }
function df(1.0|2.5 $x = 1): float { return $x; }
var_dump(d(), df());

function one(42 $x): 42 { return $x; }
var_dump(one(42));
try {
one(43);
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}

function n(1|2|null $x): null|int { return $x; }
var_dump(n(1), n(null));
?>
--EXPECTF--
int(2)
r(): Return value must be of type 1|2|3, int returned
int(2)
float(1)
int(42)
one(): Argument #1 ($x) must be of type 42, int given, called in %s on line %d
int(1)
NULL
32 changes: 32 additions & 0 deletions Zend/tests/type_declarations/literal_types/strict_types.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--TEST--
Literal types: strict_types disallows coercion (except int -> float literal)
--FILE--
<?php
declare(strict_types=1);

function i(1|2|3 $x): int { return $x; }
function f(1.5|2.0 $x): float { return $x; }
function s('a'|'b' $x): string { return $x; }

var_dump(i(2));
var_dump(f(1.5));
var_dump(f(2));
var_dump(s('a'));

foreach ([['i', "2"], ['i', 2.0], ['f', 3], ['s', 1]] as [$fn, $arg]) {
try {
$fn($arg);
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
}
?>
--EXPECTF--
int(2)
float(1.5)
float(2)
string(1) "a"
i(): Argument #1 ($x) must be of type 1|2|3, string given, called in %s on line %d
i(): Argument #1 ($x) must be of type 1|2|3, float given, called in %s on line %d
f(): Argument #1 ($x) must be of type 1.5|2.0, int given, called in %s on line %d
s(): Argument #1 ($x) must be of type 'a'|'b', int given, called in %s on line %d
26 changes: 26 additions & 0 deletions Zend/tests/type_declarations/literal_types/string_quotes.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
Literal types: single- and double-quoted string literals are equivalent
--FILE--
<?php
function a('foo' $x): string { return $x; }
function b("foo" $x): string { return $x; }

var_dump(a('foo'), a("foo"));
var_dump(b('foo'), b("foo"));

echo (new ReflectionFunction('a'))->getParameters()[0]->getType(), "\n";
echo (new ReflectionFunction('b'))->getParameters()[0]->getType(), "\n";

function c('a\'b' $x): string { return $x; }
var_dump(c("a'b"));
echo (new ReflectionFunction('c'))->getParameters()[0]->getType(), "\n";
?>
--EXPECT--
string(3) "foo"
string(3) "foo"
string(3) "foo"
string(3) "foo"
'foo'
'foo'
string(3) "a'b"
'a\'b'
Loading
Loading