diff --git a/src/main/php/lang/FunctionType.class.php b/src/main/php/lang/FunctionType.class.php index 9c1de98dc..9d901afe2 100755 --- a/src/main/php/lang/FunctionType.class.php +++ b/src/main/php/lang/FunctionType.class.php @@ -1,5 +1,7 @@ getName()); + $details= XPClass::detailsForClass(XPClass::nameOf($class->name))[1][$r->name] ?? null; $resolve= [ 'static' => function() use($class) { return new XPClass($class); }, 'self' => function() use($class) { return new XPClass($class); }, @@ -303,7 +305,7 @@ public function isAssignableFrom($type): bool { * @param var[] $args * @return var * @throws lang.IllegalArgumentException in case the passed function is not an instance of this type - * @throws lang.reflect.TargetInvocationException for any exception raised from the invoked function + * @throws lang.Throwable for any exception raised from the invoked function */ public function invoke($func, $args= []) { $closure= $this->verified($func, function($m) use($func) { throw new IllegalArgumentException(sprintf( @@ -314,8 +316,8 @@ public function invoke($func, $args= []) { )); }); try { return $closure(...$args); - } catch (Throwable $e) { - throw new \lang\reflect\TargetInvocationException($this->getName(), $e); + } catch (Any $e) { + throw Throwable::wrap($e); } } } diff --git a/src/main/php/lang/GenericTypes.class.php b/src/main/php/lang/GenericTypes.class.php index d813a59a8..219e563ff 100755 --- a/src/main/php/lang/GenericTypes.class.php +++ b/src/main/php/lang/GenericTypes.class.php @@ -28,7 +28,8 @@ public function newType(XPClass $base, array $arguments) { public function newType0($base, $arguments) { // Verify - $annotations= $base->getAnnotations(); + $details= XPClass::detailsForClass($base->getName()); + $annotations= $details ? $details['class'][DETAIL_ANNOTATIONS] : []; if (!isset($annotations['generic']['self'])) { throw new IllegalStateException('Class '.$base->name.' is not a generic definition'); } diff --git a/src/main/php/lang/XPClass.class.php b/src/main/php/lang/XPClass.class.php index 751ba7b38..260762980 100755 --- a/src/main/php/lang/XPClass.class.php +++ b/src/main/php/lang/XPClass.class.php @@ -26,15 +26,6 @@ * $instance= XPClass::forName('util.Binford')->newInstance(); * ``` * - * Invoke a method by its name: - * ```php - * try { - * typeof($instance)->getMethod('connect')->invoke($instance); - * } catch (TargetInvocationException $e) { - * $e->getCause()->printStackTrace(); - * } - * ``` - * * @see xp://lang.XPClass#forName * @test xp://net.xp_framework.unittest.reflection.XPClassTest * @test xp://net.xp_framework.unittest.reflection.ClassDetailsTest @@ -188,195 +179,6 @@ public function newInstance(... $args) { throw new IllegalAccessException($e->getMessage(), $e); } } - - /** - * Gets class methods for this class - * - * @return lang.reflect.Method[] - */ - public function getMethods() { - $list= []; - foreach ($this->reflect()->getMethods() as $m) { - if (0 == strncmp('__', $m->getName(), 2)) continue; - $list[]= new Method($this->_class, $m); - } - return $list; - } - - /** - * Gets class methods declared by this class - * - * @return lang.reflect.Method[] - */ - public function getDeclaredMethods() { - $list= []; - $reflect= $this->reflect(); - foreach ($reflect->getMethods() as $m) { - if (0 == strncmp('__', $m->getName(), 2) || $m->class !== $reflect->name) continue; - $list[]= new Method($this->_class, $m); - } - return $list; - } - - /** - * Gets a method by a specified name. - * - * @param string name - * @return lang.reflect.Method - * @see xp://lang.reflect.Method - * @throws lang.ElementNotFoundException - */ - public function getMethod($name): Method { - if ($this->hasMethod($name)) { - return new Method($this->_class, $this->reflect()->getMethod($name)); - } - throw new ElementNotFoundException('No such method "'.$name.'" in class '.$this->name); - } - - /** - * Checks whether this class has a method named "$method" or not. - * - * Note - * ==== - * Since in PHP, methods are case-insensitive, calling hasMethod('toString') - * will provide the same result as hasMethod('tostring') - * - * @param string method the method's name - * @return bool TRUE if method exists - */ - public function hasMethod($method): bool { - return ((0 === strncmp('__', $method, 2)) - ? false - : $this->reflect()->hasMethod($method) - ); - } - - /** - * Retrieve if a constructor exists - * - * @return bool - */ - public function hasConstructor(): bool { - return $this->reflect()->hasMethod('__construct'); - } - - /** - * Retrieves this class' constructor. - * - * @return lang.reflect.Constructor - * @see xp://lang.reflect.Constructor - * @throws lang.ElementNotFoundException - */ - public function getConstructor(): Constructor { - if ($this->hasConstructor()) { - return new Constructor($this->_class, $this->reflect()->getMethod('__construct')); - } - throw new ElementNotFoundException('No constructor in class '.$this->name); - } - - /** - * Returns virtual properties - * - * @param ReflectionClass $reflect - * @param bool $parents - * @return [:var][] - */ - private function virtual($reflect, $parents= true) { - $r= []; - do { - - // If meta information is already loaded, use property arguments - if ($meta= \xp::$meta[self::nameOf($reflect->name)][0] ?? null) { - foreach ($meta as $name => $property) { - if ($arg= $property[DETAIL_ARGUMENTS] ?? null) { - $r[$name]= [(int)$arg[0], $property[DETAIL_RETURNS] ?? 'mixed']; - } - } - continue; - } - - // Parse doc comment - $comment= $reflect->getDocComment(); - if (null === $comment) continue; - - preg_match_all('/@property(\-read|\-write)? (.+) \$([^ ]+)/', $comment, $matches, PREG_SET_ORDER); - $r= []; - foreach ($matches as $match) { - $r[$match[3]]= ['-read' === $match[1] ? MODIFIER_READONLY : 0, $match[2]]; - } - } while ($parents && ($reflect= $reflect->getParentclass())); - - return $r; - } - - /** - * Retrieve a list of all member variables - * - * @return lang.reflect.Field[] - */ - public function getFields() { - $reflect= $this->reflect(); - - $f= []; - foreach ($reflect->getProperties() as $p) { - if ('__id' === $p->name) continue; - $f[]= new Field($this->_class, $p); - } - foreach ($this->virtual($reflect) as $name => $meta) { - $f[]= new Field($this->_class, new VirtualProperty($reflect, $name, $meta)); - } - return $f; - } - - /** - * Retrieve a list of member variables declared in this class - * - * @return lang.reflect.Field[] - */ - public function getDeclaredFields() { - $reflect= $this->reflect(); - - $f= []; - foreach ($reflect->getProperties() as $p) { - if ('__id' === $p->name || $p->class !== $reflect->name) continue; - $f[]= new Field($this->_class, $p); - } - foreach ($this->virtual($reflect, false) as $name => $meta) { - $f[]= new Field($this->_class, new VirtualProperty($reflect, $name, $meta)); - } - return $f; - } - - /** - * Retrieve a field by a specified name. - * - * @param string $name - * @return lang.reflect.Field - * @throws lang.ElementNotFoundException - */ - public function getField($name): Field { - $reflect= $this->reflect(); - if ($reflect->hasProperty($name)) { - return new Field($this->_class, $reflect->getProperty($name)); - } else if ($meta= $this->virtual($reflect)[$name] ?? null) { - return new Field($this->_class, new VirtualProperty($reflect, $name, $meta)); - } - - throw new ElementNotFoundException('No such field "'.$name.'" in class '.$this->name); - } - - /** - * Checks whether this class has a field with a given name - * - * @param string $name - * @return bool TRUE if field exists - */ - public function hasField($name): bool { - return '__id' === $name ? false : ($reflect= $this->reflect()) && - $reflect->hasProperty($name) || - isset($this->virtual($reflect)[$name]) - ; - } /** * Retrieve the parent class's class object. Returns NULL if there @@ -387,39 +189,6 @@ public function hasField($name): bool { public function getParentclass() { return ($parent= $this->reflect()->getParentClass()) ? new self($parent) : null; } - - /** - * Checks whether this class has a constant named "$constant" or not - * - * @param string constant - * @return bool - */ - public function hasConstant($constant): bool { - return $this->reflect()->hasConstant($constant); - } - - /** - * Retrieve a constant by a specified name. - * - * @param string constant - * @return var - * @throws lang.ElementNotFoundException in case constant does not exist - */ - public function getConstant($constant) { - if ($this->hasConstant($constant)) { - return $this->reflect()->getConstant($constant); - } - throw new ElementNotFoundException('No such constant "'.$constant.'" in class '.$this->name); - } - - /** - * Retrieve class constants - * - * @return [:var] - */ - public function getConstants() { - return $this->reflect()->getConstants(); - } /** * Cast a given object to the class represented by this object @@ -611,61 +380,6 @@ public function getModifiers(): int { return $r; } - /** - * Check whether an annotation exists - * - * @param string name - * @param string key default NULL - * @return bool - */ - public function hasAnnotation($name, $key= null): bool { - $details= self::detailsForClass($this->name); - - return $details && ($key - ? array_key_exists($key, $details['class'][DETAIL_ANNOTATIONS][$name] ?? []) - : array_key_exists($name, $details['class'][DETAIL_ANNOTATIONS] ?? []) - ); - } - - /** - * Retrieve annotation by name - * - * @param string name - * @param string key default NULL - * @return var - * @throws lang.ElementNotFoundException - */ - public function getAnnotation($name, $key= null) { - $details= self::detailsForClass($this->name); - if (!$details || !($key - ? array_key_exists($key, $details['class'][DETAIL_ANNOTATIONS][$name] ?? []) - : array_key_exists($name, $details['class'][DETAIL_ANNOTATIONS] ?? []) - )) { - throw new ElementNotFoundException('Annotation "'.$name.($key ? '.'.$key : '').'" does not exist'); - } - - return ($key - ? $details['class'][DETAIL_ANNOTATIONS][$name][$key] - : $details['class'][DETAIL_ANNOTATIONS][$name] - ); - } - - /** Retrieve whether a method has annotations */ - public function hasAnnotations(): bool { - $details= self::detailsForClass($this->name); - return $details ? !empty($details['class'][DETAIL_ANNOTATIONS]) : false; - } - - /** - * Retrieve all of a method's annotations - * - * @return array annotations - */ - public function getAnnotations() { - $details= self::detailsForClass($this->name); - return $details ? $details['class'][DETAIL_ANNOTATIONS] : []; - } - /** Retrieve the class loader a class was loaded with */ public function getClassLoader(): IClassLoader { return self::_classLoaderFor($this->name); @@ -706,42 +420,6 @@ public static function detailsForClass($class) { return \xp::$meta[$class]= $parser->parseDetails($bytes); } - /** - * Retrieve details for a specified class and method. Note: Results - * from this method are cached! - * - * @param php.ReflectionClass $class - * @param string $method - * @return array or NULL if not available - */ - public static function detailsForMethod($class, $method) { - $details= self::detailsForClass(self::nameOf($class->name)); - if (isset($details[1][$method])) return $details[1][$method]; - foreach ($class->getTraitNames() as $trait) { - $details= self::detailsForClass(self::nameOf($trait)); - if (isset($details[1][$method])) return $details[1][$method]; - } - return null; - } - - /** - * Retrieve details for a specified class and field. Note: Results - * from this method are cached! - * - * @param php.ReflectionClass $class - * @param string method - * @return array or NULL if not available - */ - public static function detailsForField($class, $field) { - $details= self::detailsForClass(self::nameOf($class->name)); - if (isset($details[0][$field])) return $details[0][$field]; - foreach ($class->getTraitNames() as $trait) { - $details= self::detailsForClass(self::nameOf($trait)); - if (isset($details[0][$field])) return $details[0][$field]; - } - return null; - } - /** * Reflectively creates a new type * @@ -769,8 +447,11 @@ public function genericComponents() { if (!$this->isGenericDefinition()) { throw new IllegalStateException('Class '.$this->name.' is not a generic definition'); } + + $details= XPClass::detailsForClass($this->name); + $annotations= $details ? $details['class'][DETAIL_ANNOTATIONS] : []; $components= []; - foreach (explode(',', $this->getAnnotation('generic', 'self')) as $name) { + foreach (explode(',', $annotations['generic']['self']) as $name) { $components[]= ltrim($name); } return $components; @@ -782,7 +463,9 @@ public function genericComponents() { * @return bool */ public function isGenericDefinition(): bool { - return $this->hasAnnotation('generic', 'self'); + $details= XPClass::detailsForClass($this->name); + $annotations= $details ? $details['class'][DETAIL_ANNOTATIONS] : []; + return isset($annotations['generic']['self']); } /** diff --git a/src/main/php/lang/reflect/ClassParser.class.php b/src/main/php/lang/reflect/ClassParser.class.php index 3a9c6cc88..558319bc8 100755 --- a/src/main/php/lang/reflect/ClassParser.class.php +++ b/src/main/php/lang/reflect/ClassParser.class.php @@ -247,14 +247,21 @@ protected function value($tokens, &$i, $context, $imports) { $class= XPClass::forName($this->type($tokens, $i, $context, $imports)); $i+= 2; if (T_VARIABLE === $tokens[$i][0]) { - $field= $class->getField(substr($tokens[$i][1], 1)); + $name= substr($tokens[$i][1], 1); + $reflect= $class->reflect(); + if (!$reflect->hasProperty($name)) { + throw new ElementNotFoundException('No such field "'.$name.'" in class '.$class->getName()); + } + $field= $class->reflect()->getProperty($name); $m= $field->getModifiers(); if ($m & MODIFIER_PUBLIC) { - return $field->get(null); + return $field->getValue(null); } else if (($m & MODIFIER_PROTECTED) && $class->isAssignableFrom($context['self'])) { - return $field->setAccessible(true)->get(null); + $field->setAccessible(true); + return $field->getValue(null); } else if (($m & MODIFIER_PRIVATE) && $class->getName() === $context['self']) { - return $field->setAccessible(true)->get(null); + $field->setAccessible(true); + return $field->getValue(null); } else { throw new IllegalAccessException(sprintf( 'Cannot access %s field %s::$%s', @@ -266,7 +273,12 @@ protected function value($tokens, &$i, $context, $imports) { } else if (T_CLASS === $tokens[$i][0]) { return $class->literal(); } else { - return $class->getConstant($tokens[$i][1]); + $name= $tokens[$i][1]; + $reflect= $class->reflect(); + if (!$reflect->hasConstant($name)) { + throw new ElementNotFoundException('No such constant "'.$name.'" in class '.$class->getName()); + } + return $reflect->getConstant($name); } } } diff --git a/src/main/php/lang/reflect/Constructor.class.php b/src/main/php/lang/reflect/Constructor.class.php deleted file mode 100755 index 2f29b7bdf..000000000 --- a/src/main/php/lang/reflect/Constructor.class.php +++ /dev/null @@ -1,81 +0,0 @@ -getConstructor(); - * - * $instance= $constructor->newInstance(); - * $instance= $constructor->newInstance([6100]); - * ``` - * - * @param var[] args - * @return object - * @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 (\Throwable $e) { - throw new TargetInvocationException(XPClass::nameOf($this->_class).'::', $e); - } - } - - /** Retrieve return type */ - public function getReturnType(): Type { return new XPClass($this->_class); } - - /** Retrieve return type name */ - public function getReturnTypeName(): string { return XPClass::nameOf($this->_class); } -} diff --git a/src/main/php/lang/reflect/Field.class.php b/src/main/php/lang/reflect/Field.class.php deleted file mode 100755 index 39d605535..000000000 --- a/src/main/php/lang/reflect/Field.class.php +++ /dev/null @@ -1,337 +0,0 @@ -_class= $class; - $this->_reflect= $reflect; - } - - /** Get field's name */ - public function getName(): string { return $this->_reflect->getName(); } - - /** - * Resolution resolve handling `self` and `parent` (`static` is only for return - * types, see https://wiki.php.net/rfc/static_return_type#allowed_positions). - * - * @return [:(function(string): lang.Type)] - */ - private function resolve() { - return [ - 'self' => function() { return new XPClass($this->_reflect->getDeclaringClass()); }, - 'parent' => function() { return new XPClass($this->_reflect->getDeclaringClass()->getParentClass()); }, - ]; - } - - /** Gets field type */ - public function getType(): Type { - $api= function() { - $details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - $r= $details[DETAIL_RETURNS] ?? $details[DETAIL_ANNOTATIONS]['type'] ?? null; - return $r ? ltrim($r, '&') : null; - }; - - $t= PHP_VERSION_ID >= 70400 || '' === $this->_reflect->name ? $this->_reflect->getType() : null; - return Type::resolve($t, $this->resolve(), $api) ?? Type::$VAR; - } - - /** Gets field type's name */ - public function getTypeName(): string { - static $map= [ - 'mixed' => 'var', - 'false' => 'bool', - 'boolean' => 'bool', - 'double' => 'float', - 'integer' => 'int', - ]; - - $t= PHP_VERSION_ID >= 70400 || '' === $this->_reflect->name ? $this->_reflect->getType() : null; - if (null === $t) { - - // Check for type in api documentation - $name= 'var'; - } else if ($t instanceof \ReflectionUnionType) { - $union= ''; - $nullable= ''; - foreach ($t->getTypes() as $component) { - if ('null' === ($name= $component->getName())) { - $nullable= '?'; - } else { - $union.= '|'.($map[$name] ?? strtr($name, '\\', '.')); - } - } - return $nullable.substr($union, 1); - } else if ($t instanceof \ReflectionIntersectionType) { - $intersection= ''; - foreach ($t->getTypes() as $component) { - $name= $component->getName(); - $intersection.= '&'.($map[$name] ?? strtr($name, '\\', '.')); - } - return substr($intersection, 1); - } else { - $name= PHP_VERSION_ID >= 70100 ? $t->getName() : $t->__toString(); - - // Check array for more specific types, e.g. `string[]` in api documentation - if ('array' !== $name) { - return $map[$name] ?? strtr($name, '\\', '.'); - } - } - - $details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - $f= $details[DETAIL_RETURNS] ?? $details[DETAIL_ANNOTATIONS]['type'] ?? null; - return null === $f ? $name : rtrim(ltrim($f, '&'), '.'); - } - - /** - * Get field's type restriction. - * - * @return lang.Type or NULL if there is no restriction - * @throws lang.ClassNotFoundException if the restriction cannot be resolved - */ - public function getTypeRestriction() { - try { - return Type::resolve(PHP_VERSION_ID >= 70400 || '' === $this->_reflect->name ? $this->_reflect->getType() : null, $this->resolve()); - } catch (ClassLoadingException $e) { - throw new ClassNotFoundException(sprintf( - 'Typehint for %s::%s()\'s parameter "%s" cannot be resolved: %s', - strtr($this->_details[0], '\\', '.'), - $this->_details[1], - $this->_reflect->getName(), - $e->getMessage() - )); - } - } - - /** - * Check whether an annotation exists - * - * @param string name - * @param string key default NULL - * @return bool - */ - public function hasAnnotation($name, $key= null): bool { - $details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - if ($key) { - $a= $details[DETAIL_ANNOTATIONS][$name] ?? null; - return is_array($a) && array_key_exists($key, $a); - } else { - return array_key_exists($name, $details[DETAIL_ANNOTATIONS] ?? []); - } - } - - /** - * Retrieve annotation by name - * - * @param string name - * @param string key default NULL - * @return var - * @throws lang.ElementNotFoundException - */ - public function getAnnotation($name, $key= null) { - $details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - if ($key) { - $a= $details[DETAIL_ANNOTATIONS][$name] ?? null; - if (is_array($a) && array_key_exists($key, $a)) return $a[$key]; - } else { - if (array_key_exists($name, $details[DETAIL_ANNOTATIONS] ?? [])) return $details[DETAIL_ANNOTATIONS][$name]; - } - - throw new ElementNotFoundException('Annotation "'.$name.($key ? '.'.$key : '').'" does not exist'); - } - - /** Retrieve whether this field has annotations */ - public function hasAnnotations(): bool { - $details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - return $details ? !empty($details[DETAIL_ANNOTATIONS]) : false; - } - - /** - * Retrieve all of this field's annotations - * - * @return [:var] annotations - */ - public function getAnnotations() { - $details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - return $details ? $details[DETAIL_ANNOTATIONS] : []; - } - - /** - * Returns the XPClass object representing the class or interface - * that declares the field represented by this Field object. - */ - public function getDeclaringClass(): XPClass { - return new XPClass($this->_reflect->getDeclaringClass()); - } - - /** - * Returns the value of the field represented by this Field, on the - * specified object. - * - * @param object instance - * @return var - * @throws lang.IllegalArgumentException in case the passed object is not an instance of the declaring class - * @throws lang.IllegalAccessException in case this field is not public - */ - public function get($instance) { - if (null !== $instance && !($instance instanceof $this->_class)) { - throw new IllegalArgumentException(sprintf( - 'Passed argument is not a %s class (%s)', - XPClass::nameOf($this->_class), - typeof($instance)->getName() - )); - } - - // Check modifiers. If scope is an instance of this class, allow - // protected method invocation (which the PHP reflection API does - // not). - $m= $this->_reflect->getModifiers(); - $public= $m & MODIFIER_PUBLIC; - if (!$public && !$this->accessible) { - $t= debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1); - $scope= $t[1]['class'] ?? ClassLoader::getDefault()->loadUri($t[0]['file'])->literal(); - $decl= $this->_reflect->getDeclaringClass()->getName(); - if ($m & MODIFIER_PROTECTED) { - $allow= $scope === $decl || is_subclass_of($scope, $decl); - } else { - $allow= $scope === $decl; - } - - if (!$allow) { - throw new IllegalAccessException(sprintf( - 'Cannot read %s %s::$%s from scope %s', - Modifiers::stringOf($this->getModifiers()), - $this->_class, - $this->_reflect->getName(), - $scope - )); - } - } - - try { - $public || $this->_reflect->setAccessible(true); - return $this->_reflect->getValue($instance); - } catch (Throwable $e) { - throw $e; - } catch (\Throwable $e) { - throw new XPException($e->getMessage()); - } - } - - /** - * Changes the value of the field represented by this Field, on the - * specified object. - * - * @param object instance - * @param var value - * @throws lang.IllegalArgumentException in case the passed object is not an instance of the declaring class - * @throws lang.IllegalAccessException in case this field is not public - */ - public function set($instance, $value) { - if (null !== $instance && !($instance instanceof $this->_class)) { - throw new IllegalArgumentException(sprintf( - 'Passed argument is not a %s class (%s)', - XPClass::nameOf($this->_class), - typeof($instance)->getName() - )); - } - - // Check modifiers. If scope is an instance of this class, allow - // protected method invocation (which the PHP reflection API does - // not). - $m= $this->_reflect->getModifiers(); - $public= $m & MODIFIER_PUBLIC; - if (!$public && !$this->accessible) { - $t= debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1); - $scope= $t[1]['class'] ?? ClassLoader::getDefault()->loadUri($t[0]['file'])->literal(); - $decl= $this->_reflect->getDeclaringClass()->getName(); - if ($m & MODIFIER_PROTECTED) { - $allow= $scope === $decl || is_subclass_of($scope, $decl); - } else { - $allow= $scope === $decl; - } - if (!$allow) { - throw new IllegalAccessException(sprintf( - 'Cannot write %s %s::$%s from scope %s', - Modifiers::stringOf($this->getModifiers()), - XPClass::nameOf($this->_class), - $this->_reflect->getName(), - $scope - )); - } - } - - try { - $public || $this->_reflect->setAccessible(true); - return $this->_reflect->setValue($instance, $value); - } catch (Throwable $e) { - throw $e; - } catch (\Error $e) { - throw new IllegalAccessException($e->getMessage(), $e); // PHP 8.1 raises errors when modifying readonly properties - } catch (\Throwable $e) { - throw new XPException($e->getMessage()); - } - } - - /** Retrieve this field's modifiers */ - public function getModifiers(): int { return $this->_reflect->getModifiers(); } - - /** - * Sets whether this routine should be accessible from anywhere, - * regardless of its visibility level. - */ - public function setAccessible(bool $flag): self { - $this->accessible= $flag; - return $this; - } - - /** Compares this field to another value */ - public function compareTo($value): int { - if (!($value instanceof self)) return 1; - if (0 !== ($c= $value->_reflect->getName() <=> $this->_reflect->getName())) return $c; - if (0 !== ($c= $value->getDeclaringClass()->compareTo($this->getDeclaringClass()))) return $c; - return 0; - } - - /** Returns a hashcode for this routine */ - public function hashCode(): string { - return 'F['.$this->_reflect->getDeclaringClass().$this->_reflect->getName(); - } - - /** Creates a string representation of this field */ - public function toString(): string { - return sprintf( - '%s %s %s::$%s', - Modifiers::stringOf($this->getModifiers()), - $this->getTypeName(), - $this->getDeclaringClass()->getName(), - $this->getName() - ); - } -} diff --git a/src/main/php/lang/reflect/InvocationHandler.class.php b/src/main/php/lang/reflect/InvocationHandler.class.php deleted file mode 100755 index 24f8e6808..000000000 --- a/src/main/php/lang/reflect/InvocationHandler.class.php +++ /dev/null @@ -1,27 +0,0 @@ -getMethod('toString'); - * $str= $method->invoke(new Date()); - * ``` - * - * Example (passing arguments) - * ```php - * $method= XPClass::forName('util.Date')->getMethod('format'); - * $str= $method->invoke(Date::now(), ['%d.%m.%Y']); - * ``` - * - * Example (static invokation): - * ```php - * $method= XPClass::forName('util.log.Logger')->getMethod('getInstance'); - * $log= $method->invoke(null); - * ``` - * - * @param 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), - typeof($obj)->getName() - )); - } - - // 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 (\Throwable $e) { - throw new TargetInvocationException(XPClass::nameOf($this->_class).'::'.$this->_reflect->getName(), $e); - } - } -} diff --git a/src/main/php/lang/reflect/Parameter.class.php b/src/main/php/lang/reflect/Parameter.class.php deleted file mode 100755 index ed532da6a..000000000 --- a/src/main/php/lang/reflect/Parameter.class.php +++ /dev/null @@ -1,258 +0,0 @@ -_reflect= $reflect; - $this->_details= $details; - } - - /** - * Get parameter's name. - * - * @return string - */ - public function getName() { - return $this->_reflect->getName(); - } - - /** - * Resolution resolve handling `self` and `parent` (`static` is only for return - * types, see https://wiki.php.net/rfc/static_return_type#allowed_positions). - * - * @return [:(function(string): lang.Type)] - */ - private function resolve() { - return [ - 'self' => function() { return new XPClass($this->_reflect->getDeclaringClass()); }, - 'parent' => function() { return new XPClass($this->_reflect->getDeclaringClass()->getParentClass()); }, - ]; - } - - /** - * Get parameter's type. - * - * @return lang.Type - * @throws lang.ClassFormatException if the restriction cannot be resolved - */ - public function getType() { - $api= function() { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); - $r= $details[DETAIL_ARGUMENTS][$this->_details[2]] ?? null; - return $r ? rtrim(ltrim($r, '&'), '.') : null; - }; - return Type::resolve($this->_reflect->getType(), $this->resolve(), $api) ?? Type::$VAR; - } - - /** - * Get parameter's type name - * - * @return string - */ - public function getTypeName() { - static $map= [ - 'mixed' => 'var', - 'false' => 'bool', - 'boolean' => 'bool', - 'double' => 'float', - 'integer' => 'int', - ]; - - $t= $this->_reflect->getType(); - if (null === $t) { - $nullable= ''; - - // Check for type in api documentation - $name= 'var'; - } else if ($t instanceof \ReflectionUnionType) { - $union= ''; - $nullable= ''; - foreach ($t->getTypes() as $component) { - if ('null' === ($name= $component->getName())) { - $nullable= '?'; - } else { - $union.= '|'.($map[$name] ?? strtr($name, '\\', '.')); - } - } - return $nullable.substr($union, 1); - } else if ($t instanceof \ReflectionIntersectionType) { - $intersection= ''; - foreach ($t->getTypes() as $component) { - $name= $component->getName(); - $intersection.= '&'.($map[$name] ?? strtr($name, '\\', '.')); - } - return ($t->allowsNull() ? '?' : '').substr($intersection, 1); - } else { - $nullable= $t->allowsNull() ? '?' : ''; - $name= PHP_VERSION_ID >= 70100 ? $t->getName() : $t->__toString(); - - // Check array and callable for more specific types, e.g. `string[]` or - // `function(): string` in api documentation - if ('array' !== $name && 'callable' !== $name) { - return $nullable.($map[$name] ?? strtr($name, '\\', '.')); - } - } - - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); - $r= $details[DETAIL_ARGUMENTS][$this->_details[2]] ?? null; - return null === $r ? $nullable.$name : rtrim(ltrim($r, '&'), '.'); - } - - /** - * Get parameter's type restriction. - * - * @return lang.Type or NULL if there is no restriction - * @throws lang.ClassNotFoundException if the restriction cannot be resolved - */ - public function getTypeRestriction() { - try { - return Type::resolve($this->_reflect->getType(), $this->resolve()); - } catch (ClassLoadingException $e) { - throw new ClassNotFoundException(sprintf( - 'Typehint for %s::%s()\'s parameter "%s" cannot be resolved: %s', - strtr($this->_details[0], '\\', '.'), - $this->_details[1], - $this->_reflect->getName(), - $e->getMessage() - )); - } - } - - /** - * Retrieve whether this argument is optional - * - * @return bool - */ - public function isOptional() { - return $this->_reflect->isOptional(); - } - - /** - * Retrieve whether this argument is variadic - * - * @return bool - */ - public function isVariadic() { - if ($this->_reflect->isVariadic()) { - return true; - } else if ( - $this->_reflect->isOptional() && - ($details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1])) && - isset($details[DETAIL_ARGUMENTS][$this->_details[2]]) - ) { - return 0 === substr_compare($details[DETAIL_ARGUMENTS][$this->_details[2]], '...', -3); - } else { - return false; - } - } - - /** - * Get default value. Additionally checks `default` annotation for NULL defaults. - * - * @throws lang.IllegalStateException in case this argument is not optional - * @return var - */ - public function getDefaultValue() { - if ($this->_reflect->isOptional()) { - if (!$this->_reflect->isDefaultValueAvailable()) return null; - - $value= $this->_reflect->getDefaultValue(); - if (null === $value && ($details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]))) { - return $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()]['default'] ?? null; - } - return $value; - } - - throw new IllegalStateException('Parameter "'.$this->_reflect->getName().'" has no default value'); - } - - /** - * Check whether an annotation exists - * - * @param string name - * @param string key default NULL - * @return bool - */ - public function hasAnnotation($name, $key= null) { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); - $r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; - - return $key ? array_key_exists($key, $r[$name] ?? []) : array_key_exists($name, $r); - } - - /** - * Retrieve annotation by name - * - * @param string name - * @param string key default NULL - * @return var - * @throws lang.ElementNotFoundException - */ - public function getAnnotation($name, $key= null) { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); - $r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; - - if ($key) { - if (array_key_exists($key, $r[$name] ?? [])) return $r[$name][$key]; - } else { - if (array_key_exists($name, $r)) return $r[$name]; - } - - throw new ElementNotFoundException('Annotation "'.$name.($key ? '.'.$key : '').'" does not exist'); - } - - /** - * Retrieve whether a method has annotations - * - * @return bool - */ - public function hasAnnotations() { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); - return !empty($details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []); - } - - /** - * Retrieve all of a method's annotations - * - * @return var[] annotations - */ - public function getAnnotations() { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]); - return $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []; - } - - /** - * Creates a string representation - * - * @return string - */ - public function toString() { - return sprintf( - '%s<%s %s%s>', - nameof($this), - $this->getType()->toString(), - $this->_reflect->getName(), - $this->_reflect->isOptional() ? '= '.Objects::stringOf($this->_reflect->getDefaultValue()) : '' - ); - } -} diff --git a/src/main/php/lang/reflect/Proxy.class.php b/src/main/php/lang/reflect/Proxy.class.php deleted file mode 100755 index 79b5b2545..000000000 --- a/src/main/php/lang/reflect/Proxy.class.php +++ /dev/null @@ -1,161 +0,0 @@ -_h= $handler; - } - - /** - * Returns the XPClass object for a proxy class given a class loader - * and an array of interfaces. The proxy class will be defined by the - * specified class loader and will implement all of the supplied - * interfaces (also loaded by the classloader). - * - * @param lang.IClassLoader classloader - * @param lang.XPClass[] interfaces names of the interfaces to implement - * @return lang.XPClass - * @throws lang.IllegalArgumentException - */ - public static function getProxyClass(IClassLoader $classloader, array $interfaces) { - static $num= 0; - static $cache= []; - - $t= sizeof($interfaces); - if (0 === $t) { - throw new IllegalArgumentException('Interfaces may not be empty'); - } - - // Calculate cache key (composed of the names of all interfaces) - $key= $classloader->hashCode().':'.implode(';', array_map( - function($i) { return $i->getName(); }, - $interfaces - )); - if (isset($cache[$key])) return $cache[$key]; - - // Create proxy class' name, using a unique identifier and a prefix - $decl= self::LITERAL.$num; - $name= self::PREFIX.$num; - $bytes= 'class '.$decl.' extends \lang\reflect\Proxy implements '; - $added= []; - - for ($j= 0; $j < $t; $j++) { - $bytes.= $interfaces[$j]->literal().', '; - } - $bytes= substr($bytes, 0, -2)." {\n"; - - for ($j= 0; $j < $t; $j++) { - $if= $interfaces[$j]; - - // Verify that the Class object actually represents an interface - if (!$if->isInterface()) { - throw new IllegalArgumentException($if->getName().' is not an interface'); - } - - // Implement all the interface's methods - foreach ($if->getMethods() as $m) { - - // Check for already declared methods, do not redeclare them - if (isset($added[$m->getName()])) continue; - $added[$m->getName()]= true; - - // Build signature and argument list - if ($m->hasAnnotation('overloaded')) { - $signatures= $m->getAnnotation('overloaded', 'signatures'); - $max= 0; - $cases= []; - foreach ($signatures as $signature) { - $args= sizeof($signature); - $max= max($max, $args- 1); - if (isset($cases[$args])) continue; - - $cases[$args]= ( - 'case '.$args.': '. - 'return $this->_h->invoke($this, \''.$m->getName(true).'\', ['. - ($args ? '$_'.implode(', $_', range(0, $args- 1)) : '').']);' - ); - } - - // Create method - $bytes.= ( - 'function '.$m->getName().'($_'.implode('= NULL, $_', range(0, $max)).'= NULL) { '. - 'switch (func_num_args()) {'.implode("\n", $cases). - ' default: throw new IllegalArgumentException(\'Illegal number of arguments\'); }'. - '}'."\n" - ); - } else { - $signature= $args= ''; - foreach ($m->getParameters() as $param) { - $restriction= $param->getTypeRestriction(); - $signature.= ', '.($restriction ? literal($restriction->getName()) : '').' $'.$param->getName(); - $args.= ', $'.$param->getName(); - $param->isOptional() && $signature.= '= '.var_export($param->getDefaultValue(), true); - } - $signature= substr($signature, 2); - $args= substr($args, 2); - - // Create method - $bytes.= ( - 'function '.$m->getName().'('.$signature.') { '. - 'return $this->_h->invoke($this, \''.$m->getName(true).'\', ['.$args.']); '. - '}'."\n" - ); - } - } - } - $bytes.= ' }'; - - // Define the generated class - \xp::$cn[$decl]= $name; - try { - $dyn= DynamicClassLoader::instanceFor(__METHOD__); - $dyn->setClassBytes($decl, $bytes); - $class= $dyn->loadClass($decl); - } catch (FormatException $e) { - unset(\xp::$cn[$decl]); - throw new IllegalArgumentException($e->getMessage()); - } - - // Update cache and return XPClass object - $cache[$key]= $class; - $num++; - return $class; - } - - /** - * Returns an instance of a proxy class for the specified interfaces - * that dispatches method invocations to the specified invocation - * handler. - * - * @param lang.ClassLoader classloader - * @param lang.XPClass[] interfaces - * @param lang.reflect.InvocationHandler handler - * @return lang.XPClass - * @throws lang.IllegalArgumentException - */ - public static function newProxyInstance($classloader, $interfaces, $handler) { - return self::getProxyClass($classloader, $interfaces)->newInstance($handler); - } -} diff --git a/src/main/php/lang/reflect/Routine.class.php b/src/main/php/lang/reflect/Routine.class.php deleted file mode 100755 index 5a74f030f..000000000 --- a/src/main/php/lang/reflect/Routine.class.php +++ /dev/null @@ -1,349 +0,0 @@ -_class= $class; - $this->_reflect= $reflect; - } - - /** Get routine's name */ - public function getName(): string { return $this->_reflect->getName(); } - - /** Retrieve this method's modifiers */ - public function getModifiers(): int { - - // Note: ReflectionMethod::getModifiers() returns whatever PHP reflection - // returns, but the numeric value changed since 5.0.0 as the zend_function - // struct's fn_flags now contains not only ZEND_ACC_(PPP, STATIC, FINAL, - // ABSTRACT) but also some internal information about how this method needs - // to be called. - // - // == List of fn_flags we don't want to return from this method == - // #define ZEND_ACC_IMPLEMENTED_ABSTRACT 0x08 - // #define ZEND_ACC_IMPLICIT_PUBLIC 0x1000 - // #define ZEND_ACC_CTOR 0x2000 - // #define ZEND_ACC_DTOR 0x4000 - // #define ZEND_ACC_CLONE 0x8000 - // #define ZEND_ACC_ALLOW_STATIC 0x10000 - // #define ZEND_ACC_SHADOW 0x20000 - // #define ZEND_ACC_DEPRECATED 0x40000 - // #define ZEND_ACC_IMPLEMENT_INTERFACES 0x80000 - // #define ZEND_ACC_CLOSURE 0x100000 - // #define ZEND_ACC_CALL_VIA_HANDLER 0x200000 - // #define ZEND_ACC_IMPLEMENT_TRAITS 0x400000 - // #define ZEND_HAS_STATIC_IN_METHODS 0x800000 - // #define ZEND_ACC_PASS_REST_BY_REFERENCE 0x1000000 - // #define ZEND_ACC_PASS_REST_PREFER_REF 0x2000000 - // #define ZEND_ACC_RETURN_REFERENCE 0x4000000 - // #define ZEND_ACC_DONE_PASS_TWO 0x8000000 - // #define ZEND_ACC_HAS_TYPE_HINTS 0x10000000 - // == - return $this->_reflect->getModifiers() & ~0x1fb7f008; - } - - /** - * Returns this method's parameters - * - * @return lang.reflect.Parameter[] - */ - public function getParameters() { - $r= []; - $c= $this->_reflect->getDeclaringClass()->getName(); - foreach ($this->_reflect->getParameters() as $offset => $param) { - $r[]= new Parameter($param, [$c, $this->_reflect->getName(), $offset]); - } - return $r; - } - - /** - * Retrieve one of this method's parameters by its offset - * - * @param int $offset - * @return lang.reflect.Parameter or NULL if it does not exist - */ - public function getParameter($offset) { - $list= $this->_reflect->getParameters(); - return isset($list[$offset]) - ? new Parameter($list[$offset], [$this->_reflect->getDeclaringClass()->getName(), $this->_reflect->getName(), $offset]) - : null - ; - } - - /** Retrieve how many parameters this method declares (including optional ones) */ - public function numParameters(): int { - return $this->_reflect->getNumberOfParameters(); - } - - /** - * Resolution resolve handling `static`, `self` and `parent`. - * - * @return [:(function(string): lang.Type)] - */ - private function resolve() { - return [ - 'static' => function() { return new XPClass($this->_class); }, - 'self' => function() { return new XPClass($this->_reflect->getDeclaringClass()); }, - 'parent' => function() { return new XPClass($this->_reflect->getDeclaringClass()->getParentClass()); }, - ]; - } - - /** - * Get return type. - * - * @return lang.Type - * @throws lang.ClassFormatException if the restriction cannot be resolved - */ - public function getReturnType(): Type { - $api= function() { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - $r= $details[DETAIL_RETURNS] ?? null; - return $r ? ltrim($r, '&') : null; - }; - return Type::resolve($this->_reflect->getReturnType(), $this->resolve(), $api) ?? Type::$VAR; - } - - /** Retrieve return type name */ - public function getReturnTypeName(): string { - static $map= [ - 'mixed' => 'var', - 'false' => 'bool', - 'boolean' => 'bool', - 'double' => 'float', - 'integer' => 'int', - ]; - - $t= $this->_reflect->getReturnType(); - if (null === $t) { - $nullable= ''; - - // Check for type in api documentation - $name= 'var'; - } else if ($t instanceof \ReflectionUnionType) { - $union= ''; - $nullable= ''; - foreach ($t->getTypes() as $component) { - if ('null' === ($name= $component->getName())) { - $nullable= '?'; - } else { - $union.= '|'.($map[$name] ?? strtr($name, '\\', '.')); - } - } - return $nullable.substr($union, 1); - } else if ($t instanceof \ReflectionIntersectionType) { - $intersection= ''; - foreach ($t->getTypes() as $component) { - $name= $component->getName(); - $intersection.= '&'.($map[$name] ?? strtr($name, '\\', '.')); - } - return ($t->allowsNull() ? '?' : '').substr($intersection, 1); - } else { - $nullable= $t->allowsNull() ? '?' : ''; - $name= PHP_VERSION_ID >= 70100 ? $t->getName() : $t->__toString(); - - // Check array, self, void and callable for more specific types, e.g. `string[]`, - // `static`, `never` or `function(): string` in api documentation - if ('array' !== $name && 'callable' !== $name && 'self' !== $name && 'void' !== $name) { - return $nullable.($map[$name] ?? strtr($name, '\\', '.')); - } - } - - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - $r= $details[DETAIL_RETURNS] ?? null; - return null === $r ? $nullable.$name : rtrim(ltrim($r, '&'), '.'); - } - - /** - * Get return type restriction. - * - * @return lang.Type or NULL if there is no restriction - * @throws lang.ClassFormatException if the restriction cannot be resolved - */ - public function getReturnTypeRestriction() { - try { - return Type::resolve($this->_reflect->getReturnType(), $this->resolve()); - } catch (ClassLoadingException $e) { - throw new ClassFormatException(sprintf( - 'Typehint for %s::%s()\'s return type cannot be resolved: %s', - strtr($this->_class, '\\', '.'), - $this->_reflect->getName(), - $e->getMessage() - )); - } - } - - /** - * Retrieve exception names - * - * @return string[] - */ - public function getExceptionNames() { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - return $details ? $details[DETAIL_THROWS] : []; - } - - /** - * Retrieve exception types - * - * @return lang.XPClass[] - */ - public function getExceptionTypes() { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - if (!$details) return []; - - $thrown= []; - foreach ($details[DETAIL_THROWS] as $name) { - $thrown[]= XPClass::forName($name); - } - return $thrown; - } - - /** - * Returns the XPClass object representing the class or interface - * that declares the method represented by this Method object. - * - * @return lang.XPClass - */ - public function getDeclaringClass() { - return new XPClass($this->_reflect->getDeclaringClass()); - } - - /** - * Retrieves the api doc comment for this method. Returns NULL if - * no documentation is present. - * - * @return string - */ - public function getComment() { - if (!($details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()))) return null; - return $details[DETAIL_COMMENT]; - } - - /** - * Check whether an annotation exists - * - * @param string $name - * @param string $key default NULL - * @return bool - */ - public function hasAnnotation($name, $key= null): bool { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - if ($key) { - $a= $details[DETAIL_ANNOTATIONS][$name] ?? null; - return is_array($a) && array_key_exists($key, $a); - } else { - return array_key_exists($name, $details[DETAIL_ANNOTATIONS] ?? []); - } - } - - /** - * Retrieve annotation by name - * - * @param string $name - * @param string $key default NULL - * @return var - * @throws lang.ElementNotFoundException - */ - public function getAnnotation($name, $key= null) { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - if ($key) { - $a= $details[DETAIL_ANNOTATIONS][$name] ?? null; - if (is_array($a) && array_key_exists($key, $a)) return $a[$key]; - } else { - if (array_key_exists($name, $details[DETAIL_ANNOTATIONS] ?? [])) return $details[DETAIL_ANNOTATIONS][$name]; - } - - throw new ElementNotFoundException('Annotation "'.$name.($key ? '.'.$key : '').'" does not exist'); - } - - /** Retrieve whether a method has annotations */ - public function hasAnnotations(): bool { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - return $details ? !empty($details[DETAIL_ANNOTATIONS]) : false; - } - - /** - * Retrieve all of a method's annotations - * - * @return [:var] annotations - */ - public function getAnnotations() { - $details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_reflect->getName()); - return $details ? $details[DETAIL_ANNOTATIONS] : []; - } - - /** - * Sets whether this routine should be accessible from anywhere, - * regardless of its visibility level. - */ - public function setAccessible(bool $flag): self { - $this->accessible= $flag; - return $this; - } - - /** Compares this routine to another value */ - public function compareTo($value): int { - if (!($value instanceof self)) return 1; - if (0 !== ($c= $value->_reflect->getName() <=> $this->_reflect->getName())) return $c; - if (0 !== ($c= $value->getDeclaringClass()->compareTo($this->getDeclaringClass()))) return $c; - return 0; - } - - /** Returns a hashcode for this routine */ - public function hashCode(): string { - return 'R['.$this->_reflect->getDeclaringClass().$this->_reflect->getName(); - } - - /** Retrieve string representation */ - public function toString(): string { - $signature= ''; - foreach ($this->getParameters() as $param) { - if ($param->isOptional()) { - $signature.= ', ['.$param->getTypeName().' $'.$param->getName().'= '.str_replace("\n", ' ', Objects::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 - ); - } -} diff --git a/src/main/php/lang/reflect/TargetInvocationException.class.php b/src/main/php/lang/reflect/TargetInvocationException.class.php deleted file mode 100755 index 011686e29..000000000 --- a/src/main/php/lang/reflect/TargetInvocationException.class.php +++ /dev/null @@ -1,12 +0,0 @@ -newGenericType([$this->fixture]) - ->getMethod('accept') - ; + $generic= XPClass::forName('lang.unittest.ArrayFilter')->newGenericType([$this->fixture]); Assert::equals( $this->fixture, - $abstractMethod->getParameter(0)->getType() + Reflection::type($generic)->method('accept')->parameter(0)->constraint()->type() ); } } \ No newline at end of file diff --git a/src/test/php/lang/unittest/AnnotationTest.class.php b/src/test/php/lang/unittest/AnnotationTest.class.php deleted file mode 100755 index 5c8b7665d..000000000 --- a/src/test/php/lang/unittest/AnnotationTest.class.php +++ /dev/null @@ -1,108 +0,0 @@ -getMethod('annotated')->hasAnnotations()); - } - - #[Test] - public function thisMethodHasAnnotations() { - Assert::true(typeof($this)->getMethod('thisMethodHasAnnotations')->hasAnnotations()); - } - - #[Test] - public function simpleAnnotationExists() { - Assert::true($this->annotated()->getMethod('simple')->hasAnnotation('simple')); - } - - #[Test] - public function simpleAnnotationValue() { - Assert::equals(null, $this->annotated()->getMethod('simple')->getAnnotation('simple')); - } - - #[Test, Expect(ElementNotFoundException::class)] - public function getAnnotationForMethodWithout() { - typeof($this)->getMethod('annotated')->getAnnotation('any'); - } - - #[Test] - public function hasAnnotationForMethodWithout() { - Assert::false(typeof($this)->getMethod('annotated')->hasAnnotation('any')); - } - - #[Test, Expect(ElementNotFoundException::class)] - public function getNonExistantAnnotation() { - $this->annotated()->getMethod('simple')->getAnnotation('doesnotexist'); - } - - #[Test] - public function hasNonExistantAnnotation() { - Assert::false($this->annotated()->getMethod('simple')->hasAnnotation('doesnotexist')); - } - - #[Test, Values(['one', 'two', 'three'])] - public function multipleAnnotationsExist($annotation) { - Assert::true($this->annotated()->getMethod('multiple')->hasAnnotation($annotation)); - } - - #[Test] - public function multipleAnnotationsReturnedAsList() { - Assert::equals( - ['one' => null, 'two' => null, 'three' => null], - $this->annotated()->getMethod('multiple')->getAnnotations() - ); - } - - #[Test] - public function stringAnnotationValue() { - Assert::equals( - 'String value', - $this->annotated()->getMethod('stringValue')->getAnnotation('strval') - ); - } - - #[Test] - public function hashAnnotationValue() { - Assert::equals( - ['key' => 'value'], - $this->annotated()->getMethod('hashValue')->getAnnotation('config') - ); - } - - #[Test] - public function testMethodHasTestAnnotation() { - Assert::true($this->annotated()->getMethod('testMethod')->hasAnnotation('test')); - } - - #[Test] - public function testMethodHasIgnoreAnnotation() { - Assert::true($this->annotated()->getMethod('testMethod')->hasAnnotation('ignore')); - } - - #[Test] - public function testMethodsLimitAnnotation() { - Assert::equals( - ['time' => 0.1, 'memory' => 100], - $this->annotated()->getMethod('testMethod')->getAnnotation('limit') - ); - } - - #[Test] - public function on_anonymous_class() { - $c= new class() { - - #[Test] - public function fixture() { } - }; - - Assert::equals(['test' => null], typeof($c)->getMethod('fixture')->getAnnotations()); - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/AnonymousInstanceTest.class.php b/src/test/php/lang/unittest/AnonymousInstanceTest.class.php index af111d7be..886ec0c62 100755 --- a/src/test/php/lang/unittest/AnonymousInstanceTest.class.php +++ b/src/test/php/lang/unittest/AnonymousInstanceTest.class.php @@ -1,6 +1,6 @@ ', [], []); - Assert::true(typeof($filter)->hasAnnotation('anon')); + Assert::true(Reflection::type($filter)->annotations()->provides('anon')); // FIXME: Should be lang.unittest.Anon } #[Test] @@ -44,12 +44,9 @@ protected function accept($e) { return true; } #[Test] public function invocation() { - $methods= newinstance('lang.unittest.ArrayFilter', [], [ - 'accept' => function($method) { return 'invocation' === $method->getName(); } + $methods= newinstance('lang.unittest.ArrayFilter', [], [ + 'accept' => function($i) { return 0 === $i % 2; } ]); - Assert::equals( - [typeof($this)->getMethod('invocation')], - $methods->filter(typeof($this)->getMethods()) - ); + Assert::equals([2], $methods->filter([1, 2, 3])); } } \ No newline at end of file diff --git a/src/test/php/lang/unittest/BrokenAnnotationTest.class.php b/src/test/php/lang/unittest/BrokenAnnotationTest.class.php index 88722f175..221251d90 100755 --- a/src/test/php/lang/unittest/BrokenAnnotationTest.class.php +++ b/src/test/php/lang/unittest/BrokenAnnotationTest.class.php @@ -2,7 +2,6 @@ use lang\reflect\ClassParser; use lang\{ClassFormatException, XPClass}; -use test\verify\Runtime; use test\{Action, Assert, Expect, Test}; class BrokenAnnotationTest { @@ -13,7 +12,7 @@ class BrokenAnnotationTest { * @param string $input * @return [:var] */ - protected function parse($input) { + private function parse($input) { try { return (new ClassParser())->parseAnnotations($input, nameof($this)); } finally { @@ -21,13 +20,9 @@ protected function parse($input) { } } - #[Test, Runtime(php: '<8.0'), Expect(class: ClassFormatException::class, message: '/Unterminated annotation/')] + #[Test, Expect(class: ClassFormatException::class, message: '/Unterminated annotation/')] public function no_ending_bracket() { - try { - XPClass::forName('lang.unittest.NoEndingBracket')->getAnnotations(); - } finally { - \xp::gc(); // Strip deprecation warning - } + $this->parse("#[@attribute\n"); } #[Test, Expect(class: ClassFormatException::class, message: '/Parse error/')] diff --git a/src/test/php/lang/unittest/ClassLoaderTest.class.php b/src/test/php/lang/unittest/ClassLoaderTest.class.php index 7b7266557..9b8371124 100755 --- a/src/test/php/lang/unittest/ClassLoaderTest.class.php +++ b/src/test/php/lang/unittest/ClassLoaderTest.class.php @@ -9,15 +9,13 @@ ClassLoader, ClassNotFoundException, IllegalStateException, + Reflection, XPClass }; use test\{After, Assert, Before, Expect, Test, Values}; class ClassLoaderTest { - protected - $libraryLoader = null, - $brokenLoader = null, - $containedLoader = null; + protected $libraryLoader, $brokenLoader, $containedLoader; /** * Register XAR @@ -106,12 +104,11 @@ public function findNullClass() { public function initializerCalled() { $name= 'lang.unittest.LoaderTestClass'; if (class_exists(literal($name), false)) { - return $this->fail('Class "'.$name.'" may not exist!'); + throw new IllegalStateException('Class "'.$name.'" may not exist!'); } - Assert::true(ClassLoader::getDefault() - ->loadClass($name) - ->getMethod('initializerCalled') + Assert::true(Reflection::type(ClassLoader::getDefault()->loadClass($name)) + ->method('initializerCalled') ->invoke(null) ); } @@ -136,7 +133,7 @@ public function loadClassFileWithRecursionInStaticBlock() { with ($p= Package::forName('lang.unittest.fixture')); { $two= $p->loadClass('StaticRecursionTwo'); $one= $p->loadClass('StaticRecursionOne'); - Assert::equals($two, $one->getField('two')->get(null)); + Assert::equals($two, Reflection::type($one)->property('two')->get(null)); } } diff --git a/src/test/php/lang/unittest/EnumTest.class.php b/src/test/php/lang/unittest/EnumTest.class.php index 487e615e0..77e5502c0 100755 --- a/src/test/php/lang/unittest/EnumTest.class.php +++ b/src/test/php/lang/unittest/EnumTest.class.php @@ -1,7 +1,7 @@ self::class], XPClass::forName(SortOrder::class)->getAnnotations()); + Assert::equals([self::class], Reflection::type(SortOrder::class)->annotation('lang.unittest.UsedBy')->arguments()); } } \ No newline at end of file diff --git a/src/test/php/lang/unittest/FieldAccessTest.class.php b/src/test/php/lang/unittest/FieldAccessTest.class.php deleted file mode 100755 index 9abbc0fcb..000000000 --- a/src/test/php/lang/unittest/FieldAccessTest.class.php +++ /dev/null @@ -1,96 +0,0 @@ -type('{ public $fixture= "Test"; }'); - Assert::equals('Test', $fixture->getField('fixture')->get($fixture->newInstance())); - } - - #[Test] - public function read_static() { - $fixture= $this->type('{ public static $fixture= "Test"; }'); - Assert::equals('Test', $fixture->getField('fixture')->get(null)); - } - - #[Test] - public function write() { - $fixture= $this->type('{ public $fixture= "Test"; }'); - $instance= $fixture->newInstance(); - $fixture->getField('fixture')->set($instance, 'Changed'); - Assert::equals('Changed', $fixture->getField('fixture')->get($instance)); - } - - #[Test] - public function write_static() { - $fixture= $this->type('{ public static $fixture= "Test"; }'); - $fixture->getField('fixture')->set(null, 'Changed'); - Assert::equals('Changed', $fixture->getField('fixture')->get(null)); - } - - #[Test, Expect(IllegalAccessException::class), Values([['{ private $fixture; }'], ['{ protected $fixture; }']])] - public function cannot_read_non_public($declaration) { - $fixture= $this->type($declaration); - $fixture->getField('fixture')->get($fixture->newInstance()); - } - - #[Test, Expect(IllegalAccessException::class), Values([['{ private $fixture; }'], ['{ protected $fixture; }']])] - public function cannot_write_non_public($declaration) { - $fixture= $this->type($declaration); - $fixture->getField('fixture')->set($fixture->newInstance(), 'Test'); - } - - #[Test, Values([['{ private $fixture= "Test"; }'], ['{ protected $fixture= "Test"; }'],])] - public function can_read_private_or_protected_via_setAccessible($declaration) { - $fixture= $this->type($declaration); - Assert::equals('Test', $fixture->getField('fixture')->setAccessible(true)->get($fixture->newInstance())); - } - - #[Test, Values([['{ private $fixture= "Test"; }'], ['{ protected $fixture= "Test"; }'],])] - public function can_write_private_or_protected_via_setAccessible($declaration) { - $fixture= $this->type($declaration); - $instance= $fixture->newInstance(); - $field= $fixture->getField('fixture')->setAccessible(true); - $field->set($instance, 'Changed'); - Assert::equals('Changed', $field->get($instance)); - } - - #[Test, Expect(IllegalArgumentException::class)] - public function cannot_read_instance_method_with_incompatible() { - $fixture= $this->type('{ public $fixture; }'); - $fixture->getField('fixture')->get($this); - } - - #[Test, Expect(IllegalArgumentException::class)] - public function cannot_write_instance_method_with_incompatible() { - $fixture= $this->type('{ public $fixture; }'); - $fixture->getField('fixture')->set($this, 'Test'); - } - - #[Test, Runtime(php: '>=8.1')] - public function can_modify_uninitialized_readonly_property() { - $fixture= $this->type('{ public readonly string $fixture; }'); - $field= $fixture->getField('fixture'); - $instance= $fixture->newInstance(); - - $field->set($instance, 'Modified'); - Assert::equals('Modified', $field->get($instance)); - } - - #[Test, Expect(IllegalAccessException::class), Runtime(php: '>=8.1')] - public function cannot_write_readonly_property_after_initialization() { - $fixture= $this->type('{ public readonly int $fixture; public function __construct() { $this->fixture= 1; } }'); - $fixture->getField('fixture')->set($fixture->newInstance(), 2); - } - - #[Test, Expect(IllegalAccessException::class), Runtime(php: '>=8.1')] - public function cannot_write_readonly_property_after_initialization_via_argument_promotiom() { - $fixture= $this->type('{ public function __construct(public readonly int $fixture) { } }'); - $fixture->getField('fixture')->set($fixture->newInstance(1), 2); - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/FieldBasicsTest.class.php b/src/test/php/lang/unittest/FieldBasicsTest.class.php deleted file mode 100755 index 7eb061d33..000000000 --- a/src/test/php/lang/unittest/FieldBasicsTest.class.php +++ /dev/null @@ -1,105 +0,0 @@ -type('{ public $declared; }'); - Assert::equals($fixture, $fixture->getField('declared')->getDeclaringClass()); - } - - #[Test] - public function has_field_for_existant() { - Assert::true($this->type('{ public $declared; }')->hasField('declared')); - } - - #[Test] - public function has_field_for_non_existant() { - Assert::false($this->type()->hasField('@@nonexistant@@')); - } - - #[Test] - public function has_field_for_special() { - Assert::false($this->type()->hasField('__id')); - } - - #[Test] - public function get_existant_Field() { - Assert::instance(Field::class, $this->type('{ public $declared; }')->getField('declared')); - } - - #[Test, Expect(ElementNotFoundException::class)] - public function get_non_existant_Field() { - $this->type()->getField('@@nonexistant@@'); - } - - #[Test, Expect(ElementNotFoundException::class)] - public function get_field_for_special() { - $this->type()->getField('__id'); - } - - #[Test] - public function name() { - Assert::equals('fixture', $this->field('public $fixture;')->getName()); - } - - #[Test] - public function equality() { - $fixture= $this->type('{ public $fixture; }'); - Assert::equals($fixture->getField('fixture'), $fixture->getField('fixture')); - } - - #[Test] - public function a_field_is_not_equal_to_null() { - Assert::notEquals($this->field('public $fixture;'), null); - } - - #[Test, Values([['public $fixture;', 'public var %s::$fixture'], ['private $fixture;', 'private var %s::$fixture'], ['protected $fixture;', 'protected var %s::$fixture'], ['static $fixture;', 'public static var %s::$fixture'], ['private static $fixture;', 'private static var %s::$fixture'], ['protected static $fixture;', 'protected static var %s::$fixture'], ['/** @var int */ public $fixture;', 'public int %s::$fixture']])] - public function string_representation($declaration, $expected) { - $fixture= $this->type('{ '.$declaration.' }'); - Assert::equals(sprintf($expected, $fixture->getName()), $fixture->getField('fixture')->toString()); - } - - #[Test] - public function trait_field_type() { - Assert::equals('int', $this->type()->getField('NOT_INSTANCE')->getTypeName()); - } - - #[Test] - public function all_fields() { - $fixture= $this->type('{ public $a= "Test", $b; }', [ - 'use' => [] - ]); - Assert::equals( - ['a', 'b'], - array_map(function($f) { return $f->getName(); }, $fixture->getFields()) - ); - } - - #[Test] - public function all_fields_include_base_class() { - $fixture= $this->type('{ public $declared= "Test"; public function getDate() { return null; }}', [ - 'use' => [], - 'extends' => [AbstractTestClass::class] - ]); - Assert::equals( - ['declared', 'inherited'], - array_map(function($f) { return $f->getName(); }, $fixture->getFields()) - ); - } - - #[Test] - public function declared_fields() { - $fixture= $this->type('{ public $a= "Test", $b; }', [ - 'use' => [] - ]); - Assert::equals( - ['a', 'b'], - array_map(function($f) { return $f->getName(); }, $fixture->getDeclaredFields()) - ); - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/FieldModifiersTest.class.php b/src/test/php/lang/unittest/FieldModifiersTest.class.php deleted file mode 100755 index 971ff1ccb..000000000 --- a/src/test/php/lang/unittest/FieldModifiersTest.class.php +++ /dev/null @@ -1,32 +0,0 @@ -field('public $fixture;')->getModifiers()); - } - - #[Test] - public function private_modifier() { - Assert::equals(MODIFIER_PRIVATE, $this->field('private $fixture;')->getModifiers()); - } - - #[Test] - public function protected_modifier() { - Assert::equals(MODIFIER_PROTECTED, $this->field('protected $fixture;')->getModifiers()); - } - - #[Test] - public function static_modifier() { - Assert::equals(MODIFIER_STATIC | MODIFIER_PUBLIC, $this->field('public static $fixture;')->getModifiers()); - } - - #[Test, Runtime(php: '>=8.1')] - public function readonly_modifier() { - Assert::equals(MODIFIER_READONLY | MODIFIER_PUBLIC, $this->field('public readonly int $fixture;')->getModifiers()); - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/FieldTypeTest.class.php b/src/test/php/lang/unittest/FieldTypeTest.class.php deleted file mode 100755 index 0e0f01df7..000000000 --- a/src/test/php/lang/unittest/FieldTypeTest.class.php +++ /dev/null @@ -1,93 +0,0 @@ -field('public $fixture;')->getType()); - } - - #[Test, Values(from: 'types')] - public function field_type_determined_via_var_tag($declaration, $type) { - Assert::equals( - $type, - $this->field('/** @var '.$declaration.' */ public $fixture;')->getType() - ); - } - - #[Test, Values(from: 'types')] - public function field_typeName_determined_via_var_tag($declaration, $type) { - Assert::equals( - $type->getName(), - $this->field('/** @var '.$declaration.' */ public $fixture;')->getTypeName() - ); - } - - #[Test, Values(from: 'types')] - public function field_type_determined_via_type_tag($declaration, $type) { - Assert::equals( - $type, - $this->field('/** @type '.$declaration.' */ public $fixture;')->getType() - ); - } - - #[Test, Values(from: 'types')] - public function field_type_determined_via_annotation($declaration, $type) { - Assert::equals( - $type, - $this->field('#[Type("'.$declaration.'")]'."\n".'public $fixture;')->getType() - ); - } - - #[Test] - public function self_type_via_apidoc() { - $fixture= $this->type('{ /** @type self */ public $fixture; }'); - Assert::equals('self', $fixture->getField('fixture')->getTypeName()); - Assert::equals($fixture, $fixture->getField('fixture')->getType()); - } - - #[Test, Runtime(php: '>=7.4')] - public function self_type_via_syntax() { - $fixture= $this->type('{ public self $fixture; }'); - Assert::equals('self', $fixture->getField('fixture')->getTypeName()); - Assert::equals($fixture, $fixture->getField('fixture')->getType()); - } - - #[Test] - public function array_of_self_type() { - $fixture= $this->type('{ /** @type array */ public $fixture; }'); - Assert::equals(new ArrayType($fixture), $fixture->getField('fixture')->getType()); - } - - #[Test, Runtime(php: '>=7.4')] - public function specific_array_type_determined_via_apidoc() { - $fixture= $this->type('{ /** @type string[] */ public array $fixture; }'); - Assert::equals('string[]', $fixture->getField('fixture')->getTypeName()); - Assert::equals(new ArrayType(Primitive::$STRING), $fixture->getField('fixture')->getType()); - } - - #[Test] - public function untyped_restriction() { - Assert::null($this->field('public $fixture;')->getTypeRestriction()); - } - - #[Test, Runtime(php: '>=7.4')] - public function typed_restriction() { - Assert::equals(Primitive::$STRING, $this->field('public string $fixture;')->getTypeRestriction()); - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/FieldsTest.class.php b/src/test/php/lang/unittest/FieldsTest.class.php deleted file mode 100755 index b2afc3b73..000000000 --- a/src/test/php/lang/unittest/FieldsTest.class.php +++ /dev/null @@ -1,16 +0,0 @@ -type('{ '.$decl.' }', ['modifiers' => $modifiers])->getField('fixture'); - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/FunctionTypeTest.class.php b/src/test/php/lang/unittest/FunctionTypeTest.class.php index fd42ef178..ea926d965 100755 --- a/src/test/php/lang/unittest/FunctionTypeTest.class.php +++ b/src/test/php/lang/unittest/FunctionTypeTest.class.php @@ -1,7 +1,16 @@ invoke($value); } - #[Test, Expect(TargetInvocationException::class)] - public function invoke_wraps_exceptions_in_TargetInvocationExceptions() { + #[Test, Expect(IllegalArgumentException::class)] + public function invoke_raises() { $t= new FunctionType([], Primitive::$VOID); $t->invoke(function() { throw new \lang\IllegalArgumentException('Test'); }, []); } diff --git a/src/test/php/lang/unittest/ImplementationTest.class.php b/src/test/php/lang/unittest/ImplementationTest.class.php index e294c9d66..f80382d98 100755 --- a/src/test/php/lang/unittest/ImplementationTest.class.php +++ b/src/test/php/lang/unittest/ImplementationTest.class.php @@ -1,6 +1,6 @@ '); Assert::equals( XPClass::forName('lang.Type'), - typeof($fixture)->getMethod('put')->getParameter(0)->getType() + Reflection::type($fixture)->method('put')->parameter(0)->constraint()->type() ); } @@ -43,7 +43,7 @@ public function abstractTypeDictionaryPutMethodKeyParameter() { $fixture= Type::forName('lang.unittest.AbstractTypeDictionary'); Assert::equals( XPClass::forName('lang.Type'), - $fixture->getMethod('put')->getParameter(0)->getType() + Reflection::type($fixture)->method('put')->parameter(0)->constraint()->type() ); } diff --git a/src/test/php/lang/unittest/InstanceReflectionTest.class.php b/src/test/php/lang/unittest/InstanceReflectionTest.class.php index 352343211..3740faf78 100755 --- a/src/test/php/lang/unittest/InstanceReflectionTest.class.php +++ b/src/test/php/lang/unittest/InstanceReflectionTest.class.php @@ -1,6 +1,6 @@ fixture)->getField('elements')->getTypeName() + Reflection::type($this->fixture)->property('elements')->constraint()->type()->getName() ); } #[Test] public function putParameters() { - $params= typeof($this->fixture)->getMethod('put')->getParameters(); - Assert::equals(2, sizeof($params)); - Assert::equals(Primitive::$STRING, $params[0]->getType()); - Assert::equals(XPClass::forName('lang.Value'), $params[1]->getType()); + $params= Reflection::type($this->fixture)->method('put')->parameters(); + Assert::equals(2, $params->size()); + Assert::equals(Primitive::$STRING, $params->at(0)->constraint()->type()); + Assert::equals(XPClass::forName('lang.Value'), $params->at(1)->constraint()->type()); } #[Test] public function getReturnType() { Assert::equals( 'lang.Value', - typeof($this->fixture)->getMethod('get')->getReturnTypeName() + Reflection::type($this->fixture)->method('get')->returns()->type()->getName() ); } @@ -97,7 +97,7 @@ public function getReturnType() { public function valuesReturnType() { Assert::equals( 'lang.Value[]', - typeof($this->fixture)->getMethod('values')->getReturnTypeName() + Reflection::type($this->fixture)->method('values')->returns()->type()->getName() ); } } \ No newline at end of file diff --git a/src/test/php/lang/unittest/MethodAnnotationsTest.class.php b/src/test/php/lang/unittest/MethodAnnotationsTest.class.php deleted file mode 100755 index bed4d287f..000000000 --- a/src/test/php/lang/unittest/MethodAnnotationsTest.class.php +++ /dev/null @@ -1,81 +0,0 @@ -method('public function fixture() { }')->hasAnnotations()); - } - - #[Test] - public function has_annotations_when_present() { - Assert::true($this->method("#[Test]\npublic function fixture() { }")->hasAnnotations()); - } - - #[Test] - public function annotations_are_empty_by_default() { - Assert::equals([], $this->method('public function fixture() { }')->getAnnotations()); - } - - #[Test] - public function test_annotation() { - Assert::equals( - ['test' => null], - $this->method("#[Test]\npublic function fixture() { }")->getAnnotations() - ); - } - - #[Test] - public function two_annotations() { - Assert::equals( - ['test' => null, 'limit' => 20], - $this->method("#[Test, Limit(20)]\npublic function fixture() { }")->getAnnotations() - ); - } - - #[Test] - public function has_annotation_when_absent() { - Assert::false($this->method('public function fixture() { }')->hasAnnotation('test')); - } - - #[Test] - public function has_annotation_for_existant_annotation() { - Assert::true($this->method("#[Test]\npublic function fixture() { }")->hasAnnotation('test')); - } - - #[Test] - public function has_annotation_for_non_existant_annotation() { - Assert::false($this->method("#[Test]\npublic function fixture() { }")->hasAnnotation('@@nonexistant@@')); - } - - #[Test, Expect(ElementNotFoundException::class)] - public function get_annotation_when_absent() { - $this->method('public function fixture() { }')->getAnnotation('test'); - } - - #[Test] - public function get_annotation_for_existant_annotation() { - Assert::null($this->method("#[Test]\npublic function fixture() { }")->getAnnotation('test')); - } - - #[Test] - public function get_annotation_for_existant_annotation_with_value() { - Assert::equals(20, $this->method("#[Limit(20)]\npublic function fixture() { }")->getAnnotation('limit')); - } - - #[Test, Expect(ElementNotFoundException::class)] - public function get_annotation_for_non_existant_annotation() { - $this->method("#[Test]\npublic function fixture() { }")->getAnnotation('@@nonexistant@@'); - } - - #[Test] - public function parameter_annotation() { - Assert::true($this->method("public function fixture(\n#[Inject]\n\$arg) { }") - ->getParameter(0) - ->hasAnnotation('inject') - ); - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/MethodBasicsTest.class.php b/src/test/php/lang/unittest/MethodBasicsTest.class.php deleted file mode 100755 index 6d0107d16..000000000 --- a/src/test/php/lang/unittest/MethodBasicsTest.class.php +++ /dev/null @@ -1,131 +0,0 @@ -type(); - $equals= $fixture->getMethod('equals'); - foreach ($fixture->getMethods() as $method) { - if ($equals->equals($method)) return; - } - $this->fail('Equals method not contained', null, $fixture->getMethods()); - } - - #[Test, Ignore('TODO: Add parent')] - public function declared_methods_does_not_contain_hashCode_from_Object() { - $fixture= $this->type(); - $equals= $fixture->getMethod('equals'); - foreach ($fixture->getDeclaredMethods() as $method) { - if ($equals->equals($method)) $this->fail('Equals method contained', null, $fixture->getDeclaredMethods()); - } - } - - #[Test] - public function declaring_class() { - $fixture= $this->type('{ public function declared() { }}'); - Assert::equals($fixture, $fixture->getMethod('declared')->getDeclaringClass()); - } - - #[Test, Ignore('TODO: Add parent')] - public function declaring_class_of_inherited_method() { - $fixture= $this->type(); - Assert::equals($fixture->getParentclass(), $fixture->getMethod('equals')->getDeclaringClass()); - } - - #[Test] - public function has_method_for_existant() { - Assert::true($this->type('{ public function declared() { }}')->hasMethod('declared')); - } - - #[Test] - public function has_method_for_non_existant() { - Assert::false($this->type()->hasMethod('@@nonexistant@@')); - } - - #[Test, Values(['__construct', '__destruct', '__static', '__import'])] - public function has_method_for_special($named) { - Assert::false($this->type()->hasMethod($named)); - } - - #[Test] - public function get_existant_method() { - Assert::instance(Method::class, $this->type('{ public function declared() { }}')->getMethod('declared')); - } - - #[Test, Expect(ElementNotFoundException::class)] - public function get_non_existant_method() { - $this->type()->getMethod('@@nonexistant@@'); - } - - #[Test, Expect(ElementNotFoundException::class), Values(['__construct', '__destruct', '__static', '__import'])] - public function get_method_for_special($named) { - $this->type()->getMethod($named); - } - - #[Test] - public function name() { - Assert::equals('fixture', $this->method('public function fixture() { }')->getName()); - } - - #[Test] - public function no_parameters() { - Assert::equals(0, $this->method('public function fixture() { }')->numParameters()); - } - - #[Test] - public function one_parameter() { - Assert::equals(1, $this->method('public function fixture($param) { }')->numParameters()); - } - - #[Test] - public function two_parameters() { - Assert::equals(2, $this->method('public function fixture($a, $b) { }')->numParameters()); - } - - #[Test] - public function with_comment() { - Assert::equals('Test', $this->method('/** Test */ public function fixture() { }')->getComment()); - } - - #[Test] - public function without_comment() { - Assert::equals('', $this->method('public function fixture() { }')->getComment()); - } - - #[Test] - public function equality() { - $fixture= $this->type('{ public function hashCode() { } }'); - Assert::equals($fixture->getMethod('hashCode'), $fixture->getMethod('hashCode')); - } - - #[Test, Ignore('TODO: Add parent')] - public function a_method_is_not_equal_to_parent_method() { - $fixture= $this->type('{ public function hashCode() { } }'); - Assert::notEquals($fixture->getMethod('hashCode'), $fixture->getParentclass()->getMethod('hashCode')); - } - - #[Test] - public function a_method_is_not_equal_to_null() { - Assert::notEquals($this->method('public function fixture() { }'), null); - } - - #[Test, Values([['public function fixture() { }', 'public var fixture()'], ['private function fixture() { }', 'private var fixture()'], ['protected function fixture() { }', 'protected var fixture()'], ['static function fixture() { }', 'public static var fixture()'], ['private static function fixture() { }', 'private static var fixture()'], ['protected static function fixture() { }', 'protected static var fixture()'], ['public function fixture($param) { }', 'public var fixture(var $param)'], ['/** @return void */ public function fixture() { }', 'public void fixture()'], ['/** @param string[] */ public function fixture($param) { }', 'public var fixture(string[] $param)'], ['/** @throws lang.IllegalAccessException */ public function fixture() { }', 'public var fixture() throws lang.IllegalAccessException']])] - public function string_representation($declaration, $expected) { - Assert::equals($expected, $this->method($declaration)->toString()); - } - - #[Test] - public function trait_comment() { - Assert::equals('Compares a given value to this', $this->type()->getMethod('compareTo')->getComment()); - } - - #[Test] - public function trait_return_type() { - Assert::equals('int', $this->type()->getMethod('compareTo')->getReturnTypeName()); - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/MethodExceptionTypesTest.class.php b/src/test/php/lang/unittest/MethodExceptionTypesTest.class.php deleted file mode 100755 index 2e39d3e9c..000000000 --- a/src/test/php/lang/unittest/MethodExceptionTypesTest.class.php +++ /dev/null @@ -1,61 +0,0 @@ -method('public function fixture() { }')->getExceptionTypes()); - } - - #[Test] - public function thrown_exception_names_are_empty_by_default() { - Assert::equals([], $this->method('public function fixture() { }')->getExceptionNames()); - } - - #[Test, Values([['/** @throws lang.IllegalAccessException */'], ['/** @throws \lang\IllegalAccessException */']])] - public function thrown_exception_via_compact_apidoc($apidoc) { - Assert::equals( - [new XPClass(IllegalAccessException::class)], - $this->method($apidoc.' public function fixture() { }')->getExceptionTypes() - ); - } - - #[Test] - public function thrown_exception_name_via_compact_apidoc() { - Assert::equals( - ['lang.IllegalAccessException'], - $this->method('/** @throws lang.IllegalAccessException */ public function fixture() { }')->getExceptionNames() - ); - } - - #[Test] - public function thrown_exceptions_via_apidoc() { - Assert::equals( - [new XPClass(IllegalAccessException::class), new XPClass(IllegalArgumentException::class)], - $this->method(' - /** - * @throws lang.IllegalAccessException - * @throws lang.IllegalArgumentException - */ - public function fixture() { } - ')->getExceptionTypes() - ); - } - - #[Test] - public function thrown_exception_names_via_apidoc() { - Assert::equals( - ['lang.IllegalAccessException', 'lang.IllegalArgumentException'], - $this->method(' - /** - * @throws lang.IllegalAccessException - * @throws lang.IllegalArgumentException - */ - public function fixture() { } - ')->getExceptionNames() - ); - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/MethodInvocationTest.class.php b/src/test/php/lang/unittest/MethodInvocationTest.class.php deleted file mode 100755 index 7943527cc..000000000 --- a/src/test/php/lang/unittest/MethodInvocationTest.class.php +++ /dev/null @@ -1,75 +0,0 @@ -type('{ public function fixture() { return "Test"; } }'); - Assert::equals('Test', $fixture->getMethod('fixture')->invoke($fixture->newInstance(), [])); - } - - #[Test] - public function invoke_static() { - $fixture= $this->type('{ public static function fixture() { return "Test"; } }'); - Assert::equals('Test', $fixture->getMethod('fixture')->invoke(null, [])); - } - - #[Test] - public function invoke_passes_arguments() { - $fixture= $this->type('{ public function fixture($a, $b) { return $a + $b; } }'); - Assert::equals(3, $fixture->getMethod('fixture')->invoke($fixture->newInstance(), [1, 2])); - } - - #[Test] - public function invoke_method_without_return() { - $fixture= $this->type('{ public function fixture() { } }'); - Assert::null($fixture->getMethod('fixture')->invoke($fixture->newInstance(), [])); - } - - #[Test, Expect(TargetInvocationException::class)] - public function cannot_invoke_instance_method_without_object() { - $fixture= $this->type('{ public function fixture() { } }'); - $fixture->getMethod('fixture')->invoke(null, []); - } - - #[Test, Expect(TargetInvocationException::class)] - public function exceptions_raised_during_invocation_are_wrapped() { - $fixture= $this->type('{ public function fixture() { throw new \lang\IllegalAccessException("Test"); } }'); - $fixture->getMethod('fixture')->invoke($fixture->newInstance(), []); - } - - #[Test, Expect(TargetInvocationException::class), Runtime(php: '>=7.0')] - public function exceptions_raised_for_return_type_violations() { - $fixture= $this->type('{ public function fixture(): array { return null; } }'); - $fixture->getMethod('fixture')->invoke($fixture->newInstance(), []); - } - - #[Test, Expect(TargetInvocationException::class), Runtime(php: '>=7.0')] - public function exceptions_raised_for_parameter_type_violations() { - $fixture= $this->type('{ public function fixture(int $i) { } }'); - $fixture->getMethod('fixture')->invoke($fixture->newInstance(), ['abc']); - } - - #[Test, Expect(IllegalArgumentException::class)] - public function cannot_invoke_instance_method_with_incompatible() { - $fixture= $this->type('{ public function fixture() { } }'); - $fixture->getMethod('fixture')->invoke($this, []); - } - - #[Test, Expect(IllegalAccessException::class), Values([['{ private function fixture() { } }'], ['{ protected function fixture() { } }'], ['{ public abstract function fixture(); }', 'abstract'],])] - public function cannot_invoke_non_public($declaration, $modifiers= '') { - $fixture= $this->type($declaration, ['modifiers' => $modifiers]); - $fixture->getMethod('fixture')->invoke($fixture->newInstance(), []); - } - - #[Test, Values([['{ private function fixture() { return "Test"; } }'], ['{ protected function fixture() { return "Test"; } }'],])] - public function can_invoke_private_or_protected_via_setAccessible($declaration) { - $fixture= $this->type($declaration); - Assert::equals('Test', $fixture->getMethod('fixture')->setAccessible(true)->invoke($fixture->newInstance(), [])); - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/MethodModifiersTest.class.php b/src/test/php/lang/unittest/MethodModifiersTest.class.php deleted file mode 100755 index 4cf34557a..000000000 --- a/src/test/php/lang/unittest/MethodModifiersTest.class.php +++ /dev/null @@ -1,36 +0,0 @@ -method('public function fixture() { }')->getModifiers()); - } - - #[Test] - public function private_modifier() { - Assert::equals(MODIFIER_PRIVATE, $this->method('private function fixture() { }')->getModifiers()); - } - - #[Test] - public function protected_modifier() { - Assert::equals(MODIFIER_PROTECTED, $this->method('protected function fixture() { }')->getModifiers()); - } - - #[Test] - public function final_modifier() { - Assert::equals(MODIFIER_FINAL | MODIFIER_PUBLIC, $this->method('public final function fixture() { }')->getModifiers()); - } - - #[Test] - public function static_modifier() { - Assert::equals(MODIFIER_STATIC | MODIFIER_PUBLIC, $this->method('public static function fixture() { }')->getModifiers()); - } - - #[Test] - public function abstract_modifier() { - Assert::equals(MODIFIER_ABSTRACT | MODIFIER_PUBLIC, $this->method('public abstract function fixture();', 'abstract')->getModifiers()); - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/MethodParametersTest.class.php b/src/test/php/lang/unittest/MethodParametersTest.class.php deleted file mode 100755 index 029850614..000000000 --- a/src/test/php/lang/unittest/MethodParametersTest.class.php +++ /dev/null @@ -1,407 +0,0 @@ -getType(), 'type'); - Assert::equals($expected->getName(), $param->getTypeName(), 'name'); - } - - #[Test] - public function parameter_type_defaults_to_var() { - $this->assertParamType(Type::$VAR, $this->method('public function fixture($param) { }')->getParameter(0)); - } - - #[Test, Values(from: 'types')] - public function parameter_type_determined_via_apidoc($declaration, $type) { - $this->assertParamType( - $type, - $this->method('/** @param '.$declaration.' */ public function fixture($param) { }')->getParameter(0) - ); - } - - #[Test, Values(from: 'arrays')] - public function specific_array_type_determined_via_apidoc_if_present($declaration, $type) { - $this->assertParamType( - $type, - $this->method('/** @param '.$declaration.' */ public function fixture(array $param) { }')->getParameter(0) - ); - } - - #[Test] - public function specific_callable_type_determined_via_apidoc_if_present() { - $this->assertParamType( - new FunctionType([], Primitive::$STRING), - $this->method('/** @param (function(): string) */ public function fixture(callable $param) { }')->getParameter(0) - ); - } - - #[Test, Values([['\lang\Value', Value::class], ['\lang\unittest\Name', Name::class], ['Value', Value::class]])] - public function parameter_type_determined_via_syntax($literal, $type) { - $this->assertParamType( - new XPClass($type), - $this->method('public function fixture('.$literal.' $param) { }')->getParameter(0) - ); - } - - #[Test, Runtime(php: '>=7.0'), Values([['string'], ['int'], ['bool'], ['float']])] - public function parameter_type_determined_via_scalar_syntax($literal) { - $this->assertParamType( - Primitive::forName($literal), - $this->method('public function fixture('.$literal.' $param) { }')->getParameter(0) - ); - } - - #[Test, Runtime(php: '>=7.1')] - public function nullable_parameter_type() { - $fixture= $this->type('{ public function fixture(?string $arg) { } }'); - $this->assertParamType( - new Nullable(Primitive::$STRING), - $fixture->getMethod('fixture')->getParameter(0) - ); - } - - #[Test, Runtime(php: '>=8.0'), Values([['string|int'], ['string|false']])] - public function parameter_type_determined_via_union_syntax($literal) { - $this->assertParamType( - TypeUnion::forName($literal), - $this->method('public function fixture('.$literal.' $param) { }')->getParameter(0) - ); - } - - #[Test] - public function self_parameter_type() { - $fixture= $this->type('{ public function fixture(self $param) { } }'); - Assert::equals($fixture, $fixture->getMethod('fixture')->getParameter(0)->getType()); - } - - #[Test] - public function self_parameter_typeName() { - $fixture= $this->type('{ public function fixture(self $param) { } }'); - Assert::equals('self', $fixture->getMethod('fixture')->getParameter(0)->getTypeName()); - } - - #[Test] - public function self_parameter_type_via_apidoc() { - $fixture= $this->type('{ /** @param self $param */ public function fixture($param) { } }'); - Assert::equals($fixture, $fixture->getMethod('fixture')->getParameter(0)->getType()); - } - - #[Test] - public function self_parameter_typeName_via_apidoc() { - $fixture= $this->type('{ /** @param self $param */ public function fixture($param) { } }'); - Assert::equals('self', $fixture->getMethod('fixture')->getParameter(0)->getTypeName()); - } - - #[Test] - public function array_of_self_parameter_type_via_apidoc() { - $fixture= $this->type('{ /** @param array */ public function fixture($list) { } }'); - Assert::equals(new ArrayType($fixture), $fixture->getMethod('fixture')->getParameter(0)->getType()); - } - - #[Test] - public function parent_parameter_type() { - $fixture= $this->type('{ public function fixture(parent $param) { } }', [ - 'extends' => [Name::class] - ]); - Assert::equals($fixture->getParentclass(), $fixture->getMethod('fixture')->getParameter(0)->getType()); - } - - #[Test] - public function parent_parameter_typeName() { - $fixture= $this->type('{ public function fixture(parent $param) { } }', [ - 'extends' => [Name::class] - ]); - Assert::equals('parent', $fixture->getMethod('fixture')->getParameter(0)->getTypeName()); - } - - #[Test] - public function parent_parameter_type_via_apidoc() { - $fixture= $this->type('{ /** @param parent $param */ public function fixture($param) { } }', [ - 'extends' => [Name::class] - ]); - Assert::equals($fixture->getParentclass(), $fixture->getMethod('fixture')->getParameter(0)->getType()); - } - - #[Test] - public function parent_parameter_typeName_via_apidoc() { - $fixture= $this->type('{ /** @param parent $param */ public function fixture($param) { } }', [ - 'extends' => [Name::class] - ]); - Assert::equals('parent', $fixture->getMethod('fixture')->getParameter(0)->getTypeName()); - } - - #[Test, Expect(ClassNotFoundException::class)] - public function nonexistant_type_class_parameter() { - $this->method('public function fixture(UnknownTypeRestriction $param) { }')->getParameter(0)->getType(); - } - - #[Test] - public function nonexistant_name_class_parameter() { - Assert::equals( - 'lang.unittest.UnknownTypeRestriction', - $this->method('public function fixture(UnknownTypeRestriction $param) { }')->getParameter(0)->getTypeName() - ); - } - - #[Test] - public function unrestricted_parameter() { - Assert::null($this->method('public function fixture($param) { }')->getParameter(0)->getTypeRestriction()); - } - - #[Test] - public function self_restricted_parameter() { - $fixture= $this->type('{ public function fixture(self $param) { } }'); - Assert::equals( - $fixture, - $fixture->getMethod('fixture')->getParameter(0)->getTypeRestriction() - ); - } - - #[Test] - public function unrestricted_parameter_with_apidoc() { - Assert::null( - $this->method('/** @param lang.Value */ public function fixture($param) { }')->getParameter(0)->getTypeRestriction() - ); - } - - #[Test, Values(from: 'restrictions')] - public function type_restriction_determined_via_syntax($literal, $type) { - Assert::equals( - $type, - $this->method('public function fixture('.$literal.' $param) { }')->getParameter(0)->getTypeRestriction() - ); - } - - #[Test, Expect(ClassNotFoundException::class)] - public function nonexistant_restriction_class_parameter() { - $this->method('public function fixture(UnknownTypeRestriction $param) { }')->getParameter(0)->getTypeRestriction(); - } - - #[Test] - public function zero_parameters() { - Assert::equals(0, $this->method('public function fixture() { }')->numParameters()); - } - - #[Test] - public function three_parameters() { - Assert::equals(3, $this->method('public function fixture($a, $b, $c) { }')->numParameters()); - } - - #[Test] - public function no_parameters() { - Assert::equals([], $this->method('public function fixture() { }')->getParameters()); - } - - #[Test] - public function parameter_names() { - Assert::equals(['a', 'b', 'c'], array_map( - function($p) { return $p->getName(); }, - $this->method('public function fixture($a, $b, $c) { }')->getParameters() - )); - } - - #[Test, Values([-1, 0, 1])] - public function accessing_a_parameter_via_non_existant_offset($offset) { - Assert::null($this->method('public function fixture() { }')->getParameter($offset)); - } - - /** @return lang.reflect.Parameter */ - private function annotatedParameter() { - try { - $p= $this->method("#[@\$param: test('value')]\npublic function fixture(\$param) { }")->getParameter(0); - $p->getAnnotations(); - return $p; - } finally { - \xp::gc(); // Strip deprecation warnings - } - } - - #[Test, Runtime(php: '<8.0')] - public function annotated_parameter() { - Assert::true($this->annotatedParameter()->hasAnnotations()); - } - - #[Test, Runtime(php: '<8.0')] - public function parameter_annotated_with_test_has_test_annotation() { - Assert::true($this->annotatedParameter()->hasAnnotation('test')); - } - - #[Test, Runtime(php: '<8.0')] - public function parameter_annotated_with_test_has_no_limit_annotation() { - Assert::false($this->annotatedParameter()->hasAnnotation('limit')); - } - - #[Test, Runtime(php: '<8.0')] - public function annotations_of_parameter_annotated_with_test() { - Assert::equals(['test' => 'value'], $this->annotatedParameter()->getAnnotations()); - } - - #[Test, Runtime(php: '<8.0')] - public function test_annotation_of_parameter_annotated_with_test() { - Assert::equals('value', $this->annotatedParameter()->getAnnotation('test')); - } - - #[Test] - public function parameters_with_attribute() { - $method= $this->method(' - #[Get] - public function fixture( - $user, - #[Param] - $sort, - #[Param("max")] - $limit, - #[Param, Optional] - $order= "asc" - ) { }' - ); - $r= []; - foreach ($method->getParameters() as $param) { - $r[$param->getName()]= $param->getAnnotations(); - } - Assert::equals( - [ - 'user' => [], - 'sort' => ['param' => null], - 'limit' => ['param' => 'max'], - 'order' => ['param' => null, 'optional' => null] - ], - $r - ); - } - - #[Test] - public function un_annotated_parameter_has_no_annotations() { - Assert::false($this->method('public function fixture($param) { }')->getParameter(0)->hasAnnotations()); - } - - #[Test] - public function un_annotated_parameter_annotations_are_empty() { - Assert::equals([], $this->method('public function fixture($param) { }')->getParameter(0)->getAnnotations()); - } - - #[Test, Expect(class: ElementNotFoundException::class, message: 'Annotation "test" does not exist')] - public function cannot_get_test_annotation_for_un_annotated_parameter() { - $this->method('public function fixture($param) { }')->getParameter(0)->getAnnotation('test'); - } - - #[Test] - public function required_parameter() { - Assert::false($this->method('public function fixture($param) { }')->getParameter(0)->isOptional()); - } - - #[Test] - public function optional_parameter() { - Assert::true($this->method('public function fixture($param= true) { }')->getParameter(0)->isOptional()); - } - - #[Test, Expect(class: IllegalStateException::class, message: 'Parameter "param" has no default value')] - public function required_parameter_does_not_have_default_value() { - $this->method('public function fixture($param) { }')->getParameter(0)->getDefaultValue(); - } - - #[Test] - public function optional_parameters_default_value() { - Assert::equals(true, $this->method('public function fixture($param= true) { }')->getParameter(0)->getDefaultValue()); - } - - #[Test] - public function default_annotation_may_supply_default_value() { - $method= $this->method('public function fixture($param= null) { }'); - - // Directly modify meta information for this test's purpose - // See https://github.com/xp-framework/compiler/pull/104#issuecomment-791924395 - \xp::$meta[$method->getDeclaringClass()->getName()][1][$method->getName()]= [ - DETAIL_TARGET_ANNO => ['$param' => ['default' => $this]] - ]; - - Assert::equals($this, $method->getParameter(0)->getDefaultValue()); - } - - #[Test] - public function vararg_parameters_default_value() { - Assert::equals(null, $this->method('public function fixture(... $param) { }')->getParameter(0)->getDefaultValue()); - } - - #[Test, Values([['/** @param string */ function fixture($a)', 'lang.reflect.Parameter a>'], ['/** @param lang.Value */ function fixture($a)', 'lang.reflect.Parameter a>'], ['/** @param \lang\Value */ function fixture($a)', 'lang.reflect.Parameter a>'], ['function fixture(\lang\Value $a)', 'lang.reflect.Parameter a>'], ['/** @param var[] */ function fixture($a)', 'lang.reflect.Parameter a>'], ['/** @param function(string): int */ function fixture($a)', 'lang.reflect.Parameter a>'], ['/** @param bool */ function fixture($a= true)', 'lang.reflect.Parameter a= true>']])] - public function parameter_representations($declaration, $expected) { - Assert::equals($expected, $this->method($declaration.' { }')->getParameter(0)->toString()); - } - - #[Test] - public function variadic_via_syntax_with_type() { - $param= $this->method('function fixture(string... $args) { }')->getParameter(0); - Assert::equals( - ['variadic' => true, 'optional' => true, 'type' => Primitive::$STRING], - ['variadic' => $param->isVariadic(), 'optional' => $param->isOptional(), 'type' => $param->getType()] - ); - } - - #[Test] - public function variadic_via_syntax() { - $param= $this->method('function fixture(... $args) { }')->getParameter(0); - Assert::equals( - ['variadic' => true, 'optional' => true, 'type' => Type::$VAR], - ['variadic' => $param->isVariadic(), 'optional' => $param->isOptional(), 'type' => $param->getType()] - ); - } - - #[Test] - public function variadic_via_apidoc() { - $param= $this->method('/** @param var... $args */ function fixture($args= null) { }')->getParameter(0); - Assert::equals( - ['variadic' => true, 'optional' => true, 'type' => Type::$VAR], - ['variadic' => $param->isVariadic(), 'optional' => $param->isOptional(), 'type' => $param->getType()] - ); - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/MethodReturnTypesTest.class.php b/src/test/php/lang/unittest/MethodReturnTypesTest.class.php deleted file mode 100755 index 6abb64a72..000000000 --- a/src/test/php/lang/unittest/MethodReturnTypesTest.class.php +++ /dev/null @@ -1,212 +0,0 @@ -getReturnType(), 'type'); - Assert::equals($expected->getName(), $method->getReturnTypeName(), 'name'); - } - - /** @return iterable */ - private function types() { - yield ['void', Type::$VOID]; - yield ['var', Type::$VAR]; - yield ['bool', Primitive::$BOOL]; - yield ['string[]', new ArrayType(Primitive::$STRING)]; - yield ['[:int]', new MapType(Primitive::$INT)]; - yield ['lang.Value', new XPClass(Value::class)]; - yield ['Value', new XPClass(Value::class)]; - yield ['\\lang\\Value', new XPClass(Value::class)]; - } - - /** @return iterable */ - private function arrays() { - yield ['string[]', new ArrayType(Primitive::$STRING)]; - yield ['[:int]', new MapType(Primitive::$INT)]; - } - - /** @return iterable */ - private function restrictions() { - yield ['string', Primitive::$STRING]; - yield ['array', Type::$ARRAY]; - yield ['callable', Type::$CALLABLE]; - yield ['\lang\Value', new XPClass(Value::class)]; - yield ['Value', new XPClass(Value::class)]; - } - - #[Test] - public function return_type_defaults_to_var() { - $this->assertReturnType(Type::$VAR, $this->method('public function fixture() { }')); - } - - #[Test] - public function return_type_restriction_defaults_to_null() { - Assert::null($this->method('public function fixture() { }')->getReturnTypeRestriction()); - } - - #[Test] - public function return_type_restriction() { - Assert::equals( - new XPClass(Value::class), - $this->method('public function fixture(): Value { }')->getReturnTypeRestriction() - ); - } - - #[Test] - public function return_type_inherited() { - $this->assertReturnType( - Primitive::$STRING, - $this->type('{ }', ['extends' => [Name::class]])->getMethod('hashCode') - ); - } - - #[Test, Values(from: 'types')] - public function return_type_determined_via_apidoc($declaration, $type) { - $this->assertReturnType( - $type, - $this->method('/** @return '.$declaration.' */ public function fixture() { }') - ); - } - - #[Test, Values(from: 'restrictions')] - public function return_type_determined_via_syntax($literal, $type) { - $this->assertReturnType($type, $this->method('public function fixture(): '.$literal.' { }')); - } - - #[Test, Runtime(php: '>=7.1')] - public function void_return_type() { - $fixture= $this->type('{ public function fixture(): void { } }'); - $this->assertReturnType(Type::$VOID, $fixture->getMethod('fixture')); - } - - #[Test, Runtime(php: '>=8.1')] - public function never_return_type() { - $fixture= $this->type('{ public function fixture(): never { exit(); } }'); - $this->assertReturnType(Type::$NEVER, $fixture->getMethod('fixture')); - } - - #[Test, Runtime(php: '>=7.1')] - public function nullable_return_type() { - $fixture= $this->type('{ public function fixture(): ?string { } }'); - $this->assertReturnType(new Nullable(Primitive::$STRING), $fixture->getMethod('fixture')); - } - - #[Test, Runtime(php: '>=8.0'), Values([['string|int'], ['string|false']])] - public function return_type_determined_via_union_syntax($literal) { - $this->assertReturnType( - TypeUnion::forName($literal), - $this->method('public function fixture(): '.$literal.' { }') - ); - } - - #[Test, Values(from: 'arrays')] - public function specific_array_type_determined_via_apidoc_if_present($declaration, $type) { - $this->assertReturnType( - $type, - $this->method('/** @return '.$declaration.' */ public function fixture(): array { }') - ); - } - - #[Test] - public function specific_callable_type_determined_via_apidoc_if_present() { - $this->assertReturnType( - new FunctionType([], Primitive::$STRING), - $this->method('/** @return (function(): string) */ public function fixture(): callable { }') - ); - } - - #[Test] - public function special_self_return_type_via_apidoc() { - $fixture= $this->type('{ /** @return self */ public function fixture() { } }'); - Assert::equals($fixture, $fixture->getMethod('fixture')->getReturnType()); - } - - #[Test] - public function special_self_return_type_via_syntax() { - $fixture= $this->type('{ public function fixture(): self { } }'); - Assert::equals($fixture, $fixture->getMethod('fixture')->getReturnType()); - } - - #[Test] - public function special_parent_return_type_via_apidoc() { - $fixture= $this->type('{ /** @return parent */ public function fixture() { } }', [ - 'extends' => [Name::class] - ]); - Assert::equals($fixture->getParentclass(), $fixture->getMethod('fixture')->getReturnType()); - } - - #[Test] - public function special_parent_return_type_via_syntax() { - $fixture= $this->type('{ public function fixture(): parent { } }', [ - 'extends' => [Name::class] - ]); - Assert::equals($fixture->getParentclass(), $fixture->getMethod('fixture')->getReturnType()); - } - - #[Test] - public function special_static_return_type_in_base_class() { - $fixture= new XPClass(Name::class); - Assert::equals($fixture, $fixture->getMethod('copy')->getReturnType()); - } - - #[Test] - public function special_static_return_type_in_inherited_class() { - $fixture= $this->type('{ }', ['extends' => [Name::class]]); - Assert::equals($fixture, $fixture->getMethod('copy')->getReturnType()); - } - - #[Test, Runtime(php: '>=8.0')] - public function special_static_return_type_via_syntax() { - $fixture= $this->type('{ public function fixture(): static { } }'); - Assert::equals($fixture, $fixture->getMethod('fixture')->getReturnType()); - } - - #[Test, Values([['/** @return static */', 'static'], ['/** @return self */', 'self'], ['/** @return parent */', 'parent'],])] - public function special_typeName_determined_via_apidoc($apidoc, $type) { - Assert::equals($type, $this->method($apidoc.' public function fixture() { }')->getReturnTypeName()); - } - - #[Test] - public function apidoc_supersedes_self_type_restriction() { - $base= $this->type('{ /** @return static */ public function fixture(): self { } }'); - $fixture= $this->type('{ /* inherited with apidoc */ }', ['extends' => [$base]]); - $method= $fixture->getMethod('fixture'); - - Assert::equals($fixture, $method->getReturnType(), 'type'); - Assert::equals('static', $method->getReturnTypeName(), 'name'); - } - - #[Test, Runtime(php: '>=7.1')] - public function apidoc_supersedes_void_type_restriction() { - $method= $this->type('{ /** @return never */ public function fixture(): void { exit(); } }')->getMethod('fixture'); - - Assert::equals(Type::$NEVER, $method->getReturnType(), 'type'); - Assert::equals('never', $method->getReturnTypeName(), 'name'); - } - - #[Test] - public function self_type_restriction_inheritance() { - $base= $this->type('{ public function fixture(): self { } }'); - $fixture= $this->type('{ /* inherited without apidoc */ }', ['extends' => [$base]]); - $method= $fixture->getMethod('fixture'); - - Assert::equals($base, $method->getReturnType(), 'type'); - Assert::equals('self', $method->getReturnTypeName(), 'name'); - } - - #[Test] - public function array_of_special_self_type() { - $fixture= $this->type('{ /** @return array */ public function fixture() { } }'); - Assert::equals(new ArrayType($fixture), $fixture->getMethod('fixture')->getReturnType()); - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/MethodsTest.class.php b/src/test/php/lang/unittest/MethodsTest.class.php deleted file mode 100755 index 953c7c4af..000000000 --- a/src/test/php/lang/unittest/MethodsTest.class.php +++ /dev/null @@ -1,16 +0,0 @@ -type('{ '.$decl.' }', ['modifiers' => $modifiers])->getMethod('fixture'); - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/NewInstanceTest.class.php b/src/test/php/lang/unittest/NewInstanceTest.class.php index f0bb35791..f146701c4 100755 --- a/src/test/php/lang/unittest/NewInstanceTest.class.php +++ b/src/test/php/lang/unittest/NewInstanceTest.class.php @@ -1,9 +1,9 @@ hasAnnotation('test')); + Assert::equals('test', Reflection::type($o)->annotation('lang.unittest.Test')->name()); } #[Test] @@ -82,7 +82,7 @@ public function new_class_with_field_annotations() { $o= newinstance(Name::class, ['Test'], [ '#[Test] fixture' => null ]); - Assert::true(typeof($o)->getField('fixture')->hasAnnotation('test')); + Assert::equals('test', Reflection::type($o)->property('fixture')->annotation('lang.unittest.Test')->name()); } #[Test] @@ -90,7 +90,7 @@ public function new_class_with_method_annotations() { $o= newinstance(Name::class, ['Test'], [ '#[Test] fixture' => function() { } ]); - Assert::true(typeof($o)->getMethod('fixture')->hasAnnotation('test')); + Assert::equals('test', Reflection::type($o)->method('fixture')->annotation('lang.unittest.Test')->name()); } #[Test] @@ -129,7 +129,7 @@ public function new_interface_with_annotations() { $o= newinstance('#[Test] lang.Runnable', [], [ 'run' => function() { } ]); - Assert::true(typeof($o)->hasAnnotation('test')); + Assert::true(Reflection::type($o)->annotations()->provides('lang.Test')); } #[Test] @@ -153,7 +153,7 @@ public function new_trait_with_annotations() { $o= newinstance('#[Test] lang.unittest.Named', [], [ 'run' => function() { } ]); - Assert::true(typeof($o)->hasAnnotation('test')); + Assert::true(Reflection::type($o)->annotations()->provides('lang.unittest.Test')); } #[Test] diff --git a/src/test/php/lang/unittest/NoEndingBracket.class.php b/src/test/php/lang/unittest/NoEndingBracket.class.php deleted file mode 100755 index 58c1ed301..000000000 --- a/src/test/php/lang/unittest/NoEndingBracket.class.php +++ /dev/null @@ -1,6 +0,0 @@ -getConstructor()->newInstance([]); - } - - /** - * Entry point: Invoke target method - * - * @param lang.XPClass - * @return string - */ - public static function invoke(XPClass $class) { - return $class->getMethod('target')->invoke(new self()); - } - - /** - * Entry point: Invoke staticTarget method - * - * @param lang.XPClass - * @return string - */ - public static function invokeStatic(XPClass $class) { - return $class->getMethod('staticTarget')->invoke(null); - } - - /** - * Entry point: Read target member - * - * @param lang.XPClass - * @return string - */ - public static function read(XPClass $class) { - return $class->getField('target')->get(new self()); - } - - /** - * Entry point: Read staticTarget member - * - * @param lang.XPClass - * @return string - */ - public static function readStatic(XPClass $class) { - return $class->getField('staticTarget')->get(null); - } - - /** - * Entry point: Write target member, then read it back - * - * @param lang.XPClass - * @return string - */ - public static function write(XPClass $class) { - return with (new self(), $class->getField('target'), function($self, $f) { - $f->set($self, 'Modified'); - return $f->get($self); - }); - } - - /** - * Entry point: Write target member, then read it back - * - * @param lang.XPClass - * @return string - */ - public static function writeStatic(XPClass $class) { - return with ($class->getField('staticTarget'), function($f) { - $f->set(null, 'Modified'); - return $f->get(null); - }); - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/PrivateAccessibilityFixtureChild.class.php b/src/test/php/lang/unittest/PrivateAccessibilityFixtureChild.class.php deleted file mode 100755 index d094b32a4..000000000 --- a/src/test/php/lang/unittest/PrivateAccessibilityFixtureChild.class.php +++ /dev/null @@ -1,77 +0,0 @@ -getMethod('target')->invoke(new self()); - } - - /** - * Entry point: Invoke staticTarget method - * - * @param lang.XPClass - * @return string - */ - public static function invokeStatic(\lang\XPClass $class) { - return $class->getMethod('staticTarget')->invoke(null); - } - - /** - * Entry point: Invoke target method - * - * @param lang.XPClass - * @return string - */ - public static function read(\lang\XPClass $class) { - return $class->getField('target')->get(new self()); - } - - /** - * Entry point: Read staticTarget member - * - * @param lang.XPClass - * @return string - */ - public static function readStatic(\lang\XPClass $class) { - return $class->getField('staticTarget')->get(null); - } - - /** - * Entry point: Write target member, then read it back - * - * @param lang.XPClass - * @return string - */ - public static function write(\lang\XPClass $class) { - with ($s= new self(), $f= $class->getField('target')); { - $f->set($s, 'Modified'); - return $f->get($s); - } - } - - /** - * Entry point: Write staticTarget member, then read it back - * - * @param lang.XPClass - * @return string - */ - public static function writeStatic(\lang\XPClass $class) { - with ($f= $class->getField('staticTarget')); { - $f->set(null, 'Modified'); - return $f->get(null); - } - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/PrivateAccessibilityFixtureCtorChild.class.php b/src/test/php/lang/unittest/PrivateAccessibilityFixtureCtorChild.class.php deleted file mode 100755 index 75a0fa120..000000000 --- a/src/test/php/lang/unittest/PrivateAccessibilityFixtureCtorChild.class.php +++ /dev/null @@ -1,14 +0,0 @@ -getConstructor()->newInstance([]); - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/PrivateAccessibilityTest.class.php b/src/test/php/lang/unittest/PrivateAccessibilityTest.class.php deleted file mode 100755 index a376d26ab..000000000 --- a/src/test/php/lang/unittest/PrivateAccessibilityTest.class.php +++ /dev/null @@ -1,218 +0,0 @@ -getConstructor()->newInstance([]); - } - - #[Test] - public function invokingPrivateConstructorFromSameClass() { - Assert::instance(self::$fixture, PrivateAccessibilityFixture::construct(self::$fixture)); - } - - #[Test, Expect(IllegalAccessException::class)] - public function invokingPrivateConstructorFromParentClass() { - PrivateAccessibilityFixtureCtorChild::construct(self::$fixture); - } - - #[Test, Expect(IllegalAccessException::class)] - public function invokingPrivateConstructorFromChildClass() { - PrivateAccessibilityFixtureCtorChild::construct(self::$fixtureChild); - } - - #[Test] - public function invokingPrivateConstructorMadeAccessible() { - Assert::instance(self::$fixture, self::$fixture - ->getConstructor() - ->setAccessible(true) - ->newInstance([]) - ); - } - - #[Test, Expect(IllegalAccessException::class)] - public function invokingPrivateMethod() { - self::$fixture->getMethod('target')->invoke(PrivateAccessibilityFixture::construct(self::$fixture)); - } - - #[Test] - public function invokingPrivateMethodFromSameClass() { - Assert::equals('Invoked', PrivateAccessibilityFixture::invoke(self::$fixture)); - } - - #[Test, Expect(IllegalAccessException::class)] - public function invokingPrivateMethodFromParentClass() { - PrivateAccessibilityFixtureChild::invoke(self::$fixture); - } - - #[Test, Expect(IllegalAccessException::class)] - public function invokingPrivateMethodFromChildClass() { - PrivateAccessibilityFixtureChild::invoke(self::$fixtureChild); - } - - #[Test] - public function invokingPrivateMethodMadeAccessible() { - Assert::equals('Invoked', self::$fixture - ->getMethod('target') - ->setAccessible(true) - ->invoke(PrivateAccessibilityFixture::construct(self::$fixture)) - ); - } - - #[Test, Expect(IllegalAccessException::class)] - public function invokingPrivateStaticMethod() { - self::$fixture->getMethod('staticTarget')->invoke(null); - } - - #[Test] - public function invokingPrivateStaticMethodFromSameClass() { - Assert::equals('Invoked', PrivateAccessibilityFixture::invokeStatic(self::$fixture)); - } - - #[Test, Expect(IllegalAccessException::class)] - public function invokingPrivateStaticMethodFromParentClass() { - PrivateAccessibilityFixtureChild::invokeStatic(self::$fixture); - } - - #[Test, Expect(IllegalAccessException::class)] - public function invokingPrivateStaticMethodFromChildClass() { - PrivateAccessibilityFixtureChild::invokeStatic(self::$fixtureChild); - } - - #[Test] - public function invokingPrivateStaticMethodMadeAccessible() { - Assert::equals('Invoked', self::$fixture - ->getMethod('staticTarget') - ->setAccessible(true) - ->invoke(null) - ); - } - - #[Test, Expect(IllegalAccessException::class)] - public function readingPrivateMember() { - self::$fixture->getField('target')->get(PrivateAccessibilityFixture::construct(self::$fixture)); - } - - #[Test] - public function readingPrivateMemberFromSameClass() { - Assert::equals('Target', PrivateAccessibilityFixture::read(self::$fixture)); - } - - #[Test, Expect(IllegalAccessException::class)] - public function readingPrivateMemberFromParentClass() { - PrivateAccessibilityFixtureChild::read(self::$fixture); - } - - #[Test, Ignore('$this->getClass()->getField($field) does not yield private field declared in parent class')] - public function readingPrivateMemberFromChildClass() { - PrivateAccessibilityFixtureChild::read(self::$fixtureChild); - } - - #[Test] - public function readingPrivateMemberMadeAccessible() { - Assert::equals('Target', self::$fixture - ->getField('target') - ->setAccessible(true) - ->get(PrivateAccessibilityFixture::construct(self::$fixture)) - ); - } - - #[Test, Expect(IllegalAccessException::class)] - public function readingPrivateStaticMember() { - self::$fixture->getField('staticTarget')->get(null); - } - - #[Test] - public function readingPrivateStaticMemberFromSameClass() { - Assert::equals('Target', PrivateAccessibilityFixture::readStatic(self::$fixture)); - } - - #[Test, Expect(IllegalAccessException::class)] - public function readingPrivateStaticMemberFromParentClass() { - PrivateAccessibilityFixtureChild::readStatic(self::$fixture); - } - - #[Test, Ignore('$this->getClass()->getField($field) does not yield private field declared in parent class')] - public function readingPrivateStaticMemberFromChildClass() { - PrivateAccessibilityFixtureChild::readStatic(self::$fixtureChild); - } - - #[Test] - public function readingPrivateStaticMemberMadeAccessible() { - Assert::equals('Target', self::$fixture - ->getField('staticTarget') - ->setAccessible(true) - ->get(null) - ); - } - - #[Test, Expect(IllegalAccessException::class)] - public function writingPrivateMember() { - self::$fixture->getField('target')->set(PrivateAccessibilityFixture::construct(self::$fixture), null); - } - - #[Test] - public function writingPrivateMemberFromSameClass() { - Assert::equals('Modified', PrivateAccessibilityFixture::write(self::$fixture)); - } - - #[Test, Expect(IllegalAccessException::class)] - public function writingPrivateMemberFromParentClass() { - PrivateAccessibilityFixtureChild::write(self::$fixture); - } - - #[Test, Ignore('$this->getClass()->getField($field) does not yield private field declared in parent class')] - public function writingPrivateMemberFromChildClass() { - PrivateAccessibilityFixtureChild::write(self::$fixtureChild); - } - - #[Test] - public function writingPrivateMemberMadeAccessible() { - with ($f= self::$fixture->getField('target'), $i= PrivateAccessibilityFixture::construct(self::$fixture)); { - $f->setAccessible(true); - $f->set($i, 'Modified'); - Assert::equals('Modified', $f->get($i)); - } - } - - #[Test, Expect(IllegalAccessException::class)] - public function writingPrivateStaticMember() { - self::$fixture->getField('staticTarget')->set(null, 'Modified'); - } - - #[Test] - public function writingPrivateStaticMemberFromSameClass() { - Assert::equals('Modified', PrivateAccessibilityFixture::writeStatic(self::$fixture)); - } - - #[Test, Expect(IllegalAccessException::class)] - public function writingPrivateStaticMemberFromParentClass() { - PrivateAccessibilityFixtureChild::writeStatic(self::$fixture); - } - - #[Test, Ignore('$this->getClass()->getField($field) does not yield private field declared in parent class')] - public function writingPrivateStaticMemberFromChildClass() { - PrivateAccessibilityFixtureChild::writeStatic(self::$fixtureChild); - } - - #[Test] - public function writingPrivateStaticMemberMadeAccessible() { - with ($f= self::$fixture->getField('staticTarget')); { - $f->setAccessible(true); - $f->set(null, 'Modified'); - Assert::equals('Modified', $f->get(null)); - } - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/ProtectedAccessibilityFixture.class.php b/src/test/php/lang/unittest/ProtectedAccessibilityFixture.class.php deleted file mode 100755 index a4de4190b..000000000 --- a/src/test/php/lang/unittest/ProtectedAccessibilityFixture.class.php +++ /dev/null @@ -1,107 +0,0 @@ -getConstructor()->newInstance([]); - } - - /** - * Entry point: Invoke target method - * - * @param lang.XPClass - * @return string - */ - public static function invoke(XPClass $class) { - return $class->getMethod('target')->invoke(new self()); - } - - /** - * Entry point: Invoke staticTarget method - * - * @param lang.XPClass - * @return string - */ - public static function invokeStatic(XPClass $class) { - return $class->getMethod('staticTarget')->invoke(null); - } - - /** - * Entry point: Read target member - * - * @param lang.XPClass - * @return string - */ - public static function read(XPClass $class) { - return $class->getField('target')->get(new self()); - } - - /** - * Entry point: Read staticTarget member - * - * @param lang.XPClass - * @return string - */ - public static function readStatic(XPClass $class) { - return $class->getField('staticTarget')->get(null); - } - - /** - * Entry point: Write target member, then read it back - * - * @param lang.XPClass - * @return string - */ - public static function write(XPClass $class) { - return with (new self(), $class->getField('target'), function($self, $f) { - $f->set($self, 'Modified'); - return $f->get($self); - }); - } - - /** - * Entry point: Write target member, then read it back - * - * @param lang.XPClass - * @return string - */ - public static function writeStatic(XPClass $class) { - return with ($class->getField('staticTarget'), function($f) { - $f->set(null, 'Modified'); - return $f->get(null); - }); - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/ProtectedAccessibilityFixtureChild.class.php b/src/test/php/lang/unittest/ProtectedAccessibilityFixtureChild.class.php deleted file mode 100755 index 37f408a49..000000000 --- a/src/test/php/lang/unittest/ProtectedAccessibilityFixtureChild.class.php +++ /dev/null @@ -1,80 +0,0 @@ -getConstructor()->newInstance([]); - } - - /** - * Entry point: Invoke target method - * - * @param lang.XPClass - * @return string - */ - public static function invoke(\lang\XPClass $class) { - return $class->getMethod('target')->invoke(new self()); - } - - /** - * Entry point: Invoke staticTarget method - * - * @param lang.XPClass - * @return string - */ - public static function invokeStatic(\lang\XPClass $class) { - return $class->getMethod('staticTarget')->invoke(null); - } - - /** - * Entry point: Invoke target method - * - * @param lang.XPClass - * @return string - */ - public static function read(\lang\XPClass $class) { - return $class->getField('target')->get(new self()); - } - - /** - * Entry point: Read staticTarget member - * - * @param lang.XPClass - * @return string - */ - public static function readStatic(\lang\XPClass $class) { - return $class->getField('staticTarget')->get(null); - } - - /** - * Entry point: Write target member, then read it back - * - * @param lang.XPClass - * @return string - */ - public static function write(\lang\XPClass $class) { - with ($s= new self(), $f= $class->getField('target')); { - $f->set($s, 'Modified'); - return $f->get($s); - } - } - - /** - * Entry point: Write staticTarget member, then read it back - * - * @param lang.XPClass - * @return string - */ - public static function writeStatic(\lang\XPClass $class) { - with ($f= $class->getField('staticTarget')); { - $f->set(null, 'Modified'); - return $f->get(null); - } - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/ProtectedAccessibilityTest.class.php b/src/test/php/lang/unittest/ProtectedAccessibilityTest.class.php deleted file mode 100755 index cbe931310..000000000 --- a/src/test/php/lang/unittest/ProtectedAccessibilityTest.class.php +++ /dev/null @@ -1,217 +0,0 @@ -getConstructor()->newInstance([]); - } - - #[Test] - public function invokingProtectedConstructorFromSameClass() { - Assert::instance(self::$fixture, ProtectedAccessibilityFixture::construct(self::$fixture)); - } - - #[Test] - public function invokingProtectedConstructorFromParentClass() { - Assert::instance(self::$fixture, ProtectedAccessibilityFixtureChild::construct(self::$fixture)); - } - - #[Test] - public function invokingProtectedConstructorFromChildClass() { - Assert::instance(self::$fixtureChild, ProtectedAccessibilityFixtureChild::construct(self::$fixtureChild)); - } - - #[Test] - public function invokingProtectedConstructorMadeAccessible() { - Assert::instance(self::$fixture, self::$fixture - ->getConstructor() - ->setAccessible(true) - ->newInstance([]) - ); - } - - #[Test, Expect(IllegalAccessException::class)] - public function invokingProtectedMethod() { - self::$fixture->getMethod('target')->invoke(ProtectedAccessibilityFixture::construct(self::$fixture)); - } - - #[Test] - public function invokingProtectedMethodFromSameClass() { - Assert::equals('Invoked', ProtectedAccessibilityFixture::invoke(self::$fixture)); - } - - #[Test] - public function invokingProtectedMethodFromParentClass() { - Assert::equals('Invoked', ProtectedAccessibilityFixtureChild::invoke(self::$fixture)); - } - - #[Test] - public function invokingProtectedMethodFromChildClass() { - Assert::equals('Invoked', ProtectedAccessibilityFixtureChild::invoke(self::$fixtureChild)); - } - - #[Test] - public function invokingProtectedMethodMadeAccessible() { - Assert::equals('Invoked', self::$fixture - ->getMethod('target') - ->setAccessible(true) - ->invoke(ProtectedAccessibilityFixture::construct(self::$fixture)) - ); - } - - #[Test, Expect(IllegalAccessException::class)] - public function invokingProtectedStaticMethod() { - self::$fixture->getMethod('staticTarget')->invoke(null); - } - - #[Test] - public function invokingProtectedStaticMethodFromSameClass() { - Assert::equals('Invoked', ProtectedAccessibilityFixture::invokeStatic(self::$fixture)); - } - - #[Test] - public function invokingProtectedStaticMethodFromParentClass() { - Assert::equals('Invoked', ProtectedAccessibilityFixtureChild::invokeStatic(self::$fixture)); - } - - #[Test] - public function invokingProtectedStaticMethodFromChildClass() { - Assert::equals('Invoked', ProtectedAccessibilityFixtureChild::invokeStatic(self::$fixtureChild)); - } - - #[Test] - public function invokingProtectedStaticMethodMadeAccessible() { - Assert::equals('Invoked', self::$fixture - ->getMethod('staticTarget') - ->setAccessible(true) - ->invoke(null) - ); - } - - #[Test, Expect(IllegalAccessException::class)] - public function readingProtectedMember() { - self::$fixture->getField('target')->get(ProtectedAccessibilityFixture::construct(self::$fixture)); - } - - #[Test] - public function readingProtectedMemberFromSameClass() { - Assert::equals('Target', ProtectedAccessibilityFixture::read(self::$fixture)); - } - - #[Test] - public function readingProtectedMemberFromParentClass() { - Assert::equals('Target', ProtectedAccessibilityFixtureChild::read(self::$fixture)); - } - - #[Test] - public function readingProtectedMemberFromChildClass() { - Assert::equals('Target', ProtectedAccessibilityFixtureChild::read(self::$fixtureChild)); - } - - #[Test] - public function readingProtectedMemberMadeAccessible() { - Assert::equals('Target', self::$fixture - ->getField('target') - ->setAccessible(true) - ->get(ProtectedAccessibilityFixture::construct(self::$fixture)) - ); - } - - #[Test, Expect(IllegalAccessException::class)] - public function readingProtectedStaticMember() { - self::$fixture->getField('staticTarget')->get(null); - } - - #[Test] - public function readingProtectedStaticMemberFromSameClass() { - Assert::equals('Target', ProtectedAccessibilityFixture::readStatic(self::$fixture)); - } - - #[Test] - public function readingProtectedStaticMemberFromParentClass() { - Assert::equals('Target', ProtectedAccessibilityFixtureChild::readStatic(self::$fixture)); - } - - #[Test] - public function readingProtectedStaticMemberFromChildClass() { - Assert::equals('Target', ProtectedAccessibilityFixtureChild::readStatic(self::$fixtureChild)); - } - - #[Test] - public function readingProtectedStaticMemberMadeAccessible() { - Assert::equals('Target', self::$fixture - ->getField('staticTarget') - ->setAccessible(true) - ->get(null) - ); - } - - #[Test, Expect(IllegalAccessException::class)] - public function writingProtectedMember() { - self::$fixture->getField('target')->set(ProtectedAccessibilityFixture::construct(self::$fixture), null); - } - - #[Test] - public function writingProtectedMemberFromSameClass() { - Assert::equals('Modified', ProtectedAccessibilityFixture::write(self::$fixture)); - } - - #[Test] - public function writingProtectedMemberFromParentClass() { - Assert::equals('Modified', ProtectedAccessibilityFixtureChild::write(self::$fixture)); - } - - #[Test] - public function writingProtectedMemberFromChildClass() { - Assert::equals('Modified', ProtectedAccessibilityFixtureChild::write(self::$fixtureChild)); - } - - #[Test] - public function writingProtectedMemberMadeAccessible() { - with ($f= self::$fixture->getField('target'), $i= ProtectedAccessibilityFixture::construct(self::$fixture)); { - $f->setAccessible(true); - $f->set($i, 'Modified'); - Assert::equals('Modified', $f->get($i)); - } - } - - #[Test, Expect(IllegalAccessException::class)] - public function writingProtectedStaticMember() { - self::$fixture->getField('staticTarget')->set(null, 'Modified'); - } - - #[Test] - public function writingProtectedStaticMemberFromSameClass() { - Assert::equals('Modified', ProtectedAccessibilityFixture::writeStatic(self::$fixture)); - } - - #[Test] - public function writingProtectedStaticMemberFromParentClass() { - Assert::equals('Modified', ProtectedAccessibilityFixtureChild::writeStatic(self::$fixture)); - } - - #[Test] - public function writingProtectedStaticMemberFromChildClass() { - Assert::equals('Modified', ProtectedAccessibilityFixtureChild::writeStatic(self::$fixtureChild)); - } - - #[Test] - public function writingProtectedStaticMemberMadeAccessible() { - with ($f= self::$fixture->getField('staticTarget')); { - $f->setAccessible(true); - $f->set(null, 'Modified'); - Assert::equals('Modified', $f->get(null)); - } - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/ProxyTest.class.php b/src/test/php/lang/unittest/ProxyTest.class.php deleted file mode 100755 index 5b2a03d4a..000000000 --- a/src/test/php/lang/unittest/ProxyTest.class.php +++ /dev/null @@ -1,186 +0,0 @@ -handler= new class() implements InvocationHandler { - public $invocations = []; - public function invoke($proxy, $method, $args) { - $this->invocations[$method.'_'.sizeof($args)]= $args; - } - }; - $this->iteratorClass= XPClass::forName(XPIterator::class); - $this->observerClass= XPClass::forName(Observer::class); - } - - /** - * Helper method which returns a proxy instance for a given list of - * interfaces, using the default classloader and the handler defined - * in setUp() - * - * @param lang.XPClass[] interfaces - * @return lang.reflect.Proxy - */ - protected function proxyInstanceFor($interfaces) { - return Proxy::newProxyInstance(ClassLoader::getDefault(), $interfaces, $this->handler); - } - - /** - * Helper method which returns a proxy class for a given list of - * interfaces, using the default classloader and the handler defined - * in setUp() - * - * @param lang.XPClass[] interfaces - * @return lang.XPClass - */ - protected function proxyClassFor($interfaces) { - return Proxy::getProxyClass(ClassLoader::getDefault(), $interfaces); - } - - /** - * Helper method which returns a proxy class with a unique name and - * a given body, using the default classloader. - * - * @param string body - * @return lang.XPClass - */ - protected function newProxyWith($body) { - static $uniq= 0; - - return $this->proxyClassFor([ClassLoader::defineInterface( - 'lang.unittest.__NP_'.($uniq++), - [], - $body - )]); - } - - #[Test, Expect(Error::class)] - public function nullClassLoader() { - Proxy::getProxyClass(null, [$this->iteratorClass]); - } - - #[Test, Expect(IllegalArgumentException::class)] - public function emptyInterfaces() { - Proxy::getProxyClass(ClassLoader::getDefault(), []); - } - - #[Test, Expect(Error::class)] - public function nullInterfaces() { - Proxy::getProxyClass(ClassLoader::getDefault(), null); - } - - #[Test] - public function proxyClassNamesGetPrefixed() { - $class= $this->proxyClassFor([$this->iteratorClass]); - Assert::equals(Proxy::PREFIX, substr($class->getName(), 0, strlen(Proxy::PREFIX))); - } - - #[Test] - public function classesEqualForSameInterfaceList() { - Assert::equals( - $this->proxyClassFor([$this->iteratorClass]), - $this->proxyClassFor([$this->iteratorClass]) - ); - } - - #[Test] - public function classesNotEqualForDifferingInterfaceList() { - Assert::notEquals( - $this->proxyClassFor([$this->iteratorClass]), - $this->proxyClassFor([$this->iteratorClass, $this->observerClass]) - ); - } - - #[Test] - public function iteratorInterfaceIsImplemented() { - $class= $this->proxyClassFor([$this->iteratorClass]); - Assert::equals([$this->iteratorClass], $class->getInterfaces()); - } - - #[Test] - public function allInterfacesAreImplemented() { - $class= $this->proxyClassFor([$this->iteratorClass, $this->observerClass]); - Assert::equals([$this->iteratorClass, $this->observerClass], $class->getInterfaces()); - } - - #[Test] - public function iteratorMethods() { - $class= $this->proxyClassFor([$this->iteratorClass]); - Assert::equals( - [true, true], - [$class->hasMethod('hasNext'), $class->hasMethod('next')] - ); - } - - #[Test] - public function iteratorNextInvoked() { - $proxy= $this->proxyInstanceFor([$this->iteratorClass]); - $proxy->next(); - Assert::equals([], $this->handler->invocations['next_0']); - } - - #[Test, Expect(IllegalArgumentException::class)] - public function cannotCreateProxiesForClasses() { - $this->proxyInstanceFor([XPClass::forName('lang.Type')]); - } - - #[Test, Expect(IllegalArgumentException::class)] - public function cannotCreateProxiesForClassesAsSecondArg() { - $this->proxyInstanceFor([ - $this->iteratorClass, - XPClass::forName('lang.Type') - ]); - } - - #[Test] - public function allowDoubledInterfaceMethod() { - $this->proxyInstanceFor([ - $this->iteratorClass, - ClassLoader::defineInterface('util.NewIterator', XPIterator::class) - ]); - } - - #[Test] - public function overloadedMethod() { - $proxy= $this->proxyInstanceFor([XPClass::forName(OverloadedInterface::class)]); - $proxy->overloaded('foo'); - $proxy->overloaded('foo', 'bar'); - Assert::equals(['foo'], $this->handler->invocations['overloaded_1']); - Assert::equals(['foo', 'bar'], $this->handler->invocations['overloaded_2']); - } - - #[Test] - public function namespaced_typehinted_parameters_handled_correctly() { - $proxy= $this->newProxyWith('{ public function fixture(\util\Date $param); }'); - Assert::equals( - XPClass::forName('util.Date'), - $proxy->getMethod('fixture')->getParameters()[0]->getTypeRestriction() - ); - } - - #[Test] - public function builtin_typehinted_parameters_handled_correctly() { - $proxy= $this->newProxyWith('{ public function fixture(\ReflectionClass $param); }'); - Assert::equals( - new XPClass('ReflectionClass'), - $proxy->getMethod('fixture')->getParameters()[0]->getTypeRestriction() - ); - } - - #[Test] - public function builtin_array_parameters_handled_correctly() { - $proxy= $this->newProxyWith('{ public function fixture(array $param); }'); - Assert::equals( - Type::$ARRAY, - $proxy->getMethod('fixture')->getParameters()[0]->getTypeRestriction() - ); - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/ReferencesTest.class.php b/src/test/php/lang/unittest/ReferencesTest.class.php index abeb8c2e3..b4cc3a016 100755 --- a/src/test/php/lang/unittest/ReferencesTest.class.php +++ b/src/test/php/lang/unittest/ReferencesTest.class.php @@ -1,6 +1,6 @@ getMethod('factory'); - $object= $factory->invoke($instance= NULL); + $object= Reflection::type(AnonymousNewInstanceFactory::class)->method('factory')->invoke(null); $value= ReferencesTest::registry('list', $r= NULL); $this->assertReference($object, $value); diff --git a/src/test/php/lang/unittest/RuntimeClassDefinitionTest.class.php b/src/test/php/lang/unittest/RuntimeClassDefinitionTest.class.php index ad160310e..7ad41f179 100755 --- a/src/test/php/lang/unittest/RuntimeClassDefinitionTest.class.php +++ b/src/test/php/lang/unittest/RuntimeClassDefinitionTest.class.php @@ -1,6 +1,7 @@ define([], '{ public $fixture= null; }'); - Assert::true($class->hasField('fixture')); + Assert::instance(Property::class, Reflection::type($class)->property('fixture')); } #[Test] public function method_exists() { $class= $this->define([], '{ public function fixture() { } }'); - Assert::true($class->hasMethod('fixture')); + Assert::instance(Method::class, Reflection::type($class)->method('fixture')); } #[Test] - public function parents_method_exists() { - Assert::true($this->define(['parent' => Throwable::class])->hasMethod('toString')); + public function parents_property_exists() { + $class= $this->define(['parent' => Throwable::class]); + Assert::instance(Property::class, Reflection::type($class)->property('message')); } #[Test] - public function parents_field_exists() { - Assert::true($this->define(['parent' => Throwable::class])->hasField('message')); + public function parents_method_exists() { + $class= $this->define(['parent' => Throwable::class]); + Assert::instance(Method::class, Reflection::type($class)->method('toString')); } #[Test] @@ -84,7 +87,7 @@ public function static_initializer_is_invoked() { public static $initializerCalled= false; static function __static() { self::$initializerCalled= true; } }'); - Assert::true($class->getField('initializerCalled')->get(null)); + Assert::true(Reflection::of($class)->property('initializerCalled')->get(null)); } #[Test, Expect(ClassNotFoundException::class)] @@ -105,47 +108,43 @@ public function cannot_define_class_with_null_interface() { #[Test] public function closure_map_style_declaring_field() { $class= $this->define([], ['fixture' => null]); - Assert::true($class->hasField('fixture')); + Assert::instance(Property::class, Reflection::type($class)->property('fixture')); } #[Test] public function closure_map_style_declaring_method() { $class= $this->define([], ['fixture' => function() { }]); - Assert::true($class->hasMethod('fixture')); + Assert::instance(Method::class, Reflection::type($class)->method('fixture')); } #[Test] public function closure_map_field_access() { $class= $this->define([], ['fixture' => 'Test']); - $instance= $class->newInstance(); - Assert::equals('Test', $class->getField('fixture')->get($instance)); + Assert::equals('Test', $class->newInstance()->fixture); } #[Test] public function closure_map_method_invocation() { $class= $this->define([], ['fixture' => function($a, $b) { return [$this, $a, $b]; }]); $instance= $class->newInstance(); - Assert::equals([$instance, 1, 2], $class->getMethod('fixture')->invoke($instance, [1, 2])); + Assert::equals([$instance, 1, 2], $instance->fixture(1, 2)); } #[Test] public function closure_with_string_parameter_type() { $class= $this->define([], ['fixture' => function(string $a) { return $a; }]); - $instance= $class->newInstance(); - Assert::equals('1', $class->getMethod('fixture')->invoke($instance, [1])); + Assert::equals('1', $class->newInstance()->fixture(1)); } #[Test] public function closure_with_string_return_type() { $class= $this->define([], ['fixture' => function($a): string { return $a; }]); - $instance= $class->newInstance(); - Assert::equals('1', $class->getMethod('fixture')->invoke($instance, [1])); + Assert::equals('1', $class->newInstance()->fixture(1)); } #[Test, Runtime(php: '>=7.1')] public function closure_with_void_return_type() { - $class= $this->define([], ['fixture' => function($a): void { }]); - $instance= $class->newInstance(); - Assert::null($class->getMethod('fixture')->invoke($instance, [1])); + $class= $this->define([], ['fixture' => function(): void { }]); + Assert::null($class->newInstance()->fixture()); } } \ No newline at end of file diff --git a/src/test/php/lang/unittest/RuntimeInterfaceDefinitionTest.class.php b/src/test/php/lang/unittest/RuntimeInterfaceDefinitionTest.class.php index d610e19b9..aa60d5073 100755 --- a/src/test/php/lang/unittest/RuntimeInterfaceDefinitionTest.class.php +++ b/src/test/php/lang/unittest/RuntimeInterfaceDefinitionTest.class.php @@ -1,6 +1,7 @@ define(['parents' => [Runnable::class]])->hasMethod('run')); + $class= $this->define(['parents' => [Runnable::class]]); + Assert::instance(Method::class, Reflection::type($class)->method('run')); } #[Test] public function method_exists() { $class= $this->define(['parents' => [Runnable::class]], '{ public function runAs($user); }'); - Assert::true($class->hasMethod('runAs')); + Assert::instance(Method::class, Reflection::type($class)->method('runAs')); } #[Test, Expect(ClassNotFoundException::class)] @@ -82,6 +84,6 @@ public function cannot_define_interface_with_null_parent() { #[Test] public function closure_map_style_declaring_method() { $class= $this->define(['parents' => [Runnable::class]], ['fixture' => function() { }]); - Assert::true($class->hasMethod('fixture')); + Assert::instance(Method::class, Reflection::type($class)->method('fixture')); } } \ No newline at end of file diff --git a/src/test/php/lang/unittest/RuntimeTypeDefinitionTest.class.php b/src/test/php/lang/unittest/RuntimeTypeDefinitionTest.class.php index 98cbfe499..6ff253b24 100755 --- a/src/test/php/lang/unittest/RuntimeTypeDefinitionTest.class.php +++ b/src/test/php/lang/unittest/RuntimeTypeDefinitionTest.class.php @@ -1,6 +1,6 @@ define(['annotations' => '#[Test]'])->hasAnnotation('test')); + $class= $this->define(['annotations' => '#[Test]']); + Assert::true(Reflection::type($class)->annotations()->provides('lang.unittest.Test')); } #[Test] public function declares_passed_annotation_with_value() { - Assert::equals('/rest', $this->define(['annotations' => '#[Webservice(["path" => "/rest"])]'])->getAnnotation('webservice', 'path')); + $class= $this->define(['annotations' => '#[Webservice(path: "/rest")]']); + Assert::equals('/rest', Reflection::type($class)->annotation('lang.unittest.Webservice')->argument('path')); } #[Test, Values(['com.example.test.RTTDDotted', 'com\\example\\test\\RTTDNative'])] diff --git a/src/test/php/lang/unittest/TypeIntersectionTest.class.php b/src/test/php/lang/unittest/TypeIntersectionTest.class.php index 7268c7bd3..f2c88786d 100755 --- a/src/test/php/lang/unittest/TypeIntersectionTest.class.php +++ b/src/test/php/lang/unittest/TypeIntersectionTest.class.php @@ -1,7 +1,7 @@ =8.1.0-dev')] public function php81_native_intersection_field_type() { - $f= typeof(eval('return new class() { public Countable&Traversable $fixture; };'))->getField('fixture'); - Assert::equals(new TypeIntersection($this->types), $f->getType()); - Assert::equals('Countable&Traversable', $f->getTypeName()); + $t= typeof(eval('return new class() { public Countable&Traversable $fixture; };')); + Assert::equals( + new TypeIntersection($this->types), + Reflection::type($t)->property('fixture')->constraint()->type() + ); } #[Test, Runtime(php: '>=8.1.0-dev')] public function php81_native_intersection_param_type() { - $m= typeof(eval('return new class() { public function fixture(Countable&Traversable $arg) { } };'))->getMethod('fixture'); - Assert::equals(new TypeIntersection($this->types), $m->getParameter(0)->getType()); - Assert::equals('Countable&Traversable', $m->getParameter(0)->getTypeName()); + $t= typeof(eval('return new class() { public function fixture(Countable&Traversable $arg) { } };')); + Assert::equals( + new TypeIntersection($this->types), + Reflection::type($t)->method('fixture')->parameter(0)->constraint()->type() + ); } #[Test, Runtime(php: '>=8.1.0-dev')] public function php81_native_intersection_return_type() { - $m= typeof(eval('return new class() { public function fixture(): Countable&Traversable { } };'))->getMethod('fixture'); - Assert::equals(new TypeIntersection($this->types), $m->getReturnType()); - Assert::equals('Countable&Traversable', $m->getReturnTypeName()); + $t= typeof(eval('return new class() { public function fixture(): Countable&Traversable { } };')); + Assert::equals( + new TypeIntersection($this->types), + Reflection::type($t)->method('fixture')->returns()->type() + ); } } \ No newline at end of file diff --git a/src/test/php/lang/unittest/TypeSyntaxTest.class.php b/src/test/php/lang/unittest/TypeSyntaxTest.class.php index a0c18322b..a07be45fb 100755 --- a/src/test/php/lang/unittest/TypeSyntaxTest.class.php +++ b/src/test/php/lang/unittest/TypeSyntaxTest.class.php @@ -1,6 +1,6 @@ 'class', 'extends' => null, 'implements' => [], 'use' => []]; /** - * Declare a field from given source code + * Declare a propertyy from given source code * * @param string $source - * @return lang.reflect.Field + * @return lang.reflection.Property */ - private function field($source) { + private function propertyy($source) { static $id= 0; - return ClassLoader::defineType(self::class.'Field'.(++$id), self::$spec, '{'.$source.'}')->getField('fixture'); + + $t= ClassLoader::defineType(self::class.'Propertyy'.(++$id), self::$spec, '{'.$source.'}'); + return Reflection::type($t)->property('fixture'); } /** * Declare a method from given source code * * @param string $source - * @return lang.reflect.Method + * @return lang.reflection.Method */ private function method($source) { static $id= 0; - return ClassLoader::defineType(self::class.'Method'.(++$id), self::$spec, '{'.$source.'}')->getMethod('fixture'); + $t= ClassLoader::defineType(self::class.'Method'.(++$id), self::$spec, '{'.$source.'}'); + return Reflection::type($t)->method('fixture'); } #[Test, Runtime(php: '>=7.4')] public function primitive_type() { - $d= $this->field('private string $fixture;'); - Assert::equals(Primitive::$STRING, $d->getType()); - Assert::equals('string', $d->getTypeName()); + $d= $this->propertyy('private string $fixture;'); + Assert::equals(Primitive::$STRING, $d->constraint()->type()); } #[Test] public function return_primitive_type() { $d= $this->method('function fixture(): string { return "Test"; }'); - Assert::equals(Primitive::$STRING, $d->getReturnType()); - Assert::equals('string', $d->getReturnTypeName()); + Assert::equals(Primitive::$STRING, $d->returns()->type()); } #[Test] public function parameter_primitive_type() { $d= $this->method('function fixture(string $name) { }'); - Assert::equals(Primitive::$STRING, $d->getParameter(0)->getType()); - Assert::equals('string', $d->getParameter(0)->getTypeName()); + Assert::equals(Primitive::$STRING, $d->parameter(0)->constraint()->type()); } #[Test, Runtime(php: '>=8.0')] public function union_type() { - $d= $this->field('private string|int $fixture;'); - Assert::equals(new TypeUnion([Primitive::$STRING, Primitive::$INT]), $d->getType()); - Assert::equals('string|int', $d->getTypeName()); + $d= $this->propertyy('private string|int $fixture;'); + Assert::equals(new TypeUnion([Primitive::$STRING, Primitive::$INT]), $d->constraint()->type()); } #[Test, Runtime(php: '>=8.0')] public function nullable_union_type() { - $d= $this->field('private string|int|null $fixture;'); - Assert::equals(new Nullable(new TypeUnion([Primitive::$STRING, Primitive::$INT])), $d->getType()); - Assert::equals('?string|int', $d->getTypeName()); + $d= $this->propertyy('private string|int|null $fixture;'); + Assert::equals(new Nullable(new TypeUnion([Primitive::$STRING, Primitive::$INT])), $d->constraint()->type()); } #[Test, Runtime(php: '>=8.0')] public function return_union_type() { $d= $this->method('function fixture(): string|int { return "Test"; }'); - Assert::equals(new TypeUnion([Primitive::$STRING, Primitive::$INT]), $d->getReturnType()); - Assert::equals('string|int', $d->getReturnTypeName()); + Assert::equals(new TypeUnion([Primitive::$STRING, Primitive::$INT]), $d->returns()->type()); } #[Test, Runtime(php: '>=8.0')] public function parameter_union_type() { $d= $this->method('function fixture(string|int $name) { }'); - Assert::equals(new TypeUnion([Primitive::$STRING, Primitive::$INT]), $d->getParameter(0)->getType()); - Assert::equals('string|int', $d->getParameter(0)->getTypeName()); + Assert::equals(new TypeUnion([Primitive::$STRING, Primitive::$INT]), $d->parameter(0)->constraint()->type()); } } \ No newline at end of file diff --git a/src/test/php/lang/unittest/TypeUnionTest.class.php b/src/test/php/lang/unittest/TypeUnionTest.class.php index a5aef7cd6..83205859b 100755 --- a/src/test/php/lang/unittest/TypeUnionTest.class.php +++ b/src/test/php/lang/unittest/TypeUnionTest.class.php @@ -10,7 +10,8 @@ Type, TypeUnion, XPClass, - Nullable + Nullable, + Reflection }; use test\verify\Runtime; use test\{Action, Assert, Expect, Test, Values}; @@ -193,7 +194,7 @@ public function php8_native_union_field_type() { $f= eval('return new class() { public int|string $fixture; };'); Assert::equals( new TypeUnion([Primitive::$INT, Primitive::$STRING]), - typeof($f)->getField('fixture')->getType() + Reflection::type($f)->property('fixture')->constraint()->type() ); } @@ -202,7 +203,7 @@ public function php8_native_union_param_type() { $f= eval('return new class() { public function fixture(int|string $arg) { } };'); Assert::equals( new TypeUnion([Primitive::$INT, Primitive::$STRING]), - typeof($f)->getMethod('fixture')->getParameter(0)->getType() + Reflection::type($f)->method('fixture')->parameter(0)->constraint()->type() ); } @@ -211,7 +212,7 @@ public function php8_native_union_return_type() { $f= eval('return new class() { public function fixture(): int|string { } };'); Assert::equals( new TypeUnion([Primitive::$INT, Primitive::$STRING]), - typeof($f)->getMethod('fixture')->getReturnType() + Reflection::type($f)->method('fixture')->returns()->type() ); } @@ -220,26 +221,26 @@ public function php8_native_nullable_union_type() { $f= eval('return new class() { public function fixture(int|string|null $arg) { } };'); Assert::equals( new Nullable(new TypeUnion([Primitive::$INT, Primitive::$STRING])), - typeof($f)->getMethod('fixture')->getParameter(0)->getType() + Reflection::type($f)->method('fixture')->parameter(0)->constraint()->type() ); } #[Test, Runtime(php: '>=8.0.0-dev')] public function php8_native_nullable_union_field_type_name() { $f= eval('return new class() { public int|string|null $fixture; };'); - Assert::equals('?', typeof($f)->getField('fixture')->getTypeName()[0]); + Assert::instance(Nullable::class, Reflection::type($f)->property('fixture')->constraint()->type()); } #[Test, Runtime(php: '>=8.0.0-dev')] public function php8_native_nullable_union_param_type_name() { $f= eval('return new class() { public function fixture(int|string|null $arg) { } };'); - Assert::equals('?', typeof($f)->getMethod('fixture')->getParameter(0)->getTypeName()[0]); + Assert::instance(Nullable::class, Reflection::type($f)->method('fixture')->parameter(0)->constraint()->type()); } #[Test, Runtime(php: '>=8.0.0-dev')] public function php8_native_nullable_union_return_type_name() { $f= eval('return new class() { public function fixture(): int|string|null { } };'); - Assert::equals('?', typeof($f)->getMethod('fixture')->getReturnTypeName()[0]); + Assert::instance(Nullable::class, Reflection::type($f)->method('fixture')->returns()->type()); } #[Test, Runtime(php: '>=8.0.0-dev')] @@ -255,7 +256,7 @@ public function php8_native_union_with_self() { )); Assert::equals( new TypeUnion([$t, new XPClass(Name::class), new XPClass(\Countable::class), new XPClass(\ArrayObject::class)]), - $t->getField('fixture')->getType() + Reflection::type($t)->property('fixture')->constraint()->type() ); } } \ No newline at end of file diff --git a/src/test/php/lang/unittest/VirtualMembersTest.class.php b/src/test/php/lang/unittest/VirtualMembersTest.class.php deleted file mode 100755 index 9b17c0a60..000000000 --- a/src/test/php/lang/unittest/VirtualMembersTest.class.php +++ /dev/null @@ -1,75 +0,0 @@ -property= XPClass::forName('lang.unittest.WithReadonly'); - } - - #[Test] - public function exists() { - Assert::true($this->property->hasField('prop')); - } - - #[Test] - public function string_representation() { - Assert::equals( - 'public readonly string '.$this->property->getName().'::$prop', - $this->property->getField('prop')->toString() - ); - } - - #[Test] - public function type() { - Assert::equals(Primitive::$STRING, $this->property->getField('prop')->getType()); - } - - #[Test] - public function type_name() { - Assert::equals('string', $this->property->getField('prop')->getTypeName()); - } - - #[Test] - public function type_restriction() { - Assert::equals(Primitive::$STRING, $this->property->getField('prop')->getTypeRestriction()); - } - - #[Test, Expect(IllegalAccessException::class)] - public function cannot_access_by_default() { - $this->property->getField('prop')->get($this->property->newInstance()); - } - - #[Test] - public function initial_value() { - Assert::null($this->property->getField('prop')->setAccessible(true)->get($this->property->newInstance())); - } - - #[Test] - public function get_set_roundtrip() { - with ($this->property->getField('prop')->setAccessible(true), $this->property->newInstance(), function($field, $instance) { - $field->set($instance, [$this]); - Assert::equals([$this], $field->get($instance)); - }); - } - - #[Test] - public function included_in_all_fields() { - Assert::equals( - ['__readonly', 'prop'], - array_map(function($f) { return $f->getName(); }, $this->property->getFields()) - ); - } - - #[Test] - public function included_in_all_declared_fields() { - Assert::equals( - ['__readonly', 'prop'], - array_map(function($f) { return $f->getName(); }, $this->property->getDeclaredFields()) - ); - } -} \ No newline at end of file diff --git a/src/test/php/lang/unittest/XPClassTest.class.php b/src/test/php/lang/unittest/XPClassTest.class.php index 756a332bf..aa3e879ab 100755 --- a/src/test/php/lang/unittest/XPClassTest.class.php +++ b/src/test/php/lang/unittest/XPClassTest.class.php @@ -1,6 +1,6 @@ getDeclaredInterfaces()); } - #[Test] - public function fixture_class_has_a_constructor() { - Assert::true($this->fixture->hasConstructor()); - } - - #[Test] - public function fixture_classes_constructor() { - Assert::instance(Constructor::class, $this->fixture->getConstructor()); - } - - #[Test] - public function value_class_does_not_have_a_constructor() { - Assert::false(XPClass::forName('lang.Value')->hasConstructor()); - } - - #[Test, Expect(ElementNotFoundException::class)] - public function getting_value_classes_constructor_raises_an_exception() { - XPClass::forName('lang.Value')->getConstructor(); - } - #[Test] public function invoking_fixture_classes_constructor() { Assert::equals( new TestClass('1977-12-14'), - $this->fixture->getConstructor()->newInstance(['1977-12-14']) + $this->fixture->newInstance('1977-12-14') ); } @@ -229,49 +209,6 @@ public function newInstance_raises_exception_if_class_is_abstract() { XPClass::forName(AbstractTestClass::class)->newInstance(); } - #[Test, Expect(TargetInvocationException::class)] - public function constructors_newInstance_method_wraps_exceptions() { - $this->fixture->getConstructor()->newInstance(['@@not-a-valid-date-string@@']); - } - - #[Test, Expect(IllegalAccessException::class)] - public function constructors_newInstance_method_raises_exception_if_class_is_abstract() { - XPClass::forName(AbstractTestClass::class)->getConstructor()->newInstance(); - } - - #[Test] - public function implementedConstructorInvocation() { - $i= ClassLoader::defineClass('ANonAbstractClass', AbstractTestClass::class, [], '{ - public function getDate() {} - }'); - Assert::instance(AbstractTestClass::class, $i->getConstructor()->newInstance()); - } - - #[Test] - public function fixture_class_has_annotations() { - Assert::true($this->fixture->hasAnnotations()); - } - - #[Test] - public function fixture_class_annotations() { - Assert::equals(['test' => 'Annotation'], $this->fixture->getAnnotations()); - } - - #[Test] - public function fixture_class_has_test_annotation() { - Assert::true($this->fixture->hasAnnotation('test')); - } - - #[Test] - public function fixture_class_test_annotation() { - Assert::equals('Annotation', $this->fixture->getAnnotation('test')); - } - - #[Test, Expect(ElementNotFoundException::class)] - public function getting_non_existant_annotation_raises_exception() { - $this->fixture->getAnnotation('non-existant'); - } - #[Test, Expect(ClassNotFoundException::class)] public function forName_raises_exceptions_for_nonexistant_classes() { XPClass::forName('class.does.not.Exist'); @@ -296,32 +233,4 @@ public function forName_supports_native_classes() { public function getClasses_returns_a_list_of_class_objects() { Assert::instance('lang.XPClass[]', iterator_to_array(XPClass::getClasses())); } - - #[Test] - public function fixture_class_constants() { - Assert::equals( - ['CONSTANT_STRING' => 'XP Framework', 'CONSTANT_INT' => 15, 'CONSTANT_NULL' => null], - $this->fixture->getConstants() - ); - } - - #[Test, Values(['CONSTANT_STRING', 'CONSTANT_INT', 'CONSTANT_NULL'])] - public function hasConstant_returns_true_for_existing_constant($name) { - Assert::true($this->fixture->hasConstant($name)); - } - - #[Test, Values(['DOES_NOT_EXIST', ''])] - public function hasConstant_returns_false_for_non_existing_constant($name) { - Assert::false($this->fixture->hasConstant($name)); - } - - #[Test, Values([['XP Framework', 'CONSTANT_STRING'], [15, 'CONSTANT_INT'], [null, 'CONSTANT_NULL']])] - public function getConstant_returns_constants_value($value, $name) { - Assert::equals('XP Framework', $this->fixture->getConstant('CONSTANT_STRING')); - } - - #[Test, Expect(ElementNotFoundException::class), Values(['DOES_NOT_EXIST', ''])] - public function getConstant_throws_exception_if_constant_doesnt_exist($name) { - $this->fixture->getConstant($name); - } } \ No newline at end of file