10000 Support arbitrary expressions in property initializations and parameter defaults by thekid · Pull Request #104 · xp-framework/compiler · GitHub
[go: up one dir, main page]

Skip to content

Support arbitrary expressions in property initializations and parameter defaults #104

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

Merged
merged 15 commits into from
Mar 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
121 changes: 104 additions & 17 deletions src/main/php/lang/ast/emit/PHP.class.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php namespace lang\ast\emit;

use lang\ast\nodes\{InstanceExpression, ScopeExpression, Variable, Block};
use lang\ast\Code;
use lang\ast\nodes\{InstanceExpression, ScopeExpression, BinaryExpression, Variable, Literal, ArrayLiteral, Block};
use lang\ast\types\{IsUnion, IsFunction, IsArray, IsMap};
use lang\ast\{Emitter, Node, Type};

Expand Down Expand Up @@ -30,6 +31,34 @@ protected function declaration($name) {
return substr($name, strrpos($name, '\\') + 1);
}

/**
* Returns whether a given node is a constant expression:
*
* - Any literal
* - Arrays where all members are literals
* - Scope expressions with literal members (self::class, T::const)
* - Binary expression where left- and right hand side are literals
*
* @see https://wiki.php.net/rfc/const_scalar_exprs
* @param lang.ast.Node $node
* @return bool
*/
protected function isConstant($node) {
if ($node instanceof Literal) {
return true;
} else if ($node instanceof ArrayLiteral) {
foreach ($node->values as $node) {
if (!$this->isConstant($node)) return false;
}
return true;
} else if ($node instanceof ScopeExpression) {
return $node->member instanceof Literal;
} else if ($node instanceof BinaryExpression) {
return $this->isConstant($node->left) && $this->isConstant($node->right);
}
return false;
}

/**
* As of PHP 7.4: Property type declarations support all type declarations
* supported by PHP with the exception of void and callable.
Expand Down Expand Up @@ -88,6 +117,21 @@ protected function enclose($result, $node, $signature, $emit) {
$result->locals= array_pop($result->stack);
}

/**
* Emits local initializations
*
* @param lang.ast.Result $result
* @param [:lang.ast.Node] $init
* @return void
*/
protected function emitInitializations($result, $init) {
foreach ($init as $assign => $expression) {
$result->out->write($assign.'=');
$this->emitOne($result, $expression);
$result->out->write(';');
}
}

/**
* Convert blocks to IIFEs to allow a list of statements where PHP syntactically
* doesn't, e.g. `fn`-style lambdas or match expressions.
Expand Down Expand Up @@ -144,8 +188,13 @@ protected function emitStatic($result, $static) {
foreach ($static->initializations as $variable => $initial) {
$result->out->write('static $'.$variable);
if ($initial) {
$result->out->write('=');
$this->emitOne($result, $initial);
if ($this->isConstant($initial)) {
$result->out->write('=');
$this->emitOne($result, $initial);
} else {
$result->out->write('= null; null === $'.$variable.' && $'.$variable.'= ');
$this->emitOne($result, $initial);
}
}
$result->out->write(';');
}
Expand Down Expand Up @@ -235,8 +284,13 @@ protected function emitParameter($result, $parameter) {
$result->out->write(($parameter->reference ? '&' : '').'$'.$parameter->name);
}
if ($parameter->default) {
$result->out->write('=');
$this->emitOne($result, $parameter->default);
if ($this->isConstant($parameter->default)) {
$result->out->write('=');
$this->emitOne($result, $parameter->default);
} else {
$result->out->write('=null');
$result->locals[1]['null === $'.$parameter->name.' && $'.$parameter->name]= $parameter->default;
}
}
$result->locals[$parameter->name]= true;
}
Expand Down Expand Up @@ -297,6 +351,7 @@ protected function emitLambda($result, $lambda) {

protected function emitClass($result, $class) {
array_unshift($result->meta, []);
$result->locals= [[], []];

$result->out->write(implode(' ', $class->modifiers).' class '.$this->declaration($class->name));
$class->parent && $result->out->write(' extends '.$class->parent);
Expand All @@ -306,7 +361,16 @@ protected function emitClass($result, $class) {
$this->emitOne($result, $member);
}

// Create constructor for property initializations to non-static scalars
// which have not already been emitted inside constructor
if ($result->locals[1]) {
$result->out->write('public function __construct() {');
$this->emitInitializations($result, $result->locals[1]);
$result->out->write('}');
}

$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.'::__init();');
}
Expand Down Expand Up @@ -452,15 +516,31 @@ protected function emitProperty($result, $property) {

$result->out->write(implode(' ', $property->modifiers).' '.$this->propertyType($property->type).' $'.$property->name);
if (isset($property->expression)) {
$result->out->write('=');
$this->emitOne($result, $property->expression);
if ($this->isConstant($property->expression)) {
$result->out->write('=');
$this->emitOne($result, $property->expression);
} else if (in_array('static', $property->modifiers)) {
$result->locals[0]['self::$'.$property->name]= $property->expression;
} else {
$result->locals[1]['$this->'.$property->name]= $property->expression;
}
}
$result->out->write(';');
}

protected function emitMethod($result, $method) {

// Include non-static initializations for members in constructor, removing
// them to signal emitClass() no constructor needs to be generated.
if ('__construct' === $method->name && isset($result->locals[1])) {
$locals= ['this' => true, 1 => $result->locals[1]];
$result->locals[1]= [];
} else {
$locals= ['this' => true, 1 => []];
}
$result->stack[]= $result->locals;
$result->locals= ['this' => true];
$result->locals= $locals;

$meta= [
DETAIL_RETURNS => $method->signature->returns ? $method->signature->returns->name() : 'var',
DETAIL_ANNOTATIONS => $method->annotations,
Expand All @@ -469,11 +549,17 @@ protected function emitMethod($result, $method) {
DETAIL_ARGUMENTS => []
];

$declare= $promote= $params= '';
$result->out->write(implode(' ', $method->modifiers).' function '.$method->name);
$this->emitSignature($result, $method->signature);

$promoted= '';
foreach ($method->signature->parameters as $param) {
$meta[DETAIL_TARGET_ANNO][$param->name]= $param->annotations;
$meta[DETAIL_ARGUMENTS][]= $param->type ? $param->type->name() : 'var';

if (isset($param->promote)) {
$declare.= $param->promote.' $'.$param->name.';';
$promote.= '$this->'.$param->name.'= '.($param->reference ? '&$' : '$').$param->name.';';
$promoted.= $param->promote.' $'.$param->name.';';
$result->locals[1]['$this->'.$param->name]= new Code(($param->reference ? '&$' : '$').$param->name);
$result->meta[0][self::PROPERTY][$param->name]= [
DETAIL_RETURNS => $param->type ? $param->type->name() : 'var',
DETAIL_ANNOTATIONS => [],
Expand All @@ -482,21 +568,22 @@ protected function emitMethod($result, $method) {
DETAIL_ARGUMENTS => []
];
}
$meta[DETAIL_TARGET_ANNO][$param->name]= $param->annotations;
$meta[DETAIL_ARGUMENTS][]= $param->type ? $param->type->name() : 'var';

if (isset($param->default) && !$this->isConstant($param->default)) {
$meta[DETAIL_TARGET_ANNO][$param->name]['default']= [$param->default];
}
}
$result->out->write($declare);
$result->out->write(implode(' ', $method->modifiers).' function '.$method->name);
$this->emitSignature($result, $method->signature);

if (null === $method->body) {
$result->out->write(';');
} else {
$result->out->write(' {'.$promote);
$result->out->write(' {');
$this->emitInitializations($result, $result->locals[1]);
$this->emitAll($result, $method->body);
$result->out->write('}');
}

$result->out->write($promoted);
$result->meta[0][self::METHOD][$method->name]= $meta;
$result->locals= array_pop($result->stack);
}
Expand Down
10 changes: 10 additions & 0 deletions src/test/php/lang/ast/unittest/emit/Handle.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,21 @@

/** Used by `UsingTest` */
class Handle implements \IDisposable {
public static $DEFAULT;
public static $called= [];
private $id;

static function __static() {
self::$DEFAULT= new Handle(0);
}

public function __construct($id) { $this->id= $id; }

public function redirect($id) {
$this->id= $id;
return $this;
}

public function read($bytes= 8192) {
self::$called[]= 'read@'.$this->id;

Expand Down
Loading
0