Skip to content

Commit b12e5c1

Browse files
committed
Emit PHP 8.1 native enums if support is available
1 parent 179b15b commit b12e5c1

File tree

5 files changed

+222
-7
lines changed

5 files changed

+222
-7
lines changed

src/main/php/lang/ast/emit/PHP.class.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ protected function isConstant($result, $node) {
5656
return (
5757
$node->member instanceof Literal &&
5858
is_string($node->type) &&
59-
!$result->lookup($node->type)->isEnumCase($node->member->expression)
59+
!$result->lookup($node->type)->rewriteEnumCase($node->member->expression)
6060
);
6161
} else if ($node instanceof BinaryExpression) {
6262
return $this->isConstant($result, $node->left) && $this->isConstant($result, $node->right);
@@ -984,7 +984,7 @@ protected function emitScope($result, $scope) {
984984
$result->out->write(')?'.$t.'::');
985985
$this->emitOne($result, $scope->member);
986986
$result->out->write(':null');
987-
} else if ($scope->member instanceof Literal && $result->lookup($scope->type)->isEnumCase($scope->member->expression)) {
987+
} else if ($scope->member instanceof Literal && $result->lookup($scope->type)->rewriteEnumCase($scope->member->expression)) {
988988
$result->out->write($scope->type.'::$'.$scope->member->expression);
989989
} else {
990990
$result->out->write($scope->type.'::');
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
<?php namespace lang\ast\emit;
2+
3+
use lang\ast\Node;
4+
use lang\ast\nodes\{Literal, Variable};
5+
use lang\ast\types\{IsUnion, IsFunction, IsArray, IsMap, IsNullable, IsValue, IsLiteral};
6+
7+
/**
8+
* PHP 8.1 syntax
9+
*
10+
* @see https://wiki.php.net/rfc#php_81
11+
*/
12+
class PHP81 extends PHP {
13+
use RewriteBlockLambdaExpressions;
14+
15+
private static $ENUMS;
16+
17+
static function __static() {
18+
self::$ENUMS= class_exists(\ReflectionEnum::class, false); // TODO remove once enum PR is merged
19+
}
20+
21+
/** Sets up type => literal mappings */
22+
public function __construct() {
23+
$this->literals= [
24+
IsArray::class => function($t) { return 'array'; },
25+
IsMap::class => function($t) { return 'array'; },
26+
IsFunction::class => function($t) { return 'callable'; },
27+
IsValue::class => function($t) { return $t->literal(); },
28+
IsNullable::class => function($t) { $l= $this->literal($t->element); return null === $l ? null : '?'.$l; },
29+
IsUnion::class => function($t) {
30+
$u= '';
31+
foreach ($t->components as $component) {
32+
if (null === ($l= $this->literal($component))) return null;
33+
$u.= '|'.$l;
34+
}
35+
return substr($u, 1);
36+
},
37+
IsLiteral::class => function($t) { return $t->literal(); }
38+
];
39+
}
40+
41+
/**
42+
* Returns whether a given node is a constant expression:
43+
*
44+
* - Any literal
45+
* - Arrays where all members are literals
46+
* - Scope expressions with literal members (self::class, T::const)
47+
* - Binary expression where left- and right hand side are literals
48+
*
49+
* @see https://wiki.php.net/rfc/const_scalar_exprs
50+
* @param lang.ast.Result $result
51+
* @param lang.ast.Node $node
52+
* @return bool
53+
*/
54+
protected function isConstant($result, $node) {
55+
if ($node instanceof Literal) {
56+
return true;
57+
} else if ($node instanceof ArrayLiteral) {
58+
foreach ($node->values as $node) {
59+
if (!$this->isConstant($result, $node)) return false;
60+
}
61+
return true;
62+
} else if ($node instanceof ScopeExpression) {
63+
return (
64+
$node->member instanceof Literal &&
65+
is_string($node->type) &&
66+
!$result->lookup($node->type)->rewriteEnumCase($node->member->expression, self::$ENUMS)
67+
);
68+
} else if ($node instanceof BinaryExpression) {
69+
return $this->isConstant($result, $node->left) && $this->isConstant($result, $node->right);
70+
}
71+
return false;
72+
}
73+
74+
protected function emitArguments($result, $arguments) {
75+
$i= 0;
76+
foreach ($arguments as $name => $argument) {
77+
if ($i++) $result->out->write(',');
78+
if (is_string($name)) $result->out->write($name.':');
79+
$this->emitOne($result, $argument);
80+
}
81+
}
82+
83+
protected function emitScope($result, $scope) {
84+
if ($scope->type instanceof Variable) {
85+
$this->emitOne($result, $scope->type);
86+
$result->out->write('::');
87+
$this->emitOne($result, $scope->member);
88+
} else if ($scope->type instanceof Node) {
89+
$t= $result->temp();
90+
$result->out->write('('.$t.'=');
91+
$this->emitOne($result, $scope->type);
92+
$result->out->write(')?'.$t.'::');
93+
$this->emitOne($result, $scope->member);
94+
$result->out->write(':null');
95+
} else if ($scope->member instanceof Literal && $result->lookup($scope->type)->rewriteEnumCase($scope->member->expression, self::$ENUMS)) {
96+
$result->out->write($scope->type.'::$'.$scope->member->expression);
97+
} else {
98+
$result->out->write($scope->type.'::');
99+
$this->emitOne($result, $scope->member);
100+
}
101+
}
102+
103+
protected function emitEnumCase($result, $case) {
104+
if (self::$ENUMS) {
105+
$result->out->write('case '.$case->name);
106+
if ($case->expression) {
107+
$result->out->write('=');
108+
$this->emitOne($result, $case->expression);
109+
}
110+
$result->out->write(';');
111+
} else {
112+
parent::emitEnumCase($result, $case);
113+
}
114+
}
115+
116+
protected function emitEnum($result, $enum) {
117+
if (self::$ENUMS) {
118+
array_unshift($result->type, $enum);
119+
array_unshift($result->meta, []);
120+
$result->locals= [[], []];
121+
122+
$result->out->write('enum '.$this->declaration($enum->name));
123+
$enum->base && $result->out->write(':'.$enum->base);
124+
$enum->implements && $result->out->write(' implements '.implode(', ', $enum->implements));
125+
$result->out->write('{');
126+
127+
foreach ($enum->body as $member) {
128+
$this->emitOne($result, $member);
129+
}
130+
131+
// Initializations
132+
$result->out->write('static function __init() {');
133+
$this->emitInitializations($result, $result->locals[0]);
134+
$this->emitMeta($result, $enum->name, $enum->annotations, $enum->comment);
135+
$result->out->write('}} '.$enum->name.'::__init();');
136+
array_shift($result->type);
137+
} else {
138+
parent::emitEnum($result, $enum);
139+
}
140+
}
141+
142+
protected function emitNew($result, $new) {
143+
if ($new->type instanceof Node) {
144+
$result->out->write('new (');
145+
$this->emitOne($result, $new->type);
146+
$result->out->write(')(');
147+
} else {
148+
$result->out->write('new '.$new->type.'(');
149+
}
150+
151+
$this->emitArguments($result, $new->arguments);
152+
$result->out->write(')');
153+
}
154+
155+
protected function emitThrowExpression($result, $throw) {
156+
$result->out->write('throw ');
157+
$this->emitOne($result, $throw->expression);
158+
}
159+
160+
protected function emitCatch($result, $catch) {
161+
$capture= $catch->variable ? ' $'.$catch->variable : '';
162+
if (empty($catch->types)) {
163+
$result->out->write('catch(\\Throwable'.$capture.') {');
164+
} else {
165+
$result->out->write('catch('.implode('|', $catch->types).$capture.') {');
166+
}
167+
$this->emitAll($result, $catch->body);
168+
$result->out->write('}');
169+
}
170+
171+
protected function emitNullsafeInstance($result, $instance) {
172+
$this->emitOne($result, $instance->expression);
173+
$result->out->write('?->');
174+
175+
if ('literal' === $instance->member->kind) {
176+
$result->out->write($instance->member->expression);
177+
} else {
178+
$result->out->write('{');
179+
$this->emitOne($result, $instance->member);
180+
$result->out->write('}');
181+
}
182+
}
183+
184+
protected function emitMatch($result, $match) {
185+
if (null === $match->expression) {
186+
$result->out->write('match (true) {');
187+
} else {
188+
$result->out->write('match (');
189+
$this->emitOne($result, $match->expression);
190+
$result->out->write(') {');
191+
}
192+
193+
foreach ($match->cases as $case) {
194+
$b= 0;
195+
foreach ($case->expressions as $expression) {
196+
$b && $result->out->write(',');
197+
$this->emitOne($result, $expression);
198+
$b++;
199+
}
200+
$result->out->write('=>');
201+
$this->emitAsExpression($result, $case->body);
202+
$result->out->write(',');
203+
}
204+
205+
if ($match->default) {
206+
$result->out->write('default=>');
207+
$this->emitAsExpression($result, $match->default);
208+
}
209+
210+
$result->out->write('}');
211+
}
212+
}

src/main/php/lang/ast/types/Declaration.class.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ public function name() { return ltrim($this->type->name, '\\'); }
2121
* Returns whether a given member is an enum case
2222
*
2323
* @param string $member
24+
* @param bool $native Whether native enum support exists
2425
* @return bool
2526
*/
26-
public function isEnumCase($member) {
27-
if ('enum' === $this->type->kind) {
27+
public function rewriteEnumCase($member, $native= false) {
28+
if (!$native && 'enum' === $this->type->kind) {
2829
return ($this->type->body[$member] ?? null) instanceof EnumCase;
2930
} else if ('class' === $this->type->kind && '\\lang\\Enum' === $this->type->parent) {
3031
return ($this->type->body['$'.$member] ?? null) instanceof Property;

src/main/php/lang/ast/types/Reflection.class.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ public function name() { return $this->reflect->name; }
2121
* Returns whether a given member is an enum case
2222
*
2323
* @param string $member
24+
* @param bool $native Whether enums are natively supported
2425
* @return bool
2526
*/
26-
public function isEnumCase($member) {
27+
public function rewriteEnumCase($member, $native= false) {
2728
if ($this->reflect->isSubclassOf(Enum::class)) {
2829
return $this->reflect->getStaticPropertyValue($member, null) instanceof Enum;
29-
} else if ($this->reflect->isSubclassOf(\UnitEnum::class)) {
30+
} else if (!$native && $this->reflect->isSubclassOf(\UnitEnum::class)) {
3031
$value= $this->reflect->getConstant($member) ?: $this->reflect->getStaticPropertyValue($member, null);
3132
return $value instanceof \UnitEnum;
3233
}

src/main/php/lang/ast/types/Type.class.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ public function name();
99
* Returns whether a given member is an enum case
1010
*
1111
* @param string $member
12+
* @param bool $native Whether native enum support exists
1213
* @return bool
1314
*/
14-
public function isEnumCase($member);
15+
public function rewriteEnumCase($member, $native= false);
1516
}

0 commit comments

Comments
 (0)