/** * Executes this check * * @param xp.compiler.ast.Node node * @param xp.compiler.types.Scope scope * @return bool */ public function verify(\xp\compiler\ast\Node $node, \xp\compiler\types\Scope $scope) { $access = \cast($node, 'xp.compiler.ast.MemberAccessNode'); // Verify type // * var: Might have method // * primitive, array, map, int: Definitely don't have fields $type = $scope->typeOf($access->target); if ($type->isVariable()) { return ['T203', 'Member access (var).' . $access->name . '() verification deferred until runtime']; } else { if (!$type->isClass()) { return ['T305', 'Using member access on unsupported type ' . $type->compoundName()]; } } // Verify target method exists $target = new \xp\compiler\types\TypeInstance($scope->resolveType($type)); if ($target->hasField($access->name)) { $member = $target->getField($access->name); } else { if ($target->hasProperty($access->name)) { $member = $target->getProperty($access->name); } else { return ['T404', 'No such field $' . $access->name . ' in ' . $target->name()]; } } // Verify visibility if (!($member->modifiers & MODIFIER_PUBLIC)) { $enclosing = $scope->resolveType($scope->declarations[0]->name); if ($member->modifiers & MODIFIER_PRIVATE && !$enclosing->equals($target) || $member->modifiers & MODIFIER_PROTECTED && !($enclosing->equals($target) || $enclosing->isSubclassOf($target))) { return ['T403', sprintf('Accessing %s %s::$%s from %s', implode(' ', \lang\reflect\Modifiers::namesOf($member->modifiers)), $target->name(), $member->name, $enclosing->name())]; } } }
/** * Executes this check * * @param xp.compiler.ast.Node node * @param xp.compiler.types.Scope scope * @return bool */ public function verify(\xp\compiler\ast\Node $node, \xp\compiler\types\Scope $scope) { $routine = \cast($node, 'xp.compiler.ast.RoutineNode'); $qname = $scope->declarations[0]->name->compoundName() . '::' . $routine->getName(); $empty = $routine->body === null; if ($scope->declarations[0] instanceof \xp\compiler\ast\InterfaceNode) { if (!$empty) { return ['R403', 'Interface methods may not have a body ' . $qname]; } else { if ($routine->modifiers !== MODIFIER_PUBLIC && $routine->modifiers !== 0) { return ['R401', 'Interface methods may only be public ' . $qname]; } } } else { if (Modifiers::isAbstract($routine->modifiers) && !$empty) { return ['R403', 'Abstract methods may not have a body ' . $qname]; } else { if (!Modifiers::isAbstract($routine->modifiers) && $empty) { return ['R401', 'Non-abstract methods must have a body ' . $qname]; } } if ($routine->extension && !Modifiers::isStatic($routine->modifiers)) { return ['E403', 'Extension methods must be static ' . $qname]; } } }
/** * Resolves a class member, which is either a field, a class constant * or the `ClassName::class` syntax, which returns the class' literal. * * @param lang.XPClass $class * @param var[] $token A token as returned by `token_get_all()` * @param string $context * @return var */ protected function memberOf($class, $token, $context) { if (T_VARIABLE === $token[0]) { $field = $class->getField(substr($token[1], 1)); $m = $field->getModifiers(); if ($m & MODIFIER_PUBLIC) { return $field->get(null); } else { if ($m & MODIFIER_PROTECTED && $class->isAssignableFrom($context)) { return $field->setAccessible(true)->get(null); } else { if ($m & MODIFIER_PRIVATE && $class->getName() === $context) { return $field->setAccessible(true)->get(null); } else { throw new IllegalAccessException(sprintf('Cannot access %s field %s::$%s', implode(' ', Modifiers::namesOf($m)), $class->getName(), $field->getName())); } } } } else { if (T_CLASS === $token[0]) { return $class->literal(); } else { return $class->getConstant($token[1]); } } }
/** * Creates a string representation of this Operator * * @return string */ public function toString() { $signature = ''; foreach ($this->parameters as $parameter) { $signature .= ', ' . $parameter->compoundName(); } return sprintf('%s<%s %s %s(%s)>', nameof($this), implode(' ', \lang\reflect\Modifiers::namesOf($this->modifiers)), $this->returns->compoundName(), $this->symbol, substr($signature, 2)); }
/** * Creates a string representation of this method * * @return string */ public function toString() { $signature = ''; foreach ($this->parameters as $parameter) { $signature .= ', ' . $parameter->toString(); } return sprintf('%s<%s __construct(%s)>', nameof($this), implode(' ', \lang\reflect\Modifiers::namesOf($this->modifiers)), substr($signature, 2)); }
/** * Provide tests from a given package to the test suite. Handles recursion. * * @param lang.reflect.Package $package * @param unittest.TestSuite $suite * @param var[] $arguments * @return void */ private function provideFrom($package, $suite, $arguments) { foreach ($package->getClasses() as $class) { if ($class->isSubclassOf(TestCase::class) && !Modifiers::isAbstract($class->getModifiers())) { $suite->addTestClass($class, $arguments); } } if ($this->recursive) { foreach ($package->getPackages() as $package) { $this->provideFrom($package, $suite, $arguments); } } }
/** * Returns a list of all classes inside a given package * * @param lang.reflect.Package * @param bool recursive whether to include subpackages * @return lang.XPClass[] */ protected static function testClassesIn(Package $package, $recursive) { $r = array(); foreach ($package->getClasses() as $class) { if (!$class->isSubclassOf('unittest.TestCase') || Modifiers::isAbstract($class->getModifiers())) { continue; } $r[] = $class; } if ($recursive) { foreach ($package->getPackages() as $package) { $r = array_merge($r, self::testClassesIn($package, $recursive)); } } return $r; }
/** * Executes this check * * @param xp.compiler.types.TypeName * @param string name method name * @param xp.compiler.types.Scope scope * @return string[] error or null */ protected function verifyMethod($type, $name, $scope) { // Verify target method exists $target = new \xp\compiler\types\TypeInstance($scope->resolveType($type)); if (!$target->hasMethod($name)) { return ['T404', 'No such method ' . $name . '() in ' . $target->name()]; } // Verify visibility $method = $target->getMethod($name); if (!($method->modifiers & MODIFIER_PUBLIC)) { $enclosing = $scope->resolveType($scope->declarations[0]->name); if ($method->modifiers & MODIFIER_PRIVATE && !$enclosing->equals($target) || $method->modifiers & MODIFIER_PROTECTED && !($enclosing->equals($target) || $enclosing->isSubclassOf($target))) { return ['T403', sprintf('Invoking %s %s::%s() from %s', implode(' ', \lang\reflect\Modifiers::namesOf($method->modifiers)), $target->name(), $method->name, $enclosing->name())]; } } }
/** * Provide tests to test suite * * @param unittest.TestSuite $suite * @param var[] $arguments * @return void * @throws lang.IllegalArgumentException if no tests are found */ public function provideTo($suite, $arguments) { $it = new FilteredIOCollectionIterator(new FileCollection($this->loader->path), new ExtensionEqualsFilter(\xp::CLASS_FILE_EXT), true); $l = strlen($this->loader->path); $e = -strlen(\xp::CLASS_FILE_EXT); $empty = true; foreach ($it as $element) { $class = $this->loader->loadClass(strtr(substr($element->getUri(), $l, $e), DIRECTORY_SEPARATOR, '.')); if ($class->isSubclassOf(TestCase::class) && !Modifiers::isAbstract($class->getModifiers())) { $suite->addTestClass($class, $arguments); $empty = false; } } if ($empty) { throw new IllegalArgumentException('Cannot find any test cases in ' . $this->loader->toString()); } }
/** * Uses the constructor represented by this Constructor object to create * and initialize a new instance of the constructor's declaring class, * with the specified initialization parameters. * * Example: * <code> * $constructor= XPClass::forName('util.Binford')->getConstructor(); * * $instance= $constructor->newInstance(); * $instance= $constructor->newInstance([6100]); * </code> * * @param var[] args * @return lang.Generic * @throws lang.IllegalAccessException in case the constructor is not public or if it is abstract * @throws lang.reflect.TargetInvocationException in case the constructor throws an exception */ public function newInstance(array $args = []) { $class = new \ReflectionClass($this->_class); if ($class->isAbstract()) { throw new IllegalAccessException('Cannot instantiate abstract class ' . XPClass::nameOf($this->_class)); } // Check modifiers. If caller is an instance of this class, allow private and // protected constructor invocation (which the PHP reflection API does not). $m = $this->_reflect->getModifiers(); $public = $m & MODIFIER_PUBLIC; if (!$public && !$this->accessible) { $t = debug_backtrace(0, 2); $decl = $this->_reflect->getDeclaringClass()->getName(); if ($m & MODIFIER_PROTECTED) { $allow = $t[1]['class'] === $decl || is_subclass_of($t[1]['class'], $decl); } else { $allow = $t[1]['class'] === $decl; } if (!$allow) { throw new IllegalAccessException(sprintf('Cannot invoke %s constructor of class %s from scope %s', Modifiers::stringOf($this->getModifiers()), XPClass::nameOf($this->_class), $t[1]['class'])); } } // For non-public constructors: Use setAccessible() / invokeArgs() combination try { if ($public) { return $class->newInstanceArgs($args); } $instance = unserialize('O:' . strlen($this->_class) . ':"' . $this->_class . '":0:{}'); $this->_reflect->setAccessible(true); $this->_reflect->invokeArgs($instance, $args); return $instance; } catch (\lang\SystemExit $e) { throw $e; } catch (\lang\Throwable $e) { throw new TargetInvocationException(XPClass::nameOf($this->_class) . '::<init>', $e); } catch (\Exception $e) { throw new TargetInvocationException(XPClass::nameOf($this->_class) . '::<init>', new \lang\XPException($e->getMessage())); } catch (\BaseException $e) { throw new TargetInvocationException(XPClass::nameOf($this->_class) . '::' . $this->_reflect->getName(), new \lang\Error($e->getMessage())); } }
/** * Invokes the underlying method represented by this Method object, * on the specified object with the specified parameters. * * Example: * ```php * $method= XPClass::forName('lang.Object')->getMethod('toString'); * $str= $method->invoke(new Object()); * ``` * * Example (passing arguments) * ```php * $method= XPClass::forName('lang.types.String')->getMethod('concat'); * $str= $method->invoke(new String('Hello'), ['World']); * ``` * * Example (static invokation): * ```php * $method= XPClass::forName('util.log.Logger')->getMethod('getInstance'); * $log= $method->invoke(null); * ``` * * @param lang.Object $obj * @param var[] $args default [] * @return var * @throws lang.IllegalArgumentException in case the passed object is not an instance of the declaring class * @throws lang.IllegalAccessException in case the method is not public or if it is abstract * @throws lang.reflect.TargetInvocationException for any exception raised from the invoked method */ public function invoke($obj, $args = []) { if (null !== $obj && !$obj instanceof $this->_class) { throw new IllegalArgumentException(sprintf('Passed argument is not a %s class (%s)', XPClass::nameOf($this->_class), \xp::typeOf($obj))); } // Check modifiers. If caller is an instance of this class, allow // protected method invocation (which the PHP reflection API does // not). $m = $this->_reflect->getModifiers(); if ($m & MODIFIER_ABSTRACT) { throw new IllegalAccessException(sprintf('Cannot invoke abstract %s::%s', XPClass::nameOf($this->_class), $this->_reflect->getName())); } $public = $m & MODIFIER_PUBLIC; if (!$public && !$this->accessible) { $t = debug_backtrace(0, 2); $decl = $this->_reflect->getDeclaringClass()->getName(); if ($m & MODIFIER_PROTECTED) { $allow = $t[1]['class'] === $decl || is_subclass_of($t[1]['class'], $decl); } else { $allow = $t[1]['class'] === $decl; } if (!$allow) { throw new IllegalAccessException(sprintf('Cannot invoke %s %s::%s from scope %s', Modifiers::stringOf($this->getModifiers()), XPClass::nameOf($this->_class), $this->_reflect->getName(), $t[1]['class'])); } } try { if (!$public) { $this->_reflect->setAccessible(true); } return $this->_reflect->invokeArgs($obj, (array) $args); } catch (\lang\SystemExit $e) { throw $e; } catch (\lang\Throwable $e) { throw new TargetInvocationException(XPClass::nameOf($this->_class) . '::' . $this->_reflect->getName(), $e); } catch (\Exception $e) { throw new TargetInvocationException(XPClass::nameOf($this->_class) . '::' . $this->_reflect->getName(), new \lang\XPException($e->getMessage())); } catch (\Throwable $e) { throw new TargetInvocationException(XPClass::nameOf($this->_class) . '::' . $this->_reflect->getName(), new \lang\Error($e->getMessage())); } }
/** * Get all test cases * * @param var[] arguments * @return unittest.TestCase[] */ public function testCasesWith($arguments) { if (null === ($cl = $this->findLoaderFor($this->folder->getURI()))) { throw new IllegalArgumentException($this->folder->toString() . ' is not in class path'); } $l = strlen($cl->path); $e = -strlen(\xp::CLASS_FILE_EXT); $it = new FilteredIOCollectionIterator(new FileCollection($this->folder), new ExtensionEqualsFilter(\xp::CLASS_FILE_EXT), true); $cases = []; foreach ($it as $element) { $name = strtr(substr($element->getUri(), $l, $e), DIRECTORY_SEPARATOR, '.'); $class = XPClass::forName($name); if (!$class->isSubclassOf('unittest.TestCase') || Modifiers::isAbstract($class->getModifiers())) { continue; } $cases = array_merge($cases, $this->testCasesInClass($class, $arguments)); } if (empty($cases)) { throw new IllegalArgumentException('Cannot find any test cases in ' . $this->folder->toString()); } return $cases; }
/** * Retrieve string representation. Examples: * * <pre> * public lang.XPClass getClass() * public static util.Date now() * public open(string $mode) throws io.FileNotFoundException, io.IOException * </pre> * * @return string */ public function toString() { $signature = ''; foreach ($this->getParameters() as $param) { if ($param->isOptional()) { $signature .= ', [' . $param->getTypeName() . ' $' . $param->getName() . '= ' . str_replace("\n", ' ', \xp::stringOf($param->getDefaultValue())) . ']'; } else { $signature .= ', ' . $param->getTypeName() . ' $' . $param->getName(); } } if ($exceptions = $this->getExceptionNames()) { $throws = ' throws ' . implode(', ', $exceptions); } else { $throws = ''; } return sprintf('%s %s %s(%s)%s', Modifiers::stringOf($this->getModifiers()), $this->getReturnTypeName(), $this->getName(), substr($signature, 2), $throws); }
/** * Creates a string representation of this field * * @return string */ public function toString() { return sprintf('%s<%s %s $%s>', nameof($this), implode(' ', \lang\reflect\Modifiers::namesOf($this->modifiers)), $this->type->compoundName(), $this->name); }
public function staticModifierNames() { $this->assertEquals(array('public', 'static'), \lang\reflect\Modifiers::namesOf(MODIFIER_STATIC)); }
/** * Resolve a static call. Return true if the target is a function * (e.g. key()), a xp.compiler.types.Method instance if it's a static * method (Map::key()). * * @param string name * @return var */ public function resolveStatic($name) { foreach ($this->statics[0] as $lookup => $type) { if (true === $type && $this->importer->hasFunction($lookup, $name)) { return true; } else { if ($type instanceof Types && $type->hasMethod($name)) { $m = $type->getMethod($name); if (\lang\reflect\Modifiers::isStatic($m->modifiers)) { return $m; } } } } return null; }
/** * Lists commands * * @return void */ protected function listCommands() { $commandsIn = function ($package) { $text = ''; foreach ($package->getClasses() as $class) { if ($class->isSubclassOf('util.cmd.Command') && !Modifiers::isAbstract($class->getModifiers())) { $text .= ' $ xpcli ' . $class->getSimpleName() . "\n"; } } return $text ?: ' (no commands)'; }; self::$err->writeLine('Named commands'); self::$err->writeLine(); if ($packages = Commands::allPackages()) { foreach (Commands::allPackages() as $package) { self::$err->writeLine('* ', $package); self::$err->writeLine($commandsIn($package)); } self::$err->writeLine(); } self::$err->writeLine('* Global package'); self::$err->writeLine($commandsIn(Package::forName(null))); }
/** * Generates code for (re)implementation of the (abstract) class methods * of the base class. * * @param lang.XPClass baseClass */ private function generateBaseClassMethods($baseClass) { $bytes = ''; $reservedMethods = \lang\XPClass::forName('lang.Generic')->getMethods(); $reservedMethodNames = array_map(function ($i) { return $i->getName(); }, $reservedMethods); foreach ($baseClass->getMethods() as $m) { // do not overwrite reserved methods, omit static methods if (in_array($m->getName(), $reservedMethodNames) || Modifiers::isStatic($m->getModifiers())) { continue; } // Check for already declared methods, do not redeclare them // implement abstract methods if ($this->overwriteExisting || ($m->getModifiers() & 2) == 2) { if (isset($this->added[$m->getName()])) { continue; } $this->added[$m->getName()] = true; $bytes .= $this->generateMethod($m); } } return $bytes; }
public function staticModifierNames() { $this->assertEquals(['public', 'static'], Modifiers::namesOf(MODIFIER_STATIC)); }
/** * Lists commands * * @return void */ protected function listCommands() { $commandsIn = function ($package) { $markdown = ''; foreach ($package->getClasses() as $class) { if ($class->isSubclassOf('util.cmd.Command') && !Modifiers::isAbstract($class->getModifiers())) { $markdown .= ' $ xp cmd ' . $class->getSimpleName() . "\n"; } } return $markdown ?: ' *(no commands)*'; }; $markdown = "# Named commands\n\n"; if ($packages = Commands::allPackages()) { foreach ($packages as $package) { $markdown .= '* In package **' . $package->getName() . "**\n\n" . $commandsIn($package); } $markdown .= "\n"; } $markdown .= "* In global package\n\n" . $commandsIn(Package::forName(null)); Help::render(self::$err, $markdown, []); }
/** * Creates a string representation of this field * * @return string */ public function toString() { return sprintf('%s %s %s::$%s', Modifiers::stringOf($this->getModifiers()), $this->getTypeName(), $this->getDeclaringClass()->getName(), $this->getName()); }
/** * Prints package * * @param lang.reflect.Package package */ protected static function printPackage($package) { Console::writeLine('package ', $package->getName(), ' {'); // Child packages foreach ($package->getPackages() as $child) { Console::writeLine(' package ', $child->getName()); } // Classes $order = array('interface' => array(), 'enum' => array(), 'class' => array()); foreach ($package->getClasses() as $class) { $mod = $class->getModifiers(); if ($class->isInterface()) { $type = 'interface'; $mod = $mod ^ MODIFIER_ABSTRACT; } else { if ($class->isEnum()) { $type = 'enum'; } else { $type = 'class'; } } $name = self::displayNameOf($class); $order[$type][] = implode(' ', Modifiers::namesOf($mod)) . ' ' . $type . ' ' . self::displayNameOf($class); } foreach ($order as $type => $classes) { if (empty($classes)) { continue; } Console::writeLine(); sort($classes); foreach ($classes as $name) { Console::writeLine(' ', $name); } } Console::writeLine('}'); }
protected function assertProperty($modifiers, $name, $type, $actual) { $this->assertEquals(['modifiers' => Modifiers::namesOf($modifiers), 'name' => $name, 'type' => $type], ['modifiers' => Modifiers::namesOf($actual->modifiers), 'name' => $actual->name, 'type' => $actual->type]); }
public function is_abstract() { $this->assertTrue(\lang\reflect\Modifiers::isAbstract(self::$fixture->getModifiers())); }
/** * Asserts given modifiers do not contain abstract * * @param int $modifiers * @return void * @throws unittest.AssertionFailedError */ protected function assertNotAbstract($modifiers) { $this->assertFalse(Modifiers::isAbstract($modifiers), implode(' | ', Modifiers::namesOf($modifiers))); }
/** * Convert value based on type * * @param lang.Type type * @param [:var] value * @return var */ public function unmarshal($type, $value) { if (null === $type || $type->equals(Type::$VAR)) { // No conversion return $value; } else { if (null === $value) { // Valid for any type return null; } else { if ($type instanceof Primitive) { return $type->cast($this->valueOf($value)); } else { if ($type->equals(XPClass::forName('util.Date'))) { return $type->newInstance($value); } else { if ($type instanceof XPClass) { if ($type->isInstance($value)) { return $value; } foreach ($this->marshallers->keys() as $t) { if ($t->isAssignableFrom($type)) { return $this->marshallers[$t]->unmarshal($type, $value, $this); } } // Check if a public static valueOf() method exists if ($type->hasMethod('valueOf')) { $valueOf = $type->getMethod('valueOf'); if (Modifiers::isStatic($valueOf->getModifiers()) && Modifiers::isPublic($valueOf->getModifiers())) { if (1 === $valueOf->numParameters()) { return $valueOf->invoke(null, [$this->unmarshal($this->paramType($valueOf->getParameter(0)), $value)]); } else { $param = 0; $args = []; foreach ($this->iterableOf($value) as $value) { $args[] = $this->unmarshal($this->paramType($valueOf->getParameter($param++)), $value); } return $valueOf->invoke(null, $args); } } } // Generic approach $return = $type->newInstance(); foreach ($this->iterableOf($value) as $name => $value) { foreach ($this->variantsOf($name) as $variant) { if ($type->hasField($variant)) { $field = $type->getField($variant); $m = $field->getModifiers(); if ($m & MODIFIER_STATIC) { continue; } else { if ($m & MODIFIER_PUBLIC) { if (null !== ($fType = $field->getType())) { $field->set($return, $this->unmarshal($fType, $value)); } else { $field->set($return, $value); } continue 2; } } } if ($type->hasMethod('set' . $variant)) { $method = $type->getMethod('set' . $variant); if ($method->getModifiers() & MODIFIER_PUBLIC) { if (null !== ($param = $method->getParameter(0))) { $method->invoke($return, [$this->unmarshal($param->getType(), $value)]); } else { $method->invoke($return, [$value]); } continue 2; } } } } return $return; } else { if ($type instanceof \lang\ArrayType) { $return = []; foreach ($this->iterableOf($value) as $element) { $return[] = $this->unmarshal($type->componentType(), $element); } return $return; } else { if ($type instanceof \lang\MapType) { $return = []; foreach ($this->iterableOf($value) as $key => $element) { $return[$key] = $this->unmarshal($type->componentType(), $element); } return $return; } else { if ($type->equals(Type::$ARRAY)) { $return = []; foreach ($this->iterableOf($value) as $key => $element) { $return[$key] = $element; } return $return; } else { throw new \lang\FormatException('Cannot convert to ' . \xp::stringOf($type)); } } } } } } } } }
/** * Emit an enum declaration * * Basic form: * <code> * public enum Day { MON, TUE, WED, THU, FRI, SAT, SUN } * </code> * * With values: * <code> * public enum Coin { penny(1), nickel(2), dime(10), quarter(25) } * </code> * * Abstract: * <code> * public abstract enum Operation { * plus { * public int evaluate(int $x, int $y) { return $x + $y; } * }, * minus { * public int evaluate(int $x, int $y) { return $x - $y; } * }; * * public abstract int evaluate(int $x, int $y); * } * </code> * * @param xp.compiler.emit.Buffer b * @param xp.compiler.ast.EnumNode declaration */ protected function emitEnum($b, $declaration) { $parent = $declaration->parent ?: new TypeName('lang.Enum'); $parentType = $this->resolveType($parent); $thisType = new TypeDeclaration(new ParseTree($this->scope[0]->package, [], $declaration), $parentType); $this->scope[0]->addResolved('self', $thisType); $this->scope[0]->addResolved('parent', $parentType); // FIXME: ??? $this->scope[0]->addResolved($declaration->name->name, $thisType); $this->scope[0]->imports[$declaration->name->name] = $declaration->name->name; $this->enter(new TypeDeclarationScope()); // Ensure parent class and interfaces are loaded $this->emitTypeName($b, 'class', $declaration); $b->append(' extends ' . $this->literal($parentType, true)); array_unshift($this->metadata, [[], []]); array_unshift($this->properties, ['get' => [], 'set' => []]); $abstract = Modifiers::isAbstract($declaration->modifiers); // Meta data $this->metadata[0]['class'] = $this->meta($declaration->comment, $declaration->annotations, []); // Generics if ($declaration->name->isGeneric()) { $this->metadata[0]['class'][DETAIL_ANNOTATIONS]['generic']['self'] = $this->genericComponentAsMetadata($declaration->name); } if ($parent->isGeneric()) { $this->metadata[0]['class'][DETAIL_ANNOTATIONS]['generic']['parent'] = $this->genericComponentAsMetadata($parent); } // Interfaces if ($declaration->implements) { $b->append(' implements '); $s = sizeof($declaration->implements) - 1; foreach ($declaration->implements as $i => $type) { if ($type->isGeneric()) { $this->metadata[0]['class'][DETAIL_ANNOTATIONS]['generic']['implements'][$i] = $this->genericComponentAsMetadata($type); } $b->append($this->resolveType($type)->literal(true)); $i < $s && $b->append(', '); } } // Member declaration $b->append(' {'); // public static self[] values() { return parent::membersOf(__CLASS__) } $declaration->body[] = new MethodNode(['modifiers' => MODIFIER_PUBLIC | MODIFIER_STATIC, 'annotations' => null, 'name' => 'values', 'returns' => new TypeName('self[]'), 'parameters' => null, 'throws' => null, 'body' => [new ReturnNode(new StaticMethodCallNode(new TypeName('parent'), 'membersOf', [new StringNode($this->literal($thisType))]))], 'comment' => '(Generated)']); // Members foreach ((array) $declaration->body as $node) { $this->emitOne($b, $node); } $this->emitProperties($b, $this->properties[0]); // Initialization $b->append('static function __static() {'); foreach ($declaration->body as $i => $member) { if (!$member instanceof EnumMemberNode) { continue; } $b->append('self::$' . $member->name . '= '); if ($member->body) { if (!$abstract) { $this->error('E403', 'Only abstract enums can contain members with bodies (' . $member->name . ')'); // Continues so declaration is closed } $unique = new TypeName($declaration->name->name . '··' . $member->name); $decl = new ClassNode(0, null, $unique, $declaration->name, [], $member->body); $decl->synthetic = true; $ptr = new TypeDeclaration(new ParseTree(null, [], $decl), $thisType); $this->scope[0]->declarations[] = $decl; $b->append('new ' . $unique->name . '('); } else { $b->append('new self('); } if ($member->value) { $this->emitOne($b, $member->value); } else { $b->append($i); } $b->append(', \'' . $member->name . '\');'); } $b->append('}'); // Finish $b->append('}'); $this->leave(); $this->registerClass($b, $declaration, $thisType->name()); array_shift($this->properties); array_shift($this->metadata); // Register type info $this->types[0]->name = $thisType->name(); $this->types[0]->kind = Types::ENUM_KIND; $this->types[0]->literal = $this->declaration($declaration); $this->types[0]->parent = $parentType; $this->types[0]->modifiers = $declaration->modifiers; }