Closed thekid closed 3 years ago
The problem with this is that below the hood, we cannot set class constants to anything other than constant scalar expressions. We could rewrite code to use public static members like XP enums currently do:
// What the user writes
$color= Suit::Diamonds->color();
// What the compiler emits
$color= Suite::$Diamonds->color();
For this to work, the compiler needs to keep track of the type of Suite
(and resolve self
, parent
and static
correctly inside the type). Currently, the compiler doesn't need to know anything about the underlying types, which makes it fast. A complete type-tracking mechanism may be overblown for the beginning, but would open quite a bit of possibilities on the other side.
We will not be able to simulate the example where enum constants are used as default values for parameters with static members (nor will it work with global constants, define() will not allow them to be objects).
enum SortOrder {
case ASC;
case DESC;
}
function query($fields, $filter, SortOrder $order= SortOrder::ASC) { ... }
(see https://wiki.php.net/rfc/enumerations#examples)
This would need to be rewritten by using NULL and inserting a null-coalesce-assignment statement:
function query($fields, $filter, SortOrder $order= null) {
$order??= SortOrder::ASC; // Inserted statement
...
}
...which would also allow for arbitrary expressions for method parameters.
This would need to be rewritten by using NULL and inserting a null-coalesce-assignment statement:
This has now been implemented by #104 in the compiler and xp-framework/core#259 in the reflection API
We could rewrite code to use public static members
Here's the basics of how that would work:
diff --git a/src/main/php/lang/ast/Result.class.php b/src/main/php/lang/ast/Result.class.php
index 166d192..c59e027 100755
--- a/src/main/php/lang/ast/Result.class.php
+++ b/src/main/php/lang/ast/Result.class.php
@@ -7,6 +7,7 @@ class Result {
public $meta= [];
public $locals= [];
public $stack= [];
+ public $type= [];
/**
* Starts a result stream, including a preamble
}
\ 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 7afbf52..348ce2e 100755
--- a/src/main/php/lang/ast/emit/PHP.class.php
+++ b/src/main/php/lang/ast/emit/PHP.class.php
@@ -350,6 +350,7 @@ abstract class PHP extends Emitter {
}
protected function emitClass($result, $class) {
+ array_unshift($result->type, $class);
array_unshift($result->meta, []);
$result->locals= [[], []];
@@ -373,6 +374,7 @@ abstract class PHP extends Emitter {
$this->emitInitializations($result, $result->locals[0]);
$this->emitMeta($result, $class->name, $class->annotations, $class->comment);
$result->out->write('}} '.$class->name.'::__init();');
+ array_shift($result->type);
}
/** Stores lowercased, unnamespaced name in annotations for BC reasons! */
@@ -911,7 +913,13 @@ abstract class PHP extends Emitter {
$result->out->write(':null');
} else {
$result->out->write($scope->type.'::');
- $this->emitOne($result, $scope->member);
+
+ // Check for [Enum]::MEMBER and rewrite to [Enum]::$MEMBER
+ if ($scope->member instanceof Literal && '\\lang\\Enum' === $result->type[0]->parent) {
+ $result->out->write('$'.$scope->member->expression);
+ } else {
+ $this->emitOne($result, $scope->member);
+ }
}
}
diff --git a/src/test/php/lang/ast/unittest/emit/MembersTest.class.php b/...
index 6807e5e..10381cd 100755
--- a/src/test/php/lang/ast/unittest/emit/MembersTest.class.php
+++ b/src/test/php/lang/ast/unittest/emit/MembersTest.class.php
@@ -171,6 +171,19 @@ class MembersTest extends EmittingTest {
Assert::equals('MON', $r);
}
+ #[Test]
+ public function allow_constant_syntax_for_members() {
+ $r= $this->run('class <T> extends \lang\Enum {
+ public static $MON, $TUE, $WED, $THU, $FRI, $SAT, $SUN;
+
+ public function run() {
+ return self::MON->name();
+ }
+ }');
+
+ Assert::equals('MON', $r);
+ }
+
#[Test]
public function method_with_static() {
$r= $this->run('class <T> {
Of course, '\\lang\\Enum' === $result->type[0]->parent
only works if self
is used and lang.Enum
is the direct parent, and would need to be replaced by a more elaborate lookup mechansim, e.g. on the Result
class.
For the UnitEnum
and BackendEnum
interfaces, see here: https://wiki.php.net/rfc/enumerations#new_interfaces
// What the user writes
enum Suit {
case Hearts;
case Diamonds;
case Clubs;
case Spades;
}
// What is emitted for PHP < 8.1
final class Suite implements UnitEnum {
public static $Hearts, $Diamonds, $Clubs, $Spades;
public $name;
static function __static() {
self::$Hearts= new self('Hearts');
}
/** @param string $name */
public function __construct($name) { $this->name= $name; }
/** UnitEnum implementation */
public static function cases(): array {
return [self::$Hearts, self::$Diamonds, self::$Clubs, self::$Spades];
}
}
// What the user writes
enum Suit: string {
case Hearts = 'H';
case Diamonds = 'D';
case Clubs = 'C';
case Spades = 'S';
}
// What is emitted for PHP < 8.1
class Suit implements BackedEnum {
public static $Hearts, $Diamonds, $Clubs, $Spades;
public $name;
static function __static() {
self::$Hearts= new self('Hearts', value: 'H');
}
protected function __construct($name, $value) {
$this->name= $name;
$this->value= $value;
}
/** UnitEnum implementation */
public static function cases(): array {
return [self::$Hearts, self::$Diamonds, self::$Clubs, self::$Spades];
}
/** BackedEnum implementation */
public static function from($value) {
return self::tryFrom($value) ?? throw new ValueError($value.' is not a valid backing value for enum "Suit"');
}
/** BackedEnum implementation */
public static function tryFrom($value) {
return match ($value) {
'Hearts' => self::$Hearts,
'Diamonds' => self::$Diamonds,
'Clubs' => self::$Clubs,
'Spades' => self::$Spades,
default => null
};
}
}
The BackedEnum and UnitEnum interfaces could be declared by XP Core as well as enum_exists()
in a forward-compatible manner (see https://github.com/php/php-src/pull/6489/files#diff-88038e5dd6ab60a7177903be5b4583998458df12dc1566677150964a640eb8c0)
The BackedEnum and UnitEnum interfaces could be declared by XP Core as well as enum_exists()
:shipit: Released in https://github.com/xp-framework/compiler/releases/tag/v6.3.0
See https://wiki.php.net/rfc/enumerations
Currently, the XP syntax is implemented via https://github.com/xp-lang/xp-enums