Skip to content

[PoC] Limited Abstract Generics #18260

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

Draft
wants to merge 41 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
e6fdff1
Zend: Use pointer to zend_type for variance checks
Girgias Apr 5, 2025
a192a3a
Add T_TYPE token
Girgias Apr 4, 2025
9304a82
Support minimal compilation of associated type
Girgias Apr 5, 2025
066f04f
Associate type to CE and minimal duplicate check
Girgias Apr 5, 2025
b7cf20b
Prevent associated type from being in a union or intersection type
Girgias Apr 5, 2025
0ec2ea6
Support invariant bound types for associated types
Girgias Apr 5, 2025
e215b8b
Add true global for mixed type
Girgias Apr 6, 2025
e6a1a7e
Store zend_type ptr in associated types HT
Girgias Apr 6, 2025
d252759
Add parser support for constraint type
Girgias Apr 7, 2025
1691402
Add variance checking for constrained associated type
Girgias Apr 7, 2025
139bbf6
Add basic support for extending interfaces with associated type
Girgias Apr 7, 2025
2b9f0f5
Fix namespaces interfering with AT
Girgias Apr 28, 2025
b1a3546
Improve AT constraint indication when dumping type to string
Girgias Apr 28, 2025
363f817
Fix various unhandled cases where AT is part of a union type
Girgias Apr 28, 2025
2198aaf
[skip ci] Get explicit notation working
Girgias May 6, 2025
be7efd5
Remove most of associated types
Girgias May 8, 2025
87f9544
rename
Girgias May 8, 2025
56f2f1c
Did I finally fix generic type binding?
Girgias May 10, 2025
2d83b54
fix stuff
Girgias May 12, 2025
51b689e
move stuff
Girgias May 12, 2025
cad9308
name of variable
Girgias May 12, 2025
5e076d0
Rewrite generic type binding for inherited interfaces
Girgias May 12, 2025
beabf2b
Do some fixes about implicit and explicit interface implementations
Girgias May 12, 2025
36a4736
Fix namespace resolution for interfaces
Girgias May 12, 2025
7fbdc0a
Use safe_pemalloc for generic params alloc
Girgias May 13, 2025
6cdc4f5
Fix AST and name resolution
Girgias May 13, 2025
7b38d21
Extra fixes
Girgias May 14, 2025
20e21ab
Rename last associated macro stuff to generic
Girgias May 14, 2025
92a45c7
Add bound resolved type to string for error messages
Girgias May 15, 2025
d1bae38
Fix typo in variable name
Girgias May 19, 2025
518d7ea
Update wording for error message
Girgias May 19, 2025
9d17cae
Add failing test
Girgias May 19, 2025
00372bd
Add some const qualifiers
Girgias May 19, 2025
02695e0
Change to static storage
Girgias May 19, 2025
6fd8860
rename interfaces
Girgias May 20, 2025
ad1ae05
Fix redeclaration of method, and other issues
Girgias May 21, 2025
fba8f76
Add an index field to zend_type
Girgias May 22, 2025
106c3d2
Stop relying on the generic type name to find bound type
Girgias May 22, 2025
439458c
Refactor bind_generic_types_for_inherited_interfaces
Girgias May 22, 2025
229fcc9
Zend: Inherit interfaces early
Girgias May 22, 2025
891f24d
Fix some implicit bound type issues
Girgias May 22, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
Abstract generic types basic
--FILE--
<?php

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

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

class CI implements I<int> {
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)
100 changes: 100 additions & 0 deletions Zend/tests/type_declarations/abstract_generics/big_example.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
--TEST--
Concrete example of using AT
--CREDITS--
Levi Morrison
--FILE--
<?php declare(strict_types=1);

namespace Sequence;

// No null. This is probably going to be painful, but let's try it.
interface Sequence<Item : object|array|string|float|int|bool>
{
function next(): ?Item;

/**
* @param callable(Item, Item): Item $f
* @return ?Item
*/
function reduce(callable $f): ?Item;
}

final class StringTablePair
{
public function __construct(
public readonly string $string,
public readonly int $id,
) {}
}

final class StringTableSequence implements Sequence<StringTablePair>
{
private array $strings;

public function __construct(StringTable $string_table) {
$this->strings = $string_table->to_assoc_array();
}

function next(): ?StringTablePair
{
$key = \array_key_first($this->strings);
if (!isset($key)) {
return null;
}
$value = \array_shift($this->strings);
return new StringTablePair($key, $value);
}

/**
* @param callable(Item, Item): Item $f
* @return ?Item
*/
function reduce(callable $f): ?StringTablePair
{
$reduction = $this->next();
if (!isset($reduction)) {
return null;
}

while (($next = $this->next()) !== null) {
$reduction = $f($reduction, $next);
}
return $reduction;
}
}

final class StringTable
{
private array $strings = ["" => 0];

public function __construct() {}

public function offsetGet(string $offset): int
{
return $this->strings[$offset] ?? throw new \Exception();
}

public function offsetExists(string $offset): bool
{
return \isset($this->strings[$offset]);
}

public function intern(string $str): int
{
return $this->strings[$str]
?? ($this->strings[$str] = \count($this->strings));
}

public function to_sequence(): StringTableSequence
{
return new StringTableSequence($this);
}

public function to_assoc_array(): array {
return $this->strings;
}
}

?>
--EXPECTF--
Fatal error: Generic type cannot be part of a union type in %s on line %d
Comment on lines +99 to +100
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I saw my example in here, I got excited that you added basic union support. Nope! 😆

One day 🙏🏻

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I said to Bob I really want to keep it as small as possible as it's already hurting my brain a bit! But this should be a rather easy limitation to lift :)

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Implicit interface inheritance with different bound types
--FILE--
<?php

interface I1<T> {
public function foo(T $param): T;
}

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

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

?>
--EXPECTF--
Fatal error: Bound type T for interface I1 implemented explicitly in C with type string must match the implicitly bound type float from interface I2 in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Implicit interface inheritance with different bound types 2
--FILE--
<?php

interface I1<T> {
public function foo(T $param): T;
}

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

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

?>
--EXPECTF--
Fatal error: Bound type T for interface I1 implemented explicitly in C with type string must match the implicitly bound type float from interface I2 in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Implicit interface inheritance with different bound types 3
--FILE--
<?php

interface I1<T> {
public function foo(T $param): T;
}

interface I2 extends I1<float> {
public function bar(int $o, float $param): float;
}

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

?>
--EXPECTF--
Fatal error: Bound type T for interface I1 implemented explicitly in C with type string must match the implicitly bound type float from interface I2 in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Implicit interface inheritance with different bound types 4
--FILE--
<?php

interface I1<T> {
public function foo(T $param): T;
}

interface I2 extends I1<float> {
public function bar(int $o, float $param): float;
}

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

?>
--EXPECTF--
Fatal error: Bound type T for interface I1 implemented explicitly in C with type string must match the implicitly bound type float from interface I2 in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Implicit interface inheritance missing bound types
--FILE--
<?php

interface I1<T> {
public function foo(T $param): T;
}

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

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

?>
--EXPECTF--
Fatal error: Interface I1 expects 1 generic parameters, 0 given in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Implicit interface inheritance missing bound types 2
--FILE--
<?php

interface I1<T> {
public function foo(T $param): T;
}

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

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

?>
--EXPECTF--
Fatal error: Interface I1 expects 1 generic parameters, 0 given in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Implicit interface inheritance missing bound types 3
--FILE--
<?php

interface I1<T> {
public function foo(T $param): T;
}

interface I2 extends I1<string> {
public function bar(int $o, string $param): string;
}

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

?>
--EXPECTF--
Fatal error: Interface I1 expects 1 generic parameters, 0 given in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
--TEST--
Implicit interface inheritance missing bound types 4
--FILE--
<?php

interface I1<T> {
public function foo(T $param): T;
}

interface I2 extends I1<string> {
public function bar(int $o, string $param): string;
}

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

?>
--EXPECTF--
Fatal error: Interface I1 expects 1 generic parameters, 0 given in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
Implicit interface inheritance with same bound types
--FILE--
<?php

interface I1<T> {
public function foo(T $param): T;
}

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

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

?>
DONE
--EXPECT--
DONE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
Implicit interface inheritance with same bound types 2
--FILE--
<?php

interface I1<T> {
public function foo(T $param): T;
}

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

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

?>
DONE
--EXPECT--
DONE
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
Abstract generic type with a constraint
--FILE--
<?php

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

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

class CI implements I<int> {
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)
Loading
Loading