Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PoC] Associated types #18260

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
32 changes: 32 additions & 0 deletions Zend/tests/type_declarations/associated/associated_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--TEST--
Associated types basic
--FILE--
<?php

interface I {
type T;
public function foo(T $param): T;
}

class CS implements I {
public function foo(string $param): string {
return $param . '!';
}
}

class CI implements I {
public function foo(int $param): int {
return $param + 42;
}
}

$cs = new CS();
var_dump($cs->foo("Hello"));

$ci = new CI();
var_dump($ci->foo(5));

?>
--EXPECT--
string(6) "Hello!"
int(47)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
Associated type cannot be in intersection (simple intersection with class type)
--FILE--
<?php

interface I {
type T;
public function foo(T&Traversable $param): T&Traversable;
}

?>
--EXPECTF--
Fatal error: Associated type cannot be part of an intersection type in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
Associated type cannot be in intersection (DNF type)
--FILE--
<?php

interface I {
type T;
public function foo(stdClass|(T&Traversable) $param): stdClass|(T&Traversable);
}

?>
--EXPECTF--
Fatal error: Associated type cannot be part of an intersection type in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
Associated type cannot be in union (simple union with built-in type)
--FILE--
<?php

interface I {
type T;
public function foo(T|int $param): T|int;
}

?>
--EXPECTF--
Fatal error: Associated type cannot be part of a union type in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
Associated type cannot be in union (simple union with class type)
--FILE--
<?php

interface I {
type T;
public function foo(T|stdClass $param): T|stdClass;
}

?>
--EXPECTF--
Fatal error: Associated type cannot be part of a union type in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
Associated type cannot be in union (DNF type)
--FILE--
<?php

interface I {
type T;
public function foo(T|(Traversable&Countable) $param): T|(Traversable&Countable);
}

?>
--EXPECTF--
Fatal error: Associated type cannot be part of a union type in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
Associated types in class is invalid
--FILE--
<?php

class C {
type T;
public function foo(T $param): T;
}

?>
--EXPECTF--
Fatal error: Cannot use associated types outside of interfaces, used in C in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
Associated types in trait is invalid
--FILE--
<?php

trait C {
type T;
public function foo(T $param): T;
}

?>
--EXPECTF--
Fatal error: Cannot use associated types outside of interfaces, used in C in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--TEST--
Associated type with a constraint
--FILE--
<?php

interface I {
type T : int|string;
public function foo(T $param): T;
}

class CS implements I {
public function foo(string $param): string {
return $param . '!';
}
}

class CI implements I {
public function foo(int $param): int {
return $param + 42;
}
}

$cs = new CS();
var_dump($cs->foo("Hello"));

$ci = new CI();
var_dump($ci->foo(5));

?>
--EXPECT--
string(6) "Hello!"
int(47)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Associated type with a constraint that is not satisfied
--FILE--
<?php

interface I {
type T : int|string;
public function foo(T $param): T;
}

class C implements I {
public function foo(float $param): float {}
}

?>
--EXPECTF--
Fatal error: Declaration of C::foo(float $param): float must be compatible with I::foo(T<string|int> $param): T<string|int> in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--TEST--
Associated type behaviour in extended interface
--FILE--
<?php

interface I {
type T : int|string;
public function foo(T $param): T;
}

interface I2 extends I {
type T;
public function bar(int $o, T $param): T;
}

class C implements I2 {
public function foo(string $param): string {}
public function bar(int $o, float $param): float {}
}

?>
--EXPECTF--
Fatal error: Cannot redeclare associated type T in interface I2 inherited from interface I in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
Associated type behaviour in extended interface
--FILE--
<?php

interface I {
type T : int|string|(Traversable&Countable);
public function foo(T $param): T;
}

interface I2 extends I {
public function bar(int $o, T $param): T;
}

class C implements I2 {
public function foo(string $param): string {}
public function bar(int $o, float $param): float {}
}

?>
--EXPECTF--
Fatal error: Declaration of C::bar(int $o, float $param): float must be compatible with I2::bar(int $o, T $param): T in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
Associated type behaviour in extended interface
--FILE--
<?php

interface I {
type T : int|string|(Traversable&Countable);
public function foo(T $param): T;
}

interface I2 extends I {
type T2 : float|bool|stdClass;
public function bar(T2 $o, T $param): T2;
}

class C implements I2 {
public function foo(string $param): string {}
public function bar(float $o, string $param): float {}
}

// TODO: Ideally error message would be:
//Fatal error: Declaration of C::bar(float $o, string $param): float must be compatible with I2::bar(T2<stdClass|float|bool> $o, T<(Traversable&Countable)|int|string> $param): T2<stdClass|float|bool> in %s on line %d
//Improve zend_append_type_hint()?
?>
--EXPECTF--
Fatal error: Declaration of C::bar(float $o, string $param): float must be compatible with I2::bar(T2<stdClass|float|bool> $o, T $param): T2<stdClass|float|bool> in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
--TEST--
Multiple associated types
--FILE--
<?php

interface I {
type K;
type V;
public function set(K $key, V $value): void;
public function get(K $key): V;
}

class C1 implements I {
public array $a = [];
public function set(int $key, string $value): void {
$this->a[$key] = $value . '!';
}
public function get(int $key): string {
return $this->a[$key];
}
}

class C2 implements I {
public array $a = [];
public function set(string $key, object $value): void {
$this->a[$key] = $value;
}
public function get(string $key): object {
return $this->a[$key];
}
}

$c1 = new C1();
$c1->set(5, "Hello");
var_dump($c1->a);
var_dump($c1->get(5));

$c2 = new C2();
$c2->set('C1', $c1);
var_dump($c2->a);
var_dump($c2->get('C1'));

try {
$c1->set('blah', "Hello");
} catch (\Throwable $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}


?>
--EXPECTF--
array(1) {
[5]=>
string(6) "Hello!"
}
string(6) "Hello!"
array(1) {
["C1"]=>
object(C1)#1 (1) {
["a"]=>
array(1) {
[5]=>
string(6) "Hello!"
}
}
}
object(C1)#1 (1) {
["a"]=>
array(1) {
[5]=>
string(6) "Hello!"
}
}
TypeError: C1::set(): Argument #1 ($key) must be of type int, string given, called in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--TEST--
Repeated associated type
--FILE--
<?php

interface I {
type T;
type T;
public function foo(T $param): T;
}

class C implements I {
public function foo(string $param): string {}
}

?>
--EXPECTF--
Fatal error: Cannot have two associated types with the same name "T" in %s on line %d
1 change: 1 addition & 0 deletions Zend/zend.c
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,7 @@ static void compiler_globals_ctor(zend_compiler_globals *compiler_globals) /* {{

compiler_globals->script_encoding_list = NULL;
compiler_globals->current_linking_class = NULL;
compiler_globals->bound_associated_types = NULL;

/* Map region is going to be created and resized at run-time. */
compiler_globals->map_ptr_real_base = NULL;
Expand Down
3 changes: 3 additions & 0 deletions Zend/zend.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@ struct _zend_class_entry {
zend_trait_precedence **trait_precedences;
HashTable *attributes;

/* Only for interfaces */
HashTable *associated_types;

uint32_t enum_backing_type;
HashTable *backed_enum_table;

Expand Down
10 changes: 10 additions & 0 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -2664,6 +2664,16 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
smart_str_appends(str, ": ");
ast = ast->child[1];
goto tail_call;
case ZEND_AST_ASSOCIATED_TYPE:
smart_str_appends(str, "type ");
zend_ast_export_name(str, ast->child[0], 0, indent);
if (ast->child[1]) {
smart_str_appends(str, " : ");
smart_str_appends(str, " : ");
zend_ast_export_type(str, ast->child[1], indent);
}
smart_str_appendc(str, ';');
break;

/* 3 child nodes */
case ZEND_AST_METHOD_CALL:
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ enum _zend_ast_kind {
ZEND_AST_MATCH_ARM,
ZEND_AST_NAMED_ARG,
ZEND_AST_PARENT_PROPERTY_HOOK_CALL,
ZEND_AST_ASSOCIATED_TYPE,

/* 3 child nodes */
ZEND_AST_METHOD_CALL = 3 << ZEND_AST_NUM_CHILDREN_SHIFT,
Expand Down
Loading
Loading