- Add printerGenerated property to ClassDef entity - Implement removeMethod function in ClassDef for method removal - Add comprehensive tests for getter, setter, with, printer and notnull attributes - Create new CompileTimeAttribute utility class for attribute handling - Implement GetterLowering for generating getter methods from attributes - Add NotNullLowering for validating non-null parameters - Implement PrinterLowering for generating toString methods - Add PropertyMethodLowering for setter and with method generation - Update gen_stub.php to recognize new compile-time attributes - Enhance Preprocessor with printer generation logic - Add symbol repository function removal capability - Update Translator with printer generation predicate - Modify Visitor to handle new attribute transformations - Add polyfill definitions for Getter, Setter, Withpull/34/head
parent
50cf8c6655
commit
8bcd6ce863
20 changed files with 812 additions and 3 deletions
@ -0,0 +1,95 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace CompileTimeAttributes; |
||||||
|
|
||||||
|
use \Getter; |
||||||
|
use \NotNull; |
||||||
|
use \Printer; |
||||||
|
use \Setter; |
||||||
|
use \With; |
||||||
|
|
||||||
|
class PrintableBase |
||||||
|
{ |
||||||
|
public int $baseId = 1; |
||||||
|
|
||||||
|
protected string $ignored = 'hidden'; |
||||||
|
} |
||||||
|
|
||||||
|
#[Printer] |
||||||
|
class User extends PrintableBase |
||||||
|
{ |
||||||
|
public int $id = 2; |
||||||
|
|
||||||
|
public string $name = '张三'; |
||||||
|
|
||||||
|
#[Getter, Setter, With] |
||||||
|
private string $nickname = 'typephp'; |
||||||
|
|
||||||
|
public function rename(#[NotNull] string $name): void |
||||||
|
{ |
||||||
|
$this->name = $name; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class CustomPrinterBase |
||||||
|
{ |
||||||
|
public function toString(): string |
||||||
|
{ |
||||||
|
return 'custom'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[Printer] |
||||||
|
class CustomPrinterChild extends CustomPrinterBase |
||||||
|
{ |
||||||
|
public int $value = 1; |
||||||
|
} |
||||||
|
|
||||||
|
#[Printer] |
||||||
|
class LatePrinterChild extends LatePrinterBase |
||||||
|
{ |
||||||
|
public int $value = 1; |
||||||
|
} |
||||||
|
|
||||||
|
class LatePrinterBase |
||||||
|
{ |
||||||
|
public function toString(): string |
||||||
|
{ |
||||||
|
return 'late'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class PromotedProperties |
||||||
|
{ |
||||||
|
public function __construct( |
||||||
|
#[Getter, Setter, With] |
||||||
|
private int $value, |
||||||
|
) { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function requireValue(#[NotNull] int $value): int |
||||||
|
{ |
||||||
|
return $value; |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$requireName = function (#[NotNull] string $name): string { |
||||||
|
return $name; |
||||||
|
}; |
||||||
|
$user = new User(); |
||||||
|
$user->setNickname('php'); |
||||||
|
$copy = $user->withNickname('cpp'); |
||||||
|
echo $user->getNickname(); |
||||||
|
echo $copy->getNickname(); |
||||||
|
echo $user->toString(); |
||||||
|
echo (new CustomPrinterChild())->toString(); |
||||||
|
echo (new LatePrinterChild())->toString(); |
||||||
|
echo requireValue(1); |
||||||
|
echo $requireName('typephp'); |
||||||
|
|
||||||
|
$promoted = new PromotedProperties(1); |
||||||
|
$promoted->setValue(2); |
||||||
|
echo $promoted->withValue(3)->getValue(); |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
#[Getter] |
||||||
|
function invalidGetterTarget(): int |
||||||
|
{ |
||||||
|
return 1; |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
class InvalidStaticGetter |
||||||
|
{ |
||||||
|
#[Getter] |
||||||
|
public static int $value = 1; |
||||||
|
} |
||||||
@ -0,0 +1,37 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace App; |
||||||
|
|
||||||
|
use \Getter; |
||||||
|
|
||||||
|
class User |
||||||
|
{ |
||||||
|
#[Getter] |
||||||
|
private string $name = 'Alice'; |
||||||
|
|
||||||
|
#[\Getter] |
||||||
|
protected int $age = 18; |
||||||
|
|
||||||
|
#[Getter] |
||||||
|
public string $title = 'developer'; |
||||||
|
|
||||||
|
#[Getter] |
||||||
|
public int $x = 1, $y = 2; |
||||||
|
|
||||||
|
public function __construct( |
||||||
|
#[Getter] |
||||||
|
private bool $active = true, |
||||||
|
) { |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$user = new User(); |
||||||
|
var_dump($user->getName()); |
||||||
|
var_dump($user->getAge()); |
||||||
|
var_dump($user->getTitle()); |
||||||
|
var_dump($user->getX()); |
||||||
|
var_dump($user->getY()); |
||||||
|
var_dump($user->getActive()); |
||||||
|
} |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
#[NotNull] |
||||||
|
function invalidNotNullTarget(): void |
||||||
|
{ |
||||||
|
} |
||||||
@ -0,0 +1,68 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* This file is part of TypePHP. |
||||||
|
* |
||||||
|
* @link https://www.swoole.com/ |
||||||
|
* @contact service@swoole.com |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace TypePhp\Transform; |
||||||
|
|
||||||
|
use PhpParser\Node; |
||||||
|
use TypePhp\Exception\SyntaxError; |
||||||
|
|
||||||
|
final class CompileTimeAttribute |
||||||
|
{ |
||||||
|
public static function has(Node $node, string $name): bool |
||||||
|
{ |
||||||
|
if (!property_exists($node, 'attrGroups')) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
foreach ($node->attrGroups as $group) { |
||||||
|
foreach ($group->attrs as $attribute) { |
||||||
|
if (self::is($attribute, $name)) { |
||||||
|
self::validateArguments($attribute, $name); |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
public static function consume(Node $node, string $name): bool |
||||||
|
{ |
||||||
|
$found = false; |
||||||
|
foreach ($node->attrGroups as $groupIndex => $group) { |
||||||
|
foreach ($group->attrs as $attributeIndex => $attribute) { |
||||||
|
if (!self::is($attribute, $name)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
self::validateArguments($attribute, $name); |
||||||
|
$found = true; |
||||||
|
unset($group->attrs[$attributeIndex]); |
||||||
|
} |
||||||
|
$group->attrs = array_values($group->attrs); |
||||||
|
if ($group->attrs === []) { |
||||||
|
unset($node->attrGroups[$groupIndex]); |
||||||
|
} |
||||||
|
} |
||||||
|
$node->attrGroups = array_values($node->attrGroups); |
||||||
|
return $found; |
||||||
|
} |
||||||
|
|
||||||
|
public static function is(Node\Attribute $attribute, string $name): bool |
||||||
|
{ |
||||||
|
$resolvedName = $attribute->name->getAttribute('resolvedName') |
||||||
|
?? $attribute->name->getAttribute('namespacedName') |
||||||
|
?? $attribute->name; |
||||||
|
|
||||||
|
return strcasecmp(ltrim($resolvedName->toString(), '\\'), $name) === 0; |
||||||
|
} |
||||||
|
|
||||||
|
private static function validateArguments(Node\Attribute $attribute, string $name): void |
||||||
|
{ |
||||||
|
if ($attribute->args !== []) { |
||||||
|
throw new SyntaxError($name . ' does not accept arguments'); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,79 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* This file is part of TypePHP. |
||||||
|
* |
||||||
|
* @link https://www.swoole.com/ |
||||||
|
* @contact service@swoole.com |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace TypePhp\Transform; |
||||||
|
|
||||||
|
use PhpParser\Modifiers; |
||||||
|
use PhpParser\Node; |
||||||
|
use PhpParser\Node\Expr; |
||||||
|
use PhpParser\Node\Param; |
||||||
|
use PhpParser\Node\Stmt; |
||||||
|
use TypePhp\Exception\SyntaxError; |
||||||
|
|
||||||
|
final class GetterLowering |
||||||
|
{ |
||||||
|
public static function validateTarget(Node $node): void |
||||||
|
{ |
||||||
|
if (!CompileTimeAttribute::has($node, 'Getter')) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if ($node instanceof Stmt\Property) { |
||||||
|
if ($node->isStatic()) { |
||||||
|
throw new SyntaxError('Getter can only be applied to instance properties'); |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if ($node instanceof Param && $node->isPromoted()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
throw new SyntaxError('Getter can only be applied to instance properties'); |
||||||
|
} |
||||||
|
|
||||||
|
/** @return list<Stmt\ClassMethod> */ |
||||||
|
public static function lowerProperty(Stmt\Property $property): array |
||||||
|
{ |
||||||
|
if (!CompileTimeAttribute::consume($property, 'Getter')) { |
||||||
|
return []; |
||||||
|
} |
||||||
|
|
||||||
|
$methods = []; |
||||||
|
foreach ($property->props as $prop) { |
||||||
|
$methods[] = self::createGetter( |
||||||
|
$prop->name->toString(), |
||||||
|
$property->type, |
||||||
|
$property->getAttributes(), |
||||||
|
); |
||||||
|
} |
||||||
|
return $methods; |
||||||
|
} |
||||||
|
|
||||||
|
public static function lowerPromotedProperty(Param $param): ?Stmt\ClassMethod |
||||||
|
{ |
||||||
|
if (!$param->isPromoted() || !is_string($param->var->name) || !CompileTimeAttribute::consume($param, 'Getter')) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
return self::createGetter($param->var->name, $param->type, $param->getAttributes()); |
||||||
|
} |
||||||
|
|
||||||
|
private static function createGetter(string $property, ?Node $type, array $attributes): Stmt\ClassMethod |
||||||
|
{ |
||||||
|
return new Stmt\ClassMethod('get' . ucfirst($property), [ |
||||||
|
'flags' => Modifiers::PUBLIC, |
||||||
|
'returnType' => $type === null ? null : clone $type, |
||||||
|
'stmts' => [new Stmt\Return_(new Expr\PropertyFetch( |
||||||
|
new Expr\Variable('this'), |
||||||
|
$property, |
||||||
|
))], |
||||||
|
], $attributes); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,57 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* This file is part of TypePHP. |
||||||
|
* |
||||||
|
* @link https://www.swoole.com/ |
||||||
|
* @contact service@swoole.com |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace TypePhp\Transform; |
||||||
|
|
||||||
|
use PhpParser\Node; |
||||||
|
use PhpParser\Node\Expr; |
||||||
|
use PhpParser\Node\Param; |
||||||
|
use PhpParser\Node\Stmt; |
||||||
|
use TypePhp\Exception\SyntaxError; |
||||||
|
|
||||||
|
final class NotNullLowering |
||||||
|
{ |
||||||
|
public static function validateTarget(Node $node): void |
||||||
|
{ |
||||||
|
if (CompileTimeAttribute::has($node, 'NotNull') && !$node instanceof Param) { |
||||||
|
throw new SyntaxError('NotNull can only be applied to function or method parameters'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static function lowerFunction(Stmt\Function_|Stmt\ClassMethod|Expr\Closure $function): void |
||||||
|
{ |
||||||
|
$checks = []; |
||||||
|
foreach ($function->params as $param) { |
||||||
|
if (!CompileTimeAttribute::consume($param, 'NotNull')) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
if ($function->stmts === null || !is_string($param->var->name)) { |
||||||
|
throw new SyntaxError('NotNull requires a concrete function or method parameter'); |
||||||
|
} |
||||||
|
$name = $param->var->name; |
||||||
|
$checks[] = new Stmt\If_(new Expr\Empty_(new Expr\Variable($name)), [ |
||||||
|
'stmts' => [new Stmt\Expression(new Expr\Throw_(new Expr\New_( |
||||||
|
new Node\Name\FullyQualified('ValueError'), |
||||||
|
[new Node\Arg(new Node\Scalar\String_('Parameter $' . $name . ' must not be empty'))], |
||||||
|
)))], |
||||||
|
]); |
||||||
|
} |
||||||
|
if ($checks !== []) { |
||||||
|
$function->stmts = [...$checks, ...$function->stmts]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static function rejectArrowFunction(Expr\ArrowFunction $function): void |
||||||
|
{ |
||||||
|
foreach ($function->params as $param) { |
||||||
|
if (CompileTimeAttribute::has($param, 'NotNull')) { |
||||||
|
throw new SyntaxError('NotNull is not supported on arrow function parameters'); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,103 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* This file is part of TypePHP. |
||||||
|
* |
||||||
|
* @link https://www.swoole.com/ |
||||||
|
* @contact service@swoole.com |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace TypePhp\Transform; |
||||||
|
|
||||||
|
use PhpParser\Modifiers; |
||||||
|
use PhpParser\Node; |
||||||
|
use PhpParser\Node\Expr; |
||||||
|
use PhpParser\Node\Stmt; |
||||||
|
use TypePhp\Exception\SyntaxError; |
||||||
|
|
||||||
|
final class PrinterLowering |
||||||
|
{ |
||||||
|
public const GENERATED_ATTRIBUTE = 'typephpPrinterGenerated'; |
||||||
|
|
||||||
|
public static function validateTarget(Node $node): void |
||||||
|
{ |
||||||
|
if (!CompileTimeAttribute::has($node, 'Printer')) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (!$node instanceof Stmt\Class_ || $node->name === null) { |
||||||
|
throw new SyntaxError('Printer can only be applied to named classes'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static function lowerClass(Stmt\Class_ $class, bool $generate = true): void |
||||||
|
{ |
||||||
|
if (!CompileTimeAttribute::consume($class, 'Printer') || !$generate) { |
||||||
|
return; |
||||||
|
} |
||||||
|
foreach ($class->getMethods() as $method) { |
||||||
|
if ($method->name->toLowerString() === 'tostring') { |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
self::appendGeneratedMethod($class, self::ownPublicProperties($class)); |
||||||
|
} |
||||||
|
|
||||||
|
/** @param list<string> $properties */ |
||||||
|
public static function rebuildGeneratedMethod(Stmt\Class_ $class, array $properties): void |
||||||
|
{ |
||||||
|
self::removeGeneratedMethod($class); |
||||||
|
self::appendGeneratedMethod($class, array_values(array_unique($properties))); |
||||||
|
} |
||||||
|
|
||||||
|
public static function removeGeneratedMethod(Stmt\Class_ $class): void |
||||||
|
{ |
||||||
|
foreach ($class->stmts as $index => $stmt) { |
||||||
|
if ($stmt instanceof Stmt\ClassMethod && $stmt->getAttribute(self::GENERATED_ATTRIBUTE)) { |
||||||
|
unset($class->stmts[$index]); |
||||||
|
} |
||||||
|
} |
||||||
|
$class->stmts = array_values($class->stmts); |
||||||
|
} |
||||||
|
|
||||||
|
/** @return list<string> */ |
||||||
|
public static function ownPublicProperties(Stmt\Class_ $class): array |
||||||
|
{ |
||||||
|
$properties = []; |
||||||
|
foreach ($class->stmts as $stmt) { |
||||||
|
if ($stmt instanceof Stmt\Property && $stmt->isPublic() && !$stmt->isStatic()) { |
||||||
|
foreach ($stmt->props as $property) { |
||||||
|
$properties[] = $property->name->toString(); |
||||||
|
} |
||||||
|
} |
||||||
|
if ($stmt instanceof Stmt\ClassMethod && $stmt->name->toLowerString() === '__construct') { |
||||||
|
foreach ($stmt->params as $param) { |
||||||
|
if ($param->isPromoted() && ($param->flags & Modifiers::PUBLIC) && is_string($param->var->name)) { |
||||||
|
$properties[] = $param->var->name; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return $properties; |
||||||
|
} |
||||||
|
|
||||||
|
/** @param list<string> $properties */ |
||||||
|
private static function appendGeneratedMethod(Stmt\Class_ $class, array $properties): void |
||||||
|
{ |
||||||
|
$expression = new Node\Scalar\String_($class->name->toString() . '('); |
||||||
|
foreach ($properties as $index => $property) { |
||||||
|
$prefix = ($index === 0 ? '' : ', ') . $property . '='; |
||||||
|
$expression = new Expr\BinaryOp\Concat( |
||||||
|
new Expr\BinaryOp\Concat($expression, new Node\Scalar\String_($prefix)), |
||||||
|
new Expr\PropertyFetch(new Expr\Variable('this'), $property), |
||||||
|
); |
||||||
|
} |
||||||
|
$expression = new Expr\BinaryOp\Concat($expression, new Node\Scalar\String_(')')); |
||||||
|
$method = new Stmt\ClassMethod('toString', [ |
||||||
|
'flags' => Modifiers::PUBLIC, |
||||||
|
'returnType' => new Node\Identifier('string'), |
||||||
|
'stmts' => [new Stmt\Return_($expression)], |
||||||
|
]); |
||||||
|
$method->setAttribute(self::GENERATED_ATTRIBUTE, true); |
||||||
|
$class->stmts[] = $method; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,114 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* This file is part of TypePHP. |
||||||
|
* |
||||||
|
* @link https://www.swoole.com/ |
||||||
|
* @contact service@swoole.com |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace TypePhp\Transform; |
||||||
|
|
||||||
|
use PhpParser\Modifiers; |
||||||
|
use PhpParser\Node; |
||||||
|
use PhpParser\Node\Expr; |
||||||
|
use PhpParser\Node\Param; |
||||||
|
use PhpParser\Node\Stmt; |
||||||
|
use TypePhp\Exception\SyntaxError; |
||||||
|
|
||||||
|
final class PropertyMethodLowering |
||||||
|
{ |
||||||
|
private const ATTRIBUTES = ['Setter', 'With']; |
||||||
|
|
||||||
|
public static function validateTarget(Node $node): void |
||||||
|
{ |
||||||
|
foreach (self::ATTRIBUTES as $attribute) { |
||||||
|
if (!CompileTimeAttribute::has($node, $attribute)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
if ($node instanceof Stmt\Property && !$node->isStatic()) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
if ($node instanceof Param && $node->isPromoted()) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
throw new SyntaxError($attribute . ' can only be applied to instance properties'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** @return list<Stmt\ClassMethod> */ |
||||||
|
public static function lowerProperty(Stmt\Property $property): array |
||||||
|
{ |
||||||
|
$setter = CompileTimeAttribute::consume($property, 'Setter'); |
||||||
|
$with = CompileTimeAttribute::consume($property, 'With'); |
||||||
|
if (!$setter && !$with) { |
||||||
|
return []; |
||||||
|
} |
||||||
|
|
||||||
|
$methods = []; |
||||||
|
foreach ($property->props as $prop) { |
||||||
|
$name = $prop->name->toString(); |
||||||
|
if ($setter) { |
||||||
|
$methods[] = self::createSetter($name, $property->type, $property->getAttributes()); |
||||||
|
} |
||||||
|
if ($with) { |
||||||
|
$methods[] = self::createWith($name, $property->type, $property->getAttributes()); |
||||||
|
} |
||||||
|
} |
||||||
|
return $methods; |
||||||
|
} |
||||||
|
|
||||||
|
/** @return list<Stmt\ClassMethod> */ |
||||||
|
public static function lowerPromotedProperty(Param $param): array |
||||||
|
{ |
||||||
|
if (!$param->isPromoted() || !is_string($param->var->name)) { |
||||||
|
return []; |
||||||
|
} |
||||||
|
$setter = CompileTimeAttribute::consume($param, 'Setter'); |
||||||
|
$with = CompileTimeAttribute::consume($param, 'With'); |
||||||
|
if (!$setter && !$with) { |
||||||
|
return []; |
||||||
|
} |
||||||
|
|
||||||
|
$methods = []; |
||||||
|
if ($setter) { |
||||||
|
$methods[] = self::createSetter($param->var->name, $param->type, $param->getAttributes()); |
||||||
|
} |
||||||
|
if ($with) { |
||||||
|
$methods[] = self::createWith($param->var->name, $param->type, $param->getAttributes()); |
||||||
|
} |
||||||
|
return $methods; |
||||||
|
} |
||||||
|
|
||||||
|
private static function createSetter(string $property, ?Node $type, array $attributes): Stmt\ClassMethod |
||||||
|
{ |
||||||
|
return new Stmt\ClassMethod('set' . ucfirst($property), [ |
||||||
|
'flags' => Modifiers::PUBLIC, |
||||||
|
'params' => [new Param(new Expr\Variable($property), type: $type === null ? null : clone $type)], |
||||||
|
'returnType' => new Node\Identifier('void'), |
||||||
|
'stmts' => [new Stmt\Expression(new Expr\Assign( |
||||||
|
new Expr\PropertyFetch(new Expr\Variable('this'), $property), |
||||||
|
new Expr\Variable($property), |
||||||
|
))], |
||||||
|
], $attributes); |
||||||
|
} |
||||||
|
|
||||||
|
private static function createWith(string $property, ?Node $type, array $attributes): Stmt\ClassMethod |
||||||
|
{ |
||||||
|
return new Stmt\ClassMethod('with' . ucfirst($property), [ |
||||||
|
'flags' => Modifiers::PUBLIC, |
||||||
|
'params' => [new Param(new Expr\Variable($property), type: $type === null ? null : clone $type)], |
||||||
|
'returnType' => new Node\Name('static'), |
||||||
|
'stmts' => [ |
||||||
|
new Stmt\Expression(new Expr\Assign( |
||||||
|
new Expr\Variable('clone'), |
||||||
|
new Expr\Clone_(new Expr\Variable('this')), |
||||||
|
)), |
||||||
|
new Stmt\Expression(new Expr\Assign( |
||||||
|
new Expr\PropertyFetch(new Expr\Variable('clone'), $property), |
||||||
|
new Expr\Variable($property), |
||||||
|
)), |
||||||
|
new Stmt\Return_(new Expr\Variable('clone')), |
||||||
|
], |
||||||
|
], $attributes); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue