-
Notifications
You must be signed in to change notification settings - Fork 0
Implement first-class callable syntax #114
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
Changes from 8 commits
1fd3f5b
29c2a1c
ab460ef
6b85e46
24f3b2c
a9bc033
12e517b
1c2279b
0c84bef
a321734
8ef7349
f25b9d2
e219849
cf6f56a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?php namespace lang\ast\emit; | ||
|
||
/** | ||
* Rewrites callable expressions to regular closures | ||
* | ||
* @see https://wiki.php.net/rfc/first_class_callable_syntax | ||
*/ | ||
trait CallablesAsClosures { | ||
|
||
protected function emitCallable($result, $callable) { | ||
|
||
// Use variables in the following cases: | ||
// | ||
// $closure(...); => use ($closure) | ||
// $obj->method(...); => use ($obj) | ||
// $obj->$method(...); => use ($obj, $method) | ||
// ($obj->property)(...); => use ($obj) | ||
// $class::$method(...); => use ($class, $method) | ||
// [$obj, 'method'](...); => use ($obj) | ||
// [Foo::class, $method](...); => use ($method) | ||
$use= []; | ||
foreach ($result->codegen->search($callable, 'variable') as $var) { | ||
$use[$var->name]= true; | ||
} | ||
unset($use['this']); | ||
|
||
// Create closure | ||
$t= $result->temp(); | ||
$result->out->write('function(...'.$t.')'); | ||
$use && $result->out->write('use($'.implode(',$', array_keys($use)).')'); | ||
$result->out->write('{ return '); | ||
$this->emitOne($result, $callable->expression); | ||
$result->out->write('(...'.$t.'); }'); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -988,6 +988,13 @@ protected function emitNewClass($result, $new) { | |
array_shift($result->type); | ||
} | ||
|
||
protected function emitCallable($result, $callable) { | ||
$t= $result->temp(); | ||
$result->out->write('fn(...'.$t.')=>'); | ||
$this->emitOne($result, $callable->expression); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Once the RFC merged to PHP 8.1, we should move this to a trait for PHP 7.4 and 8.0 and emit native code on PHP 8.1 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now the only thing we need to do is to remove the |
||
$result->out->write('(...'.$t.')'); | ||
} | ||
|
||
protected function emitInvoke($result, $invoke) { | ||
$this->emitOne($result, $invoke->expression); | ||
$result->out->write('('); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
<?php namespace lang\ast\unittest\emit; | ||
|
||
use unittest\{Assert, Test, Values}; | ||
|
||
/** | ||
* Tests for first-class callable syntax | ||
* | ||
* @see https://wiki.php.net/rfc/first_class_callable_syntax#proposal | ||
*/ | ||
class CallableSyntaxTest extends EmittingTest { | ||
|
||
/** | ||
* Verification helper | ||
* | ||
* @param string $code | ||
* @return void | ||
* @throws unittest.AssertionFailedError | ||
*/ | ||
private function verify($code) { | ||
Assert::equals(4, $this->run($code)('Test')); | ||
} | ||
|
||
#[Test] | ||
public function native_function() { | ||
$this->verify('class <T> { | ||
public function run() { return strlen(...); } | ||
}'); | ||
} | ||
|
||
#[Test] | ||
public function instance_method() { | ||
$this->verify('class <T> { | ||
public function length($arg) { return strlen($arg); } | ||
public function run() { return $this->length(...); } | ||
}'); | ||
} | ||
|
||
#[Test] | ||
public function class_method() { | ||
$this->verify('class <T> { | ||
public static function length($arg) { return strlen($arg); } | ||
public function run() { return self::length(...); } | ||
}'); | ||
} | ||
|
||
#[Test] | ||
public function private_method() { | ||
$this->verify('class <T> { | ||
private function length($arg) { return strlen($arg); } | ||
public function run() { return $this->length(...); } | ||
}'); | ||
} | ||
|
||
#[Test] | ||
public function variable_function() { | ||
$this->verify('class <T> { | ||
public function run() { | ||
$func= "strlen"; | ||
return $func(...); | ||
} | ||
}'); | ||
} | ||
|
||
#[Test] | ||
public function instance_method_reference() { | ||
$this->verify('class <T> { | ||
private $func= "strlen"; | ||
public function run() { | ||
return ($this->func)(...); | ||
} | ||
}'); | ||
} | ||
|
||
#[Test, Values(['$this->$func(...)', '$this->{$func}(...)'])] | ||
public function variable_instance_method($expr) { | ||
$this->verify('class <T> { | ||
private function length($arg) { return strlen($arg); } | ||
public function run() { | ||
$func= "length"; | ||
return '.$expr.'; | ||
} | ||
}'); | ||
} | ||
|
||
#[Test, Values(['self::$func(...)', 'self::{$func}(...)'])] | ||
public function variable_class_method($expr) { | ||
$this->verify('class <T> { | ||
private static function length($arg) { return strlen($arg); } | ||
public function run() { | ||
$func= "length"; | ||
return '.$expr.'; | ||
} | ||
}'); | ||
} | ||
|
||
#[Test] | ||
public function variable_class_method_with_variable_class() { | ||
$this->verify('class <T> { | ||
private static function length($arg) { return strlen($arg); } | ||
public function run() { | ||
$func= "length"; | ||
$class= __CLASS__; | ||
return $class::$func(...); | ||
} | ||
}'); | ||
} | ||
|
||
#[Test] | ||
public function string_function_reference() { | ||
$this->verify('class <T> { | ||
public function run() { return "strlen"(...); } | ||
}'); | ||
} | ||
|
||
#[Test] | ||
public function array_instance_method_reference() { | ||
$this->verify('class <T> { | ||
public function length($arg) { return strlen($arg); } | ||
public function run() { return [$this, "length"](...); } | ||
}'); | ||
} | ||
|
||
#[Test] | ||
public function array_class_method_reference() { | ||
$this->verify('class <T> { | ||
public static function length($arg) { return strlen($arg); } | ||
public function run() { return [self::class, "length"](...); } | ||
}'); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.