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

Support generic methods #3

Open
thekid opened this issue Nov 12, 2022 · 4 comments
Open

Support generic methods #3

thekid opened this issue Nov 12, 2022 · 4 comments
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@thekid
Copy link
Contributor

thekid commented Nov 12, 2022

Like the reverse() method in the following example:

// A generic definition, shortened for brevity
class List<E> {
  public function __construct(E... $elements) { }
  public function get(int $i): E $element { }
  public function size(): int { }
}

class Test {

  // Generic method declaration
  private static function reverse<E>(List<E> $list): iterable {
    for ($i= $list->size() - 1; $i >= 0; $i--) {
      yield $list->get($i);
    }
  }

  public static function main(array<string> $args): void {
    $list= new List<string>($args);

    // Call the generic method, potentially without type (as it could be inferred)
    foreach (self::reverse<string>($list) as $element) {
      Console::writeLine($element);
    }
  }
}
@thekid thekid added enhancement New feature or request help wanted Extra attention is needed labels Nov 12, 2022
@thekid
Copy link
Contributor Author

thekid commented Nov 12, 2022

The call introduces an ambiguity in the parser, which would need to be solved by looking ahead for a closing >:

$reverse= self::reverse<string>($list)
//                            ^
// Up until here... ----------´
// ...it could also be a class constant, which is compared with "less than" to a constant, e.g:

$reverse= self::reverse < string;
$mismatch= self::REQUIRED < PHP_VERSION;

@thekid
Copy link
Contributor Author

thekid commented Nov 12, 2022

The call to reverse() could be rewritten as follows:

// self::reverse($list), infer type
invoke(self::class, 'reverse', [], $list);

// self::reverse<string>($list), supply type
invoke(self::class, 'reverse', ['string'], $list);

The reverse() method is emitted as follows:

#[Generic(self: 'E', params: 'List<E>')]
public function reverse($list) {
  for ($i= $list->size() - 1; $i >= 0; $i--) {
    yield $list->get($i);
  }
}

The invoke() helper would:

  1. Determine whether argument types contain placeholders. For List<E>, as E in self, this is true
  2. Otherwise, directly verify it
  3. If the placeholder is not yet mapped to a concrete type (either by it being specified at call site or by this step), infer it from the current argument. This would map E => string for the above example.
  4. Otherwise, verify the type

@thekid
Copy link
Contributor Author

thekid commented Aug 7, 2023

@thekid
Copy link
Contributor Author

thekid commented Oct 1, 2023

Test

diff --git a/src/test/php/lang/ast/syntax/php/unittest/MethodsTest.class.php b/src/test/php/lang/ast/syntax/php/unittest/MethodsTest.class.php
index af6d975..8866b99 100755
--- a/src/test/php/lang/ast/syntax/php/unittest/MethodsTest.class.php
+++ b/src/test/php/lang/ast/syntax/php/unittest/MethodsTest.class.php
@@ -139,4 +139,20 @@ class MethodsTest extends EmittingTest {
      }
    }');
  }
+
+  #[Test]
+  public function generic_method() {
+    $r= $this->run('class %T {
+      public static function sort<E>(array<E> $elements) {
+        sort($elements);
+        return $elements;
+      }
+
+      public function run($elements) {
+        return self::sort<string>($elements);
+      }
+    }', ['C', 'A', 'B']);
+
+    Assert::equals(['A', 'B', 'C'], $r);
+  }
}
\ No newline at end of file

Syntactic support

Emitting idea

// Input
public static function sort<E>(array<E> $elements) {
  sort($elements);
  return $elements;
}

$sorted= T::sort([1, 2, 3]);
$sorted= T::sort<int>([1, 2, 3]);

// Output
public static function __sort($types, ... $args) {
  foreach ($types as $i => $type) {
    $type->isInstance($args[$i]) || throw new IllegalArgumentException('...');
  }
  list($E)= $types;
  list($elements)= $args;

  sort($elements);
  return $elements;
}

public static function sort($elements) {
  return self::__sort([typeof($elements)], $elements);
}

$sorted= T::sort([1, 2, 3]);
$sorted= T::__sort([Type::forName('int')], [1, 2, 3]);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

1 participant