From 6d7a8dd0820ba10bd38d60725af3bc8c4012d9ea Mon Sep 17 00:00:00 2001
From: Timm Friebe <thekid@thekid.de>
Date: Mon, 28 Nov 2022 21:50:17 +0100
Subject: [PATCH 1/3] Implement interface defaults

---
 .../php/lang/ast/emit/Declaration.class.php   | 16 ++++++-
 src/main/php/lang/ast/emit/PHP.class.php      | 44 +++++++++++++++++--
 .../php/lang/ast/emit/Reflection.class.php    |  9 ++++
 .../emit/TypeDeclarationTest.class.php        | 16 +++++++
 4 files changed, 81 insertions(+), 4 deletions(-)

diff --git a/src/main/php/lang/ast/emit/Declaration.class.php b/src/main/php/lang/ast/emit/Declaration.class.php
index f3d58a3a..b5223b6b 100755
--- a/src/main/php/lang/ast/emit/Declaration.class.php
+++ b/src/main/php/lang/ast/emit/Declaration.class.php
@@ -1,6 +1,6 @@
 <?php namespace lang\ast\emit;
 
-use lang\ast\nodes\{EnumCase, Property};
+use lang\ast\nodes\{EnumCase, Property, Method};
 
 class Declaration extends Type {
   private $type, $result;
@@ -19,6 +19,20 @@ public function __construct($type, $result) {
   /** @return string */
   public function name() { return ltrim($this->type->name, '\\'); }
 
+  /**
+   * Returns whether this is an interface with default implementations
+   *
+   * @return bool
+   */
+  public function defaultImplementations() {
+    if ('interface' === $this->type->kind) {
+      foreach ($this->type->body as $member) {
+        if ($member instanceof Method && null !== $member->body) return true;
+      }
+    }
+    return false;
+  }
+
   /**
    * Returns whether a given member is an enum case
    *
diff --git a/src/main/php/lang/ast/emit/PHP.class.php b/src/main/php/lang/ast/emit/PHP.class.php
index f36183a7..12eda21a 100755
--- a/src/main/php/lang/ast/emit/PHP.class.php
+++ b/src/main/php/lang/ast/emit/PHP.class.php
@@ -12,9 +12,10 @@
   Property,
   ScopeExpression,
   UnpackExpression,
+  TraitDeclaration,
   Variable
 };
-use lang\ast\types\{IsUnion, IsFunction, IsArray, IsMap, IsNullable, IsExpression};
+use lang\ast\types\{IsUnion, IsFunction, IsArray, IsMap, IsNullable, IsValue, IsExpression};
 use lang\ast\{Emitter, Node, Type, Result};
 
 abstract class PHP extends Emitter {
@@ -359,7 +360,7 @@ protected function emitEnumCase($result, $case) {
   protected function emitEnum($result, $enum) {
     array_unshift($result->type, $enum);
     array_unshift($result->meta, []);
-    $result->locals= [[], []];
+    $result->locals ?: $result->locals= [[], [], [], []];
 
     $enum->comment && $this->emitOne($result, $enum->comment);
     $enum->annotations && $this->emitOne($result, $enum->annotations);
@@ -385,23 +386,27 @@ protected function emitEnum($result, $enum) {
     $this->emitInitializations($result, $result->locals[0]);
     $this->emitMeta($result, $enum->name, $enum->annotations, $enum->comment);
     $result->out->write('}} '.$enum->name->literal().'::__init();');
+
     array_shift($result->type);
+    $result->locals= [];
   }
 
   protected function emitClass($result, $class) {
     array_unshift($result->type, $class);
     array_unshift($result->meta, []);
-    $result->locals ?: $result->locals= [[], [], []];
+    $result->locals ?: $result->locals= [[], [], [], []];
 
     $class->comment && $this->emitOne($result, $class->comment);
     $class->annotations && $this->emitOne($result, $class->annotations);
     $result->at($class->declared)->out->write(implode(' ', $class->modifiers).' class '.$class->declaration());
     $class->parent && $result->out->write(' extends '.$class->parent->literal());
 
+    $defaults= [];
     if ($class->implements) {
       $list= '';
       foreach ($class->implements as $type) {
         $list.= ', '.$type->literal();
+        $result->lookup($type->literal())->defaultImplementations() && $defaults[]= $type;
       }
       $result->out->write(' implements '.substr($list, 2));
     }
@@ -447,10 +452,15 @@ protected function emitClass($result, $class) {
       $result->out->write('}');
     }
 
+    foreach ($defaults as $trait) {
+      $result->out->write('use __'.substr($trait->literal(), 1).'_Defaults;');
+    }
+
     $result->out->write('static function __init() {');
     $this->emitInitializations($result, $result->locals[0]);
     $this->emitMeta($result, $class->name, $class->annotations, $class->comment);
     $result->out->write('}} '.$class->name->literal().'::__init();');
+
     array_shift($result->type);
     $result->locals= [];
   }
@@ -509,7 +519,9 @@ protected function emitAnnotations($result, $annotations) {
   }
 
   protected function emitInterface($result, $interface) {
+    array_unshift($result->type, $interface);
     array_unshift($result->meta, []);
+    $result->locals ?: $result->locals= [[], [], [], []];
 
     $interface->comment && $this->emitOne($result, $interface->comment);
     $interface->annotations && $this->emitOne($result, $interface->annotations);
@@ -522,10 +534,24 @@ protected function emitInterface($result, $interface) {
     $result->out->write('}');
 
     $this->emitMeta($result, $interface->name, $interface->annotations, $interface->comment);
+
+    // Emit default implementations
+    if ($result->locals[3]) {
+      $this->emitOne($result, new TraitDeclaration(
+        [],
+        new IsValue('\\__'.$interface->declaration().'_Defaults'),
+        $result->locals[3]
+      ));
+    }
+
+    array_shift($result->type);
+    $result->locals= [];
   }
 
   protected function emitTrait($result, $trait) {
+    array_unshift($result->type, $trait);
     array_unshift($result->meta, []);
+    $result->locals= [[], [], [], []];
 
     $trait->comment && $this->emitOne($result, $trait->comment);
     $trait->annotations && $this->emitOne($result, $trait->annotations);
@@ -537,6 +563,9 @@ protected function emitTrait($result, $trait) {
     $result->out->write('}');
 
     $this->emitMeta($result, $trait->name, $trait->annotations, $trait->comment);
+
+    array_shift($result->type);
+    $result->locals= [];
   }
 
   protected function emitUse($result, $use) {
@@ -626,6 +655,10 @@ protected function emitMethod($result, $method) {
 
     if (null === $method->body) {
       $result->out->write(';');
+      $default= null;
+    } else if ('interface' === $result->type[0]->kind) {
+      $result->out->write(';');
+      $default= $method;
     } else {
       $result->out->write(' {');
       $this->emitInitializations($result, $result->locals[1]);
@@ -634,6 +667,7 @@ protected function emitMethod($result, $method) {
       }
       $this->emitAll($result, $method->body);
       $result->out->write('}');
+      $default= null;
     }
 
     foreach ($promoted as $param) {
@@ -646,6 +680,10 @@ protected function emitMethod($result, $method) {
     foreach ($virtual as $name => $access) {
       $result->locals[2][$name]= $access;
     }
+
+    // Copy default implementations to class scope
+    $default && $result->locals[3][]= $default;
+
     $result->meta[0][self::METHOD][$method->name]= $meta;
   }
 
diff --git a/src/main/php/lang/ast/emit/Reflection.class.php b/src/main/php/lang/ast/emit/Reflection.class.php
index 858439b4..ce291502 100755
--- a/src/main/php/lang/ast/emit/Reflection.class.php
+++ b/src/main/php/lang/ast/emit/Reflection.class.php
@@ -23,6 +23,15 @@ public function __construct($type) {
   /** @return string */
   public function name() { return $this->reflect->name; }
 
+  /**
+   * Returns whether this is an interface with default implementations
+   *
+   * @return bool
+   */
+  public function defaultImplementations() {
+    return $this->reflect->isInterface() && trait_exists('__'.$this->reflect->name.'_Defaults', false);
+  }
+
   /**
    * Returns whether a given member is an enum case
    *
diff --git a/src/test/php/lang/ast/unittest/emit/TypeDeclarationTest.class.php b/src/test/php/lang/ast/unittest/emit/TypeDeclarationTest.class.php
index 508167cb..579c9d48 100755
--- a/src/test/php/lang/ast/unittest/emit/TypeDeclarationTest.class.php
+++ b/src/test/php/lang/ast/unittest/emit/TypeDeclarationTest.class.php
@@ -58,6 +58,22 @@ public function interface_type_with_method() {
     Assert::true($this->type('interface <T> { public function name(); }')->isInterface());
   }
 
+  #[Test]
+  public function interface_type_with_default_method() {
+    $i= $this->type('interface <T> {
+      public function all();
+      public function filter($filter) {
+        foreach ($this->all() as $element) {
+          $filter($element) || yield $element;
+        }
+      }
+    }');
+    $t= $this->type('class <T> implements '.$i->literal().'{
+      public function all() { return [1, 2, 3]; }
+    }');
+    Assert::equals([2], iterator_to_array($t->newInstance()->filter(function($i) { return $i % 2; })));
+  }
+
   #[Test, Values(['public', 'private', 'protected'])]
   public function constant($modifiers) {
     $c= $this->type('class <T> { '.$modifiers.' const test = 1; }')->getConstant('test');

From 0575e8abf0c9b81021185aa4c7476c740b147dc1 Mon Sep 17 00:00:00 2001
From: Timm Friebe <thekid@thekid.de>
Date: Sat, 24 Jun 2023 12:27:48 +0200
Subject: [PATCH 2/3] Fix interface default implementations declared inside
 namespaces

See https://github.com/xp-framework/compiler/pull/151/files#r1034038040
---
 .../php/lang/ast/emit/Declaration.class.php     | 16 ++++++++++------
 src/main/php/lang/ast/emit/Incomplete.class.php | 10 ++++++++++
 src/main/php/lang/ast/emit/PHP.class.php        | 17 ++++++++---------
 src/main/php/lang/ast/emit/Reflection.class.php | 13 ++++++++++---
 .../unittest/emit/TypeDeclarationTest.class.php |  8 ++++----
 5 files changed, 42 insertions(+), 22 deletions(-)

diff --git a/src/main/php/lang/ast/emit/Declaration.class.php b/src/main/php/lang/ast/emit/Declaration.class.php
index b5223b6b..b79aac95 100755
--- a/src/main/php/lang/ast/emit/Declaration.class.php
+++ b/src/main/php/lang/ast/emit/Declaration.class.php
@@ -20,17 +20,21 @@ public function __construct($type, $result) {
   public function name() { return ltrim($this->type->name, '\\'); }
 
   /**
-   * Returns whether this is an interface with default implementations
+   * Returns the (fully qualified) default implementations' trait name for
+   * interfaces, or `null`.
    *
-   * @return bool
+   * @return ?string
    */
   public function defaultImplementations() {
-    if ('interface' === $this->type->kind) {
-      foreach ($this->type->body as $member) {
-        if ($member instanceof Method && null !== $member->body) return true;
+    if ('interface' !== $this->type->kind) return null;
+
+    foreach ($this->type->body as $member) {
+      if ($member instanceof Method && null !== $member->body) {
+        $p= strrpos($this->type->name, '\\');
+        return substr($this->type->name, 0, $p).'\\__'.substr($this->type->name, $p + 1).'_Defaults';
       }
     }
-    return false;
+    return null;
   }
 
   /**
diff --git a/src/main/php/lang/ast/emit/Incomplete.class.php b/src/main/php/lang/ast/emit/Incomplete.class.php
index f4fcaffb..872b2da1 100755
--- a/src/main/php/lang/ast/emit/Incomplete.class.php
+++ b/src/main/php/lang/ast/emit/Incomplete.class.php
@@ -18,4 +18,14 @@ public function name() { return $this->name; }
   public function rewriteEnumCase($member) {
     return false;
   }
+
+  /**
+   * Returns the (fully qualified) default implementations' trait name for
+   * interfaces, or `null`.
+   *
+   * @return ?string
+   */
+  public function defaultImplementations() {
+    return null;
+  }
 }
\ No newline at end of file
diff --git a/src/main/php/lang/ast/emit/PHP.class.php b/src/main/php/lang/ast/emit/PHP.class.php
index 00a148c3..79fcbad9 100755
--- a/src/main/php/lang/ast/emit/PHP.class.php
+++ b/src/main/php/lang/ast/emit/PHP.class.php
@@ -430,7 +430,9 @@ protected function emitClass($result, $class) {
       $list= '';
       foreach ($class->implements as $type) {
         $list.= ', '.$type->literal();
-        $result->lookup($type->literal())->defaultImplementations() && $defaults[]= $type;
+        if ($default= $result->lookup($type->literal())->defaultImplementations()) {
+          $defaults[]= $default;
+        }
       }
       $result->out->write(' implements '.substr($list, 2));
     }
@@ -476,9 +478,8 @@ protected function emitClass($result, $class) {
       $result->out->write('}');
     }
 
-    foreach ($defaults as $trait) {
-      $result->out->write('use __'.substr($trait->literal(), 1).'_Defaults;');
-    }
+    // Use interface default implementations
+    $defaults && $result->out->write('use '.implode(', ', $defaults).';');
 
     $result->out->write('static function __init() {');
     $this->emitInitializations($result, $context->statics);
@@ -553,11 +554,9 @@ protected function emitInterface($result, $interface) {
 
     // Emit default implementations
     if (isset($result->locals[3])) {
-      $this->emitOne($result, new TraitDeclaration(
-        [],
-        new IsValue('\\__'.$interface->declaration().'_Defaults'),
-        $result->locals[3]
-      ));
+      $p= strrpos($interface->name, '\\');
+      $name= substr($interface->name, 0, $p).'\\__'.substr($interface->name, $p + 1).'_Defaults';
+      $this->emitOne($result, new TraitDeclaration([], new IsValue($name), $result->locals[3]));
     }
 
     $result->codegen->leave();
diff --git a/src/main/php/lang/ast/emit/Reflection.class.php b/src/main/php/lang/ast/emit/Reflection.class.php
index ce291502..a78e5b1d 100755
--- a/src/main/php/lang/ast/emit/Reflection.class.php
+++ b/src/main/php/lang/ast/emit/Reflection.class.php
@@ -24,12 +24,19 @@ public function __construct($type) {
   public function name() { return $this->reflect->name; }
 
   /**
-   * Returns whether this is an interface with default implementations
+   * Returns the (fully qualified) default implementations' trait name for
+   * interfaces, or `null`.
    *
-   * @return bool
+   * @return ?string
    */
   public function defaultImplementations() {
-    return $this->reflect->isInterface() && trait_exists('__'.$this->reflect->name.'_Defaults', false);
+    if (!$this->reflect->isInterface()) return null;
+
+    $defaults= false === ($p= strrpos($this->reflect->name, '\\'))
+      ? "__{$this->reflect->name}_Defaults"
+      : substr($this->reflect->name, 0, $p).'\\__'.substr($this->reflect->name, $p + 1).'_Defaults'
+    ;
+    return trait_exists($defaults, false) ? '\\'.$defaults : null;
   }
 
   /**
diff --git a/src/test/php/lang/ast/unittest/emit/TypeDeclarationTest.class.php b/src/test/php/lang/ast/unittest/emit/TypeDeclarationTest.class.php
index 6bca3d14..41f200a0 100755
--- a/src/test/php/lang/ast/unittest/emit/TypeDeclarationTest.class.php
+++ b/src/test/php/lang/ast/unittest/emit/TypeDeclarationTest.class.php
@@ -69,9 +69,9 @@ public function interface_type_with_method() {
     );
   }
 
-  #[Test]
-  public function interface_type_with_default_method() {
-    $i= $this->type('interface <T> {
+  #[Test, Values(['', 'namespace test;'])]
+  public function interface_type_with_default_method($namespace) {
+    $i= $this->type($namespace.' interface <T> {
       public function all();
       public function filter($filter) {
         foreach ($this->all() as $element) {
@@ -79,7 +79,7 @@ public function filter($filter) {
         }
       }
     }');
-    $t= $this->type('class <T> implements '.$i->literal().'{
+    $t= $this->type($namespace.' class <T> implements \\'.$i->literal().'{
       public function all() { return [1, 2, 3]; }
     }');
     Assert::equals([2], iterator_to_array($t->newInstance()->filter(function($i) { return $i % 2; })));

From 588ff3815ef326ecc03ada254696fb6712f7be16 Mon Sep 17 00:00:00 2001
From: Timm Friebe <thekid@thekid.de>
Date: Sat, 24 Jun 2023 12:33:58 +0200
Subject: [PATCH 3/3] Declare defaultImplementations inside InType scope

See https://github.com/xp-framework/compiler/pull/151#discussion_r1103792229
---
 src/main/php/lang/ast/emit/InType.class.php |  1 +
 src/main/php/lang/ast/emit/PHP.class.php    | 12 +++---------
 2 files changed, 4 insertions(+), 9 deletions(-)

diff --git a/src/main/php/lang/ast/emit/InType.class.php b/src/main/php/lang/ast/emit/InType.class.php
index 4df91107..e356fa47 100755
--- a/src/main/php/lang/ast/emit/InType.class.php
+++ b/src/main/php/lang/ast/emit/InType.class.php
@@ -6,6 +6,7 @@ class InType {
   public $init= [];
   public $statics= [];
   public $virtual= [];
+  public $defaultImplementations= [];
 
   public function __construct($type) {
     $this->type= $type;
diff --git a/src/main/php/lang/ast/emit/PHP.class.php b/src/main/php/lang/ast/emit/PHP.class.php
index 79fcbad9..35bcfcac 100755
--- a/src/main/php/lang/ast/emit/PHP.class.php
+++ b/src/main/php/lang/ast/emit/PHP.class.php
@@ -553,10 +553,10 @@ protected function emitInterface($result, $interface) {
     $this->emitMeta($result, $interface->name, $interface->annotations, $interface->comment);
 
     // Emit default implementations
-    if (isset($result->locals[3])) {
+    if ($defaults= $result->codegen->scope[0]->defaultImplementations) {
       $p= strrpos($interface->name, '\\');
       $name= substr($interface->name, 0, $p).'\\__'.substr($interface->name, $p + 1).'_Defaults';
-      $this->emitOne($result, new TraitDeclaration([], new IsValue($name), $result->locals[3]));
+      $this->emitOne($result, new TraitDeclaration([], new IsValue($name), $defaults));
     }
 
     $result->codegen->leave();
@@ -670,10 +670,9 @@ protected function emitMethod($result, $method) {
 
     if (null === $method->body) {
       $result->out->write(';');
-      $default= null;
     } else if ('interface' === $result->codegen->scope[0]->type->kind) {
       $result->out->write(';');
-      $default= $method;
+      $result->codegen->scope[0]->defaultImplementations[]= $method;
     } else {
       $result->out->write(' {');
 
@@ -697,7 +696,6 @@ protected function emitMethod($result, $method) {
 
       $this->emitAll($result, $method->body);
       $result->out->write('}');
-      $default= null;
     }
 
     foreach ($promoted as $param) {
@@ -705,10 +703,6 @@ protected function emitMethod($result, $method) {
     }
 
     $result->locals= array_pop($result->stack);
-
-    // Copy default implementations to class scope
-    $default && $result->locals[3][]= $default;
-
     $result->codegen->scope[0]->meta[self::METHOD][$method->name]= $meta;
   }
 
