diff --git a/.php_cs.dist b/.php_cs.dist index 40ab7e9162..85a5cfca43 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -40,10 +40,13 @@ return PhpCsFixer\Config::create() '@Symfony' => true, '@Symfony:risky' => true, '@PHPUnit48Migration:risky' => true, + '@PSR2' => true, 'php_unit_no_expectation_annotation' => false, 'array_syntax' => ['syntax' => 'short'], 'fopen_flags' => false, 'ordered_imports' => ['sortAlgorithm' => 'alpha'], 'protected_to_private' => false, 'phpdoc_var_annotation_correct_order' => true, + 'no_superfluous_phpdoc_tags' => false, + 'single_line_throw' => false, ]); diff --git a/.travis.yml b/.travis.yml index 9c3061a62e..2d17f2987e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -148,7 +148,7 @@ install: # Prepare Zephir executable if [ "$BUILD_PHAR" -eq 1 ] then - echo "Build Zephit PHAR" + echo "Build Zephir PHAR" .ci/build-phar.sh sudo ln -s "$(pwd)/zephir.phar" /usr/local/bin/zephir else @@ -184,8 +184,8 @@ script: # Static Code Analysis - phpenv config-rm xdebug.ini || true - - if [ "$BUILD_TYPE" == "analysis" ]; then phpcs; fi - - if [ "$BUILD_TYPE" == "analysis" ]; then php-cs-fixer fix --diff --dry-run -v; fi + - if [ "$BUILD_TYPE" == "analysis" ]; then phpcs --version; phpcs; fi + - if [ "$BUILD_TYPE" == "analysis" ]; then php-cs-fixer --version; php-cs-fixer fix --diff --dry-run -v; fi - if [ "$BUILD_TYPE" == "analysis" ]; then shellcheck .ci/*.sh; fi after_script: diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bdc623ba0..092cfbde77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,26 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +## [0.12.12] - 2019-11-25 +### Added +- Option to set banner for stubs generator + [#1987](https://github.com/phalcon/zephir/1987) + +### Fixed +- Calling object methods from static context yields segmentation fault when + `internal-call-transformation` is set to `TRUE` + [#2000](https://github.com/phalcon/zephir/issues/2000) +- Certain method calls fail when called from static context when + `internal-call-transformation` is set to `TRUE` + [#2005](https://github.com/phalcon/zephir/issues/2005) +- Method context loses track of `this` after calling static method when + `internal-call-transformation` is set to `TRUE` + [#2007](https://github.com/phalcon/zephir/issues/2007) +- Fixed incorrect stubs generation for return type hint + [#1990](https://github.com/phalcon/zephir/issues/1990) +- Fixed incorrect stubs generation for classes in the same namespace + [#2016](https://github.com/phalcon/zephir/issues/2016) + ## [0.12.11] - 2019-11-02 ### Fixed - Fixed arithmetical operations with `zvals` which stores `double` numbers @@ -319,7 +339,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Fixed casting resource to int (only ZendEngine 3) [#1524](https://github.com/phalcon/zephir/issues/1524) -[Unreleased]: https://github.com/phalcon/zephir/compare/0.12.11...HEAD +[Unreleased]: https://github.com/phalcon/zephir/compare/0.12.12...HEAD +[0.12.12]: https://github.com/phalcon/zephir/compare/0.12.11...0.12.12 [0.12.11]: https://github.com/phalcon/zephir/compare/0.12.10...0.12.11 [0.12.10]: https://github.com/phalcon/zephir/compare/0.12.9...0.12.10 [0.12.9]: https://github.com/phalcon/zephir/compare/0.12.8...0.12.9 diff --git a/Library/AliasManager.php b/Library/AliasManager.php index bef31e3aa8..03b37926f5 100644 --- a/Library/AliasManager.php +++ b/Library/AliasManager.php @@ -113,7 +113,10 @@ public function isAliasPresentFor(string $className): bool { $extractAlias = $this->implicitAlias($className); - return !isset($this->aliases[$extractAlias]); + $isClassDeclarated = \in_array($className, $this->aliases); + $classAlias = array_flip($this->aliases)[$className] ?? null; + + return $isClassDeclarated && $classAlias !== $extractAlias; } /** diff --git a/Library/Backends/ZendEngine3/FcallManager.php b/Library/Backends/ZendEngine3/FcallManager.php index b3e106707c..b82974f256 100644 --- a/Library/Backends/ZendEngine3/FcallManager.php +++ b/Library/Backends/ZendEngine3/FcallManager.php @@ -114,7 +114,6 @@ public function genFcallCode() } $codePrinter->output('ZEPHIR_BACKUP_SCOPE(); \\'); - $codePrinter->output('ZEPHIR_BACKUP_THIS_PTR(); \\'); if (!$scope) { $codePrinter->output('ZEPHIR_SET_THIS(object); \\'); $codePrinter->output('ZEPHIR_SET_SCOPE((Z_OBJ_P(object) ? Z_OBJCE_P(object) : NULL), (Z_OBJ_P(object) ? Z_OBJCE_P(object) : NULL)); \\'); @@ -156,7 +155,6 @@ public function genFcallCode() } $codePrinter->output('ZEPHIR_LAST_CALL_STATUS = EG(exception) ? FAILURE : SUCCESS; \\'); - $codePrinter->output('ZEPHIR_RESTORE_THIS_PTR(); \\'); $codePrinter->output('ZEPHIR_RESTORE_SCOPE(); \\'); $codePrinter->decreaseLevel(); diff --git a/Library/Compiler.php b/Library/Compiler.php index bec5d0a080..6940e1deb8 100644 --- a/Library/Compiler.php +++ b/Library/Compiler.php @@ -973,7 +973,8 @@ public function stubs(bool $fromGenerate = false) $stubsGenerator->generate( $this->config->get('namespace'), $path, - $this->config->get('indent', 'extra') + $this->config->get('indent', 'extra'), + $this->config->getBanner() ); } diff --git a/Library/CompilerFile.php b/Library/CompilerFile.php index 2a947bfdb7..3dbc5ebd4f 100644 --- a/Library/CompilerFile.php +++ b/Library/CompilerFile.php @@ -849,13 +849,6 @@ public function compile(Compiler $compiler, StringsManager $stringsManager) foreach ($this->ir as $topStatement) { switch ($topStatement['type']) { case 'class': - if ($interface || $class) { - throw new CompilerException('More than one class defined in the same file', $topStatement); - } - $class = true; - $this->compileClass($compilationContext, $this->namespace, $topStatement); - break; - case 'interface': if ($interface || $class) { throw new CompilerException('More than one class defined in the same file', $topStatement); @@ -867,6 +860,9 @@ public function compile(Compiler $compiler, StringsManager $stringsManager) case 'comment': $this->compileComment($compilationContext, $topStatement); break; + + default: + break; } } diff --git a/Library/Config.php b/Library/Config.php index 1d5fe9a99d..825d2f7cbc 100644 --- a/Library/Config.php +++ b/Library/Config.php @@ -34,6 +34,7 @@ class Config implements \ArrayAccess, \JsonSerializable 'stubs' => [ 'path' => 'ide/%version%/%namespace%/', 'stubs-run-after-generate' => false, + 'banner' => '', ], 'api' => [ 'path' => 'doc/%version%', @@ -328,6 +329,16 @@ public function jsonSerialize() return $this->container; } + /** + * Returns banner from configuration file. + * + * @return string + */ + public function getBanner(): string + { + return $this->get('banner', 'stubs') ?? ''; + } + /** * Populate project configuration. * diff --git a/Library/Console/Command/GenerateCommand.php b/Library/Console/Command/GenerateCommand.php index c11325d93b..29cdecc733 100644 --- a/Library/Console/Command/GenerateCommand.php +++ b/Library/Console/Command/GenerateCommand.php @@ -53,7 +53,8 @@ protected function execute(InputInterface $input, OutputInterface $output) $io = new SymfonyStyle($input, $output); if (\extension_loaded('timecop') && 1 == ini_get('timecop.func_override')) { - $io->getErrorStyle()->warning(<<getErrorStyle()->warning( + << check if the command line argument --output-directory was given * => if not ; check if config config[api][path] was given * - * * @param string $outputDir * * @return string|null diff --git a/Library/Stubs/Generator.php b/Library/Stubs/Generator.php index 6cb0113c1f..144ce2d283 100644 --- a/Library/Stubs/Generator.php +++ b/Library/Stubs/Generator.php @@ -54,10 +54,11 @@ public function __construct(array $files) * @param string $namespace * @param string $path * @param string $indent + * @param string $banner * * @throws Exception\LogicException */ - public function generate(string $namespace, string $path, string $indent) + public function generate(string $namespace, string $path, string $indent, string $banner) { if (empty($path)) { throw new Exception\LogicException( @@ -69,13 +70,13 @@ public function generate(string $namespace, string $path, string $indent) foreach ($this->files as $file) { $class = $file->getClassDefinition(); - $source = $this->buildClass($class, $indent); + $source = $this->buildClass($class, $indent, $banner); $filename = ucfirst($class->getName()).'.zep.php'; - $filePath = $path.str_replace( + $filePath = $path.str_ireplace( $namespace, '', - str_replace($namespace.'\\\\', \DIRECTORY_SEPARATOR, strtolower($class->getNamespace())) + str_replace($namespace.'\\\\', \DIRECTORY_SEPARATOR, $class->getNamespace()) ); $filePath = str_replace('\\', \DIRECTORY_SEPARATOR, $filePath); $filePath = str_replace(\DIRECTORY_SEPARATOR.\DIRECTORY_SEPARATOR, \DIRECTORY_SEPARATOR, $filePath); @@ -94,14 +95,16 @@ public function generate(string $namespace, string $path, string $indent) * * @param ClassDefinition $class * @param string $indent + * @param string $banner * * @throws Exception\RuntimeException * * @return string */ - protected function buildClass(ClassDefinition $class, string $indent): string + protected function buildClass(ClassDefinition $class, string $indent, string $banner): string { $source = 'getNamespace()};".PHP_EOL; /** @var Zephir\AliasManager $aliasManager */ diff --git a/Library/Stubs/MethodDocBlock.php b/Library/Stubs/MethodDocBlock.php index 758f0ffc82..c718daaf67 100644 --- a/Library/Stubs/MethodDocBlock.php +++ b/Library/Stubs/MethodDocBlock.php @@ -13,6 +13,7 @@ use Zephir\AliasManager; use Zephir\ClassMethod; +use Zephir\Types; /** * Stubs Generator. @@ -40,7 +41,10 @@ class MethodDocBlock extends DocBlock /** @var ClassMethod */ private $classMethod; - public function __construct(ClassMethod $method, AliasManager $aliasManager, $indent = ' ') + /** @var Types */ + private $types; + + public function __construct(ClassMethod $method, AliasManager $aliasManager, $indent = ' ', Types $types = null) { parent::__construct($method->getDocBlock(), $indent); @@ -48,6 +52,7 @@ public function __construct(ClassMethod $method, AliasManager $aliasManager, $in $this->aliasManager = $aliasManager; $this->shortcutName = $method->isShortcut() ? $method->getShortcutName() : ''; $this->classMethod = $method; + $this->types = $types ?? new Types(); } /** @@ -114,8 +119,12 @@ protected function parseMethodReturnType(ClassMethod $method) } } - if (!empty($return)) { - $this->return = [implode('|', $return), '']; + $processedTypes = !empty($method->getReturnClassTypes()) ? $return : null; + $returnType = $this->types->getReturnTypeAnnotation($this->classMethod, $processedTypes); + + if (!empty($returnType)) { + // Empty line in array - it's an empty description. Don't remove it! + $this->return = [$returnType, '']; } } diff --git a/Library/Types.php b/Library/Types.php index 6347b8fffe..9c63f8d0ca 100644 --- a/Library/Types.php +++ b/Library/Types.php @@ -26,13 +26,297 @@ final class Types const T_BOOL = 'bool'; const T_STRING = 'string'; const T_ISTRING = 'istring'; - const T_VOID = 'void'; const T_VARIABLE = 'variable'; const T_MIXED = 'mixed'; const T_ARRAY = 'array'; + const T_VOID = 'void'; const T_OBJECT = 'object'; const T_CALLABLE = 'callable'; const T_RESOURCE = 'resource'; const T_ITERABLE = 'iterable'; const T_UNDEFINED = 'undefined'; + + /** + * Gets PHP compatible return type from class method. + * + * @param ClassMethod $method + * + * @return string + */ + public function getReturnTypeAnnotation(ClassMethod $method, array $returnTypes = null): string + { + if (!$method->hasReturnTypes() && !$method->isVoid()) { + return ''; + } + + $returnTypes = $returnTypes ?? $method->getReturnTypes(); + $typesCount = \count($returnTypes); + + $isDynamic = \in_array('var', array_keys($returnTypes)); + $isNullable = $this->isNullable($returnTypes); + + $isBool = $this->areReturnTypesBoolCompatible($returnTypes); + $isNull = $this->areReturnTypesNullCompatible($returnTypes); + $isVoid = $this->areReturnTypesVoidCompatible($returnTypes); + $isArray = $this->areReturnTypesArrayCompatible($returnTypes); + $isDouble = $this->areReturnTypesFloatCompatible($returnTypes); + $isString = $this->areReturnTypesStringCompatible($returnTypes); + $isObject = $this->areReturnTypesObjectCompatible($returnTypes); + $isInteger = $this->areReturnTypesIntegerCompatible($returnTypes); + $isNumeric = $this->isNumeric($returnTypes); + $isIterable = $this->areReturnTypesIterableCompatible($returnTypes); + $isResource = $this->areReturnTypesResourceCompatible($returnTypes); + + $isTypeHinted = $method->isReturnTypesHintDetermined(); + $isBasicTypes = $isArray || $isBool || $isDouble || $isInteger || $isResource || $isString || $isVoid || $isNumeric; + + $nullableType = $isNullable ? '|null' : ''; + + if ($method->isVoid() || $isVoid) { + return static::T_VOID; + } + + if ($isInteger) { + return static::T_INT.$nullableType; + } + + if ($isDouble) { + return static::T_FLOAT.$nullableType; + } + + if ($isBool) { + return static::T_BOOL.$nullableType; + } + + if ($isString) { + return static::T_STRING.$nullableType; + } + + if ($isNull && 1 === $typesCount) { + return static::T_NULL; + } + + if ($isArray) { + return static::T_ARRAY; + } + + if ($isObject) { + return static::T_OBJECT; + } + + if ($isIterable) { + return static::T_ITERABLE; + } + + if ($isResource) { + return static::T_RESOURCE; + } + + if ($method->areReturnTypesCompatible() && !$isTypeHinted) { + return static::T_MIXED.$nullableType; + } + + if ($isTypeHinted && !$isBasicTypes && !$isDynamic) { + return implode('|', array_keys($returnTypes)); + } + + return static::T_MIXED.$nullableType; + } + + /** + * Match Zephir types with Integer type. + * + * @param array $types + * + * @return bool + */ + private function areReturnTypesIntegerCompatible(array $types): bool + { + return $this->areReturnTypesCompatible( + $types, + [ + self::T_INT, + self::T_UINT, + self::T_CHAR, + self::T_UCHAR, + self::T_LONG, + self::T_ULONG, + ] + ); + } + + /** + * Match Zephir types with Float type. + * + * @param array $types + * + * @return bool + */ + private function areReturnTypesFloatCompatible(array $types): bool + { + return $this->areReturnTypesCompatible( + $types, + [ + self::T_FLOAT, + self::T_DOUBLE, + ] + ); + } + + /** + * Match Zephir types with Boolean type. + * + * @param array $types + * + * @return bool + */ + private function areReturnTypesBoolCompatible(array $types): bool + { + return $this->areReturnTypesCompatible($types, [static::T_BOOL]); + } + + /** + * Match Zephir types with String type. + * + * @param array $types + * + * @return bool + */ + private function areReturnTypesStringCompatible(array $types): bool + { + return $this->areReturnTypesCompatible( + $types, + [ + static::T_STRING, + static::T_ISTRING, + ], + $this->isNullable($types) + ); + } + + /** + * Match Zephir types with Null type. + * + * @param array $types + * + * @return bool + */ + private function areReturnTypesNullCompatible(array $types): bool + { + return $this->areReturnTypesCompatible($types, [static::T_NULL]); + } + + /** + * Match Zephir types with Array type. + * + * @param array $types + * + * @return bool + */ + private function areReturnTypesArrayCompatible(array $types): bool + { + return $this->areReturnTypesCompatible($types, [static::T_ARRAY]); + } + + /** + * Match Zephir types with Object type. + * + * @param array $types + * + * @return bool + */ + private function areReturnTypesObjectCompatible(array $types): bool + { + return $this->areReturnTypesCompatible($types, [static::T_OBJECT]); + } + + /** + * Match Zephir types with Iterable type. + * + * @param array $types + * + * @return bool + */ + private function areReturnTypesIterableCompatible(array $types): bool + { + return $this->areReturnTypesCompatible($types, [static::T_ITERABLE]); + } + + /** + * Match Zephir types with Resource type. + * + * @param array $types + * + * @return bool + */ + private function areReturnTypesResourceCompatible(array $types): bool + { + return $this->areReturnTypesCompatible($types, [static::T_RESOURCE]); + } + + /** + * Match Zephir types with Void type. + * + * @param array $types + * + * @return bool + */ + private function areReturnTypesVoidCompatible(array $types): bool + { + return $this->areReturnTypesCompatible($types, [static::T_VOID]); + } + + /** + * Check if Zephir types is a Numeric type compatible. + * + * @param array $types + * + * @return bool + */ + private function isNumeric(array $types): bool + { + return $this->areReturnTypesCompatible($types, [static::T_NUMBER]); + } + + /** + * Check if Zephir types can be Nullable. + * + * @param array $types + * + * @return bool + */ + private function isNullable(array $types): bool + { + return \array_key_exists(static::T_NULL, $types) + && 1 !== \count($types); + } + + /** + * Match if return types from Zephir are compatible + * with allowed return types from PHP. + * + * @param array $types - Return types from parser + * @param array $allowedTypes - Allowed return types + * + * @return bool + */ + private function areReturnTypesCompatible(array $types, array $allowedTypes, bool $isNullable = false): bool + { + $result = null; + $areEquals = false; + + if ($isNullable) { + array_push($allowedTypes, static::T_NULL); + } + + foreach ($types as $type => $data) { + $areEquals = \in_array($type, $allowedTypes); + + $result = isset($result) + ? ($areEquals && $result) + : $areEquals; + } + + return $result ?? false; + } } diff --git a/Library/Zephir.php b/Library/Zephir.php index ef6e336497..f7595703e4 100644 --- a/Library/Zephir.php +++ b/Library/Zephir.php @@ -16,7 +16,7 @@ */ final class Zephir { - const VERSION = '0.12.11-$Id$'; + const VERSION = '0.12.12-$Id$'; const LOGO = <<<'ASCII' _____ __ _ diff --git a/appveyor.yml b/appveyor.yml index 3f0486516c..46188ecd25 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 0.12.11-{build} +version: 0.12.12-{build} environment: matrix: diff --git a/config.json b/config.json index 136fabc730..fbdf745117 100644 --- a/config.json +++ b/config.json @@ -18,7 +18,8 @@ "stubs": { "path": "ide\/%version%\/%namespace%\/", - "stubs-run-after-generate": false + "stubs-run-after-generate": false, + "banner": "/**\n * This file is part of the Zephir.\n *\n * (c) Zephir Team \n *\n * For the full copyright and license information, please view\n * the LICENSE file that was distributed with this source code.\n */" }, "api": { diff --git a/ext/kernel/fcall.h b/ext/kernel/fcall.h index 1237bf2f60..cfb506711e 100644 --- a/ext/kernel/fcall.h +++ b/ext/kernel/fcall.h @@ -58,18 +58,6 @@ typedef enum _zephir_call_type { ZEPHIR_LAST_CALL_STATUS = zephir_call_zval_func_aparams(return_value_ptr, func_name, cache, cache_slot, ZEPHIR_CALL_NUM_PARAMS(params_), ZEPHIR_PASS_CALL_PARAMS(params_)); \ } while (0) -/* Saves the if pointer, and called/calling scope */ -#define ZEPHIR_BACKUP_THIS_PTR() \ - zend_object *old_this_ptr = Z_OBJ(EG(current_execute_data)->This) ? Z_OBJ(EG(current_execute_data)->This) : NULL; - -#define ZEPHIR_RESTORE_THIS_PTR() do { \ - if (old_this_ptr) { \ - ZEPHIR_SET_THIS_OBJ(old_this_ptr); \ - } else { \ - ZEPHIR_SET_THIS_EXPLICIT_NULL(); \ - } \ -} while (0) - #define ZEPHIR_SET_THIS(zv) ZEPHIR_SET_THIS_OBJ((zv ? Z_OBJ_P(zv) : NULL)) #define ZEPHIR_SET_THIS_EXPLICIT_NULL() \ ZVAL_NULL(&EG(current_execute_data)->This); \ @@ -77,19 +65,33 @@ typedef enum _zephir_call_type { #define ZEPHIR_SET_THIS_OBJ(obj) \ if (obj) { \ - Z_OBJ_P(&EG(current_execute_data)->This) = obj; \ + ZVAL_OBJ(&EG(current_execute_data)->This, obj); \ } \ else { ZEPHIR_SET_THIS_EXPLICIT_NULL(); } \ + #if PHP_VERSION_ID >= 70100 #define ZEPHIR_BACKUP_SCOPE() \ zend_class_entry *old_scope = EG(fake_scope); \ - zend_class_entry *old_called_scope = zend_get_called_scope(EG(current_execute_data)); + zend_execute_data *old_call = execute_data; \ + zend_execute_data *old_execute_data = EG(current_execute_data), new_execute_data; \ + if (!EG(current_execute_data)) { \ + memset(&new_execute_data, 0, sizeof(zend_execute_data)); \ + execute_data = EG(current_execute_data) = &new_execute_data; \ + } else { \ + new_execute_data = *EG(current_execute_data); \ + new_execute_data.prev_execute_data = EG(current_execute_data); \ + new_execute_data.call = NULL; \ + new_execute_data.opline = NULL; \ + new_execute_data.func = NULL; \ + execute_data = EG(current_execute_data) = &new_execute_data; \ + } #define ZEPHIR_RESTORE_SCOPE() \ - zephir_set_called_scope(EG(current_execute_data), old_called_scope); \ EG(fake_scope) = old_scope; \ + execute_data = old_call; \ + EG(current_execute_data) = old_execute_data; #define ZEPHIR_SET_SCOPE(_scope, _scope_called) \ EG(fake_scope) = _scope; \ @@ -99,15 +101,28 @@ typedef enum _zephir_call_type { #define ZEPHIR_BACKUP_SCOPE() \ zend_class_entry *old_scope = EG(scope); \ - zend_class_entry *old_called_scope = EG(current_execute_data)->called_scope; + zend_execute_data *old_call = execute_data; \ + zend_execute_data *old_execute_data = EG(current_execute_data), new_execute_data; \ + if (!EG(current_execute_data)) { \ + memset(&new_execute_data, 0, sizeof(zend_execute_data)); \ + execute_data = EG(current_execute_data) = &new_execute_data; \ + } else { \ + new_execute_data = *EG(current_execute_data); \ + new_execute_data.prev_execute_data = EG(current_execute_data); \ + new_execute_data.call = NULL; \ + new_execute_data.opline = NULL; \ + new_execute_data.func = NULL; \ + execute_data = EG(current_execute_data) = &new_execute_data; \ + } #define ZEPHIR_RESTORE_SCOPE() \ - EG(current_execute_data)->called_scope = old_called_scope; \ EG(scope) = old_scope; \ + execute_data = old_call; \ + EG(current_execute_data) = old_execute_data; #define ZEPHIR_SET_SCOPE(_scope, _scope_called) \ EG(scope) = _scope; \ - EG(current_execute_data)->called_scope = _scope_called; \ + EG(current_execute_data)->called_scope = _scope_called; #endif @@ -437,13 +452,13 @@ static inline void zephir_set_called_scope(zend_execute_data *ex, zend_class_ent if (Z_TYPE(ex->This) == IS_OBJECT) { Z_OBJCE(ex->This) = called_scope; return; - } else if (Z_CE(ex->This)) { - Z_CE(ex->This) = called_scope; - return; - } else if (ex->func) { + }else if (ex->func) { if (ex->func->type != ZEND_INTERNAL_FUNCTION || ex->func->common.scope) { return; } + } else { + Z_CE(ex->This) = called_scope; + return; } ex = ex->prev_execute_data; } diff --git a/ext/php_test.h b/ext/php_test.h index 78371f790a..66ac3107e1 100644 --- a/ext/php_test.h +++ b/ext/php_test.h @@ -14,7 +14,7 @@ #define PHP_TEST_VERSION "1.0.0" #define PHP_TEST_EXTNAME "test" #define PHP_TEST_AUTHOR "Zephir Team and contributors" -#define PHP_TEST_ZEPVERSION "0.12.11-$Id$" +#define PHP_TEST_ZEPVERSION "0.12.12-$Id$" #define PHP_TEST_DESCRIPTION "Description test for
Test Extension." typedef struct _zephir_struct_db { diff --git a/kernels/ZendEngine3/fcall.h b/kernels/ZendEngine3/fcall.h index 1237bf2f60..cfb506711e 100644 --- a/kernels/ZendEngine3/fcall.h +++ b/kernels/ZendEngine3/fcall.h @@ -58,18 +58,6 @@ typedef enum _zephir_call_type { ZEPHIR_LAST_CALL_STATUS = zephir_call_zval_func_aparams(return_value_ptr, func_name, cache, cache_slot, ZEPHIR_CALL_NUM_PARAMS(params_), ZEPHIR_PASS_CALL_PARAMS(params_)); \ } while (0) -/* Saves the if pointer, and called/calling scope */ -#define ZEPHIR_BACKUP_THIS_PTR() \ - zend_object *old_this_ptr = Z_OBJ(EG(current_execute_data)->This) ? Z_OBJ(EG(current_execute_data)->This) : NULL; - -#define ZEPHIR_RESTORE_THIS_PTR() do { \ - if (old_this_ptr) { \ - ZEPHIR_SET_THIS_OBJ(old_this_ptr); \ - } else { \ - ZEPHIR_SET_THIS_EXPLICIT_NULL(); \ - } \ -} while (0) - #define ZEPHIR_SET_THIS(zv) ZEPHIR_SET_THIS_OBJ((zv ? Z_OBJ_P(zv) : NULL)) #define ZEPHIR_SET_THIS_EXPLICIT_NULL() \ ZVAL_NULL(&EG(current_execute_data)->This); \ @@ -77,19 +65,33 @@ typedef enum _zephir_call_type { #define ZEPHIR_SET_THIS_OBJ(obj) \ if (obj) { \ - Z_OBJ_P(&EG(current_execute_data)->This) = obj; \ + ZVAL_OBJ(&EG(current_execute_data)->This, obj); \ } \ else { ZEPHIR_SET_THIS_EXPLICIT_NULL(); } \ + #if PHP_VERSION_ID >= 70100 #define ZEPHIR_BACKUP_SCOPE() \ zend_class_entry *old_scope = EG(fake_scope); \ - zend_class_entry *old_called_scope = zend_get_called_scope(EG(current_execute_data)); + zend_execute_data *old_call = execute_data; \ + zend_execute_data *old_execute_data = EG(current_execute_data), new_execute_data; \ + if (!EG(current_execute_data)) { \ + memset(&new_execute_data, 0, sizeof(zend_execute_data)); \ + execute_data = EG(current_execute_data) = &new_execute_data; \ + } else { \ + new_execute_data = *EG(current_execute_data); \ + new_execute_data.prev_execute_data = EG(current_execute_data); \ + new_execute_data.call = NULL; \ + new_execute_data.opline = NULL; \ + new_execute_data.func = NULL; \ + execute_data = EG(current_execute_data) = &new_execute_data; \ + } #define ZEPHIR_RESTORE_SCOPE() \ - zephir_set_called_scope(EG(current_execute_data), old_called_scope); \ EG(fake_scope) = old_scope; \ + execute_data = old_call; \ + EG(current_execute_data) = old_execute_data; #define ZEPHIR_SET_SCOPE(_scope, _scope_called) \ EG(fake_scope) = _scope; \ @@ -99,15 +101,28 @@ typedef enum _zephir_call_type { #define ZEPHIR_BACKUP_SCOPE() \ zend_class_entry *old_scope = EG(scope); \ - zend_class_entry *old_called_scope = EG(current_execute_data)->called_scope; + zend_execute_data *old_call = execute_data; \ + zend_execute_data *old_execute_data = EG(current_execute_data), new_execute_data; \ + if (!EG(current_execute_data)) { \ + memset(&new_execute_data, 0, sizeof(zend_execute_data)); \ + execute_data = EG(current_execute_data) = &new_execute_data; \ + } else { \ + new_execute_data = *EG(current_execute_data); \ + new_execute_data.prev_execute_data = EG(current_execute_data); \ + new_execute_data.call = NULL; \ + new_execute_data.opline = NULL; \ + new_execute_data.func = NULL; \ + execute_data = EG(current_execute_data) = &new_execute_data; \ + } #define ZEPHIR_RESTORE_SCOPE() \ - EG(current_execute_data)->called_scope = old_called_scope; \ EG(scope) = old_scope; \ + execute_data = old_call; \ + EG(current_execute_data) = old_execute_data; #define ZEPHIR_SET_SCOPE(_scope, _scope_called) \ EG(scope) = _scope; \ - EG(current_execute_data)->called_scope = _scope_called; \ + EG(current_execute_data)->called_scope = _scope_called; #endif @@ -437,13 +452,13 @@ static inline void zephir_set_called_scope(zend_execute_data *ex, zend_class_ent if (Z_TYPE(ex->This) == IS_OBJECT) { Z_OBJCE(ex->This) = called_scope; return; - } else if (Z_CE(ex->This)) { - Z_CE(ex->This) = called_scope; - return; - } else if (ex->func) { + }else if (ex->func) { if (ex->func->type != ZEND_INTERNAL_FUNCTION || ex->func->common.scope) { return; } + } else { + Z_CE(ex->This) = called_scope; + return; } ex = ex->prev_execute_data; } diff --git a/unit-tests/Extension/ConcatTest.php b/unit-tests/Extension/ConcatTest.php index 210d0ed8d6..02af02682c 100644 --- a/unit-tests/Extension/ConcatTest.php +++ b/unit-tests/Extension/ConcatTest.php @@ -62,7 +62,7 @@ public function shouldConcatenateStringWithVarDouble() { $t = new Concat(); $this->assertSame( - /* @lang text */ 'SELECT * FROM TEST WHERE value <= 946.5 AND value >= 473.25', + 'SELECT * FROM TEST WHERE value <= 946.5 AND value >= 473.25', $t->testConcat4(1893) ); } diff --git a/unit-tests/Zephir/Test/AliasManagerTest.php b/unit-tests/Zephir/Test/AliasManagerTest.php index c72b0aa7be..1bb35f025a 100644 --- a/unit-tests/Zephir/Test/AliasManagerTest.php +++ b/unit-tests/Zephir/Test/AliasManagerTest.php @@ -54,16 +54,21 @@ public function baseTestSuiteProvider(): array ], 'without explicit alias' => [ [ - 'name' => 'Throwable', - 'alias' => 'Throwable', + 'name' => '\\Throwable', + ], + ], + 'without explicit alias and FQN' => [ + [ + 'name' => 'Zephir\\Compiler\\CompilerInterface', ], ], ]; } - public function aliasDataProvider(): array + public function aliasProvider(): array { $expected = [ + // [ alias => name ] [ 'EventsManagerInterface' => 'Bug\\Events\\ManagerInterface', ], @@ -71,7 +76,10 @@ public function aliasDataProvider(): array 'EventsManagerInterface' => '\\Bug\\Events\\ManagerInterface', ], [ - 'Throwable' => 'Throwable', + 'Throwable' => '\Throwable', + ], + [ + 'CompilerInterface' => 'Zephir\\Compiler\\CompilerInterface', ], ]; @@ -80,7 +88,7 @@ public function aliasDataProvider(): array /** * @test - * @dataProvider aliasDataProvider + * @dataProvider aliasProvider */ public function shouldProperAddStatements(array $useStatements, array $expected) { @@ -88,18 +96,20 @@ public function shouldProperAddStatements(array $useStatements, array $expected) 'aliases' => [$useStatements], ]); - $alias = $useStatements['alias']; $className = $useStatements['name']; + $parts = explode('\\', $className); + $alias = $useStatements['alias'] ?? $parts[\count($parts) - 1]; + $this->assertTrue($this->testAliasMgr->isAlias($alias)); - $this->assertSame($this->testAliasMgr->getAliases(), $expected); - $this->assertSame($this->testAliasMgr->getAlias($alias), $className); + $this->assertSame($expected, $this->testAliasMgr->getAliases()); + $this->assertSame($className, $this->testAliasMgr->getAlias($alias)); } - public function statementDataProvider(): array + public function statementProvider(): array { $expected = [ - true, true, false, + true, true, false, false, ]; return $this->injectExpectedResult($expected); @@ -107,7 +117,7 @@ public function statementDataProvider(): array /** * @test - * @dataProvider statementDataProvider + * @dataProvider statementProvider */ public function shouldCheckAliasedStatement(array $useStatements, bool $expected) { @@ -115,11 +125,13 @@ public function shouldCheckAliasedStatement(array $useStatements, bool $expected 'aliases' => [$useStatements], ]); - $alias = $useStatements['alias']; $className = $useStatements['name']; - $this->assertSame($this->testAliasMgr->isUseStatementAliased($alias), $expected); - $this->assertSame($this->testAliasMgr->isAliasPresentFor($className), $expected); + $parts = explode('\\', $className); + $alias = $useStatements['alias'] ?? $parts[\count($parts) - 1]; + + $this->assertSame($expected, $this->testAliasMgr->isUseStatementAliased($alias)); + $this->assertSame($expected, $this->testAliasMgr->isAliasPresentFor($className)); } public function classNameDataProvider(): array @@ -127,7 +139,8 @@ public function classNameDataProvider(): array $expected = [ 'EventsManagerInterface', '\Bug\Events\ManagerInterface', - 'Throwable', + '\Throwable', + 'CompilerInterface', ]; return $this->injectExpectedResult($expected); @@ -145,6 +158,40 @@ public function shouldGetAliasForClassName(array $useStatements, string $expecte $className = $useStatements['name']; - $this->assertSame($this->testAliasMgr->getAliasForClassName($className), $expected); + $this->assertSame($expected, $this->testAliasMgr->getAliasForClassName($className)); + } + + /** @test */ + public function shouldCheckIfAliasPresentForClass() + { + $this->testAliasMgr->add([ + 'aliases' => [ + [ + 'name' => 'One', + 'alias' => 'One', + ], + [ + 'name' => 'Bug\\Events\\ManagerInterface', + 'alias' => 'EventsManagerInterface', + ], + [ + 'name' => '\\Root\SomeNamespace\\SomeClassName', + 'alias' => 'SomeClassName', + ], + [ + 'name' => 'AnotherClass', + 'alias' => 'AnotherClass', + ], + [ + 'name' => 'Bug\\Storage\\FileSystem', + ], + ], + ]); + + $this->assertTrue($this->testAliasMgr->isAliasPresentFor('Bug\\Events\\ManagerInterface')); + $this->assertFalse($this->testAliasMgr->isAliasPresentFor('\\Root\SomeNamespace\\SomeClassName')); + $this->assertFalse($this->testAliasMgr->isAliasPresentFor('AnotherClass')); + $this->assertFalse($this->testAliasMgr->isAliasPresentFor('NonExistingClass')); + $this->assertFalse($this->testAliasMgr->isAliasPresentFor('Bug\\Storage\\FileSystem')); } } diff --git a/unit-tests/Zephir/Test/ConfigTest.php b/unit-tests/Zephir/Test/ConfigTest.php index 0651463850..76b87dbb12 100644 --- a/unit-tests/Zephir/Test/ConfigTest.php +++ b/unit-tests/Zephir/Test/ConfigTest.php @@ -23,10 +23,14 @@ class ConfigTest extends TestCase */ private $pwd; + /** @var Config $config */ + private $config; + public function setUp() { /* Store the current directory before to be change */ $this->pwd = getcwd(); + $this->config = new Config(); } /** @@ -41,82 +45,204 @@ public function tearDown() $this->cleanTmpConfigFile(); } + /** + * Clean config.json file into tmp dir. + */ + private function cleanTmpConfigFile() + { + /* clean config.json into tmp dir */ + $tmpConfigFile = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'config.json'; + + if (file_exists($tmpConfigFile)) { + unlink($tmpConfigFile); + } + } + + /** + * Returns Stubs banner for test suite. + * + * @return string + */ + private function stubsBanner(): string + { + return << + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ +DOC; + } + /** * Test when we have a bad config.json file. * + * @test * @expectedException \Zephir\Exception * @expectedExceptionMessage The config.json file is not valid or there is * no Zephir extension initialized in this directory. */ - public function testConstructWithBadConfigFile() + public function constructWithBadConfigFile() { chdir(\constant('ZEPHIRPATH').'/unit-tests/fixtures/badconfig'); new Config(); } - public function testGetWithoutNamespace() + /** + * Test data provider. + * + * [ + * 'test name' => [ + * [$key, $value, $namespace], $expected, + * ], + * ... + * ] + * + * @return array + */ + public function setConfigProvider(): array { - $config = new Config(); - $config->set('verbose', false); - $this->assertFalse($config->get('verbose')); + return [ + 'set with namespace' => [ + ['unused-variable', false, 'warnings'], false, + ], + 'set without namespace' => [ + ['config', true, null], true, + ], + 'get with namespace' => [ + ['unused-variable', true, 'warnings'], true, + ], + 'get without namespace' => [ + ['verbose', false, null], false, + ], + 'directive don`t have duplicates with namespace' => [ + ['ext.some_key', 'some_value', 'test'], 'some_value', + ], + 'directive don`t have duplicates without namespace' => [ + ['test.my_setting_1', 'test', null], 'test', + ], + ]; } - public function testGetWithNamespace() + /** + * @test + * @dataProvider setConfigProvider + */ + public function shouldSetConfigParams(array $test, $expected) { - $config = new Config(); - $config->get('unused-variable', true, 'warnings'); - $this->assertTrue($config->get('unused-variable', 'warnings')); - } + list($key, $value, $namespace) = $test; + $this->config->set($key, $value, $namespace); - public function testSetWithoutNamespace() - { - $config = new Config(); - $config->set('config', true); - $this->assertTrue($config->get('config')); + $actual = $this->config->get($key, $namespace); + + $this->assertSame($expected, $actual); } - public function testSetWithNamespace() + public function defaultConfigProvider(): array { - $config = new Config(); - $config->set('unused-variable', false, 'warnings'); - $this->assertFalse($config->get('unused-variable', 'warnings')); + return [ + // 'test suite name' => [$namespace, $key, $expected,] + 'not existing param' => ['test.my_setting_1', 'test', null], + 'stubs path' => ['stubs', 'path', 'ide/%version%/%namespace%/'], + 'stubs run' => ['stubs', 'stubs-run-after-generate', false], + 'stubs banner' => ['stubs', 'banner', $this->stubsBanner()], + 'api path' => ['api', 'path', 'doc/%version%'], + 'api path' => ['api', 'path', 'doc/%version%'], + 'warnings unused-variable' => ['warnings', 'unused-variable', true], + 'optimizations static-type-inference' => ['optimizations', 'static-type-inference', true], + 'extra indent' => ['extra', 'indent', 'spaces'], + 'namespace' => [null, 'namespace', 'test'], + 'name' => [null, 'name', 'Test Extension'], + 'author' => [null, 'author', 'Zephir Team and contributors'], + 'globals test_setting_1' => ['globals', 'test_setting_1', ['type' => 'bool', 'default' => true]], + 'globals db.my_setting_1' => ['globals', 'db.my_setting_1', ['type' => 'bool', 'default' => false]], + ]; } /** - * Test saveOnExit method. + * @test + * @dataProvider defaultConfigProvider */ - public function testSaveOnExit() + public function shouldGetDefaultConfigParams($namespace, string $key, $expected) { - chdir(sys_get_temp_dir()); - $config = new Config(); - $config->set('name', 'foo'); - $config->dumpToFile(); - $configJson = json_decode(file_get_contents('config.json'), true); - $this->assertInternalType('array', $configJson); - $this->assertSame($configJson['name'], 'foo'); - $this->cleanTmpConfigFile(); + $actual = $this->config->get($key, $namespace); + + $this->assertSame($expected, $actual); + } + + public function offsetConfigProvider(): array + { + return [ + // 'test suite name' => [$key, $expected,] + 'globals test_setting_1 with namespace' => [ + ['globals' => 'test_setting_1'], ['type' => 'bool', 'default' => true], + ], + 'globals test_setting_1 without namespace' => [ + ['globals' => 'test_setting_1'], ['type' => 'bool', 'default' => true], + ], + 'info header without namespace' => [ + ['requires' => 'extensions'], ['PDO', 'SPL', 'standard', 'hash', 'json'], + ], + ]; } /** - * Clean config.json file into tmp dir. + * @test + * @dataProvider offsetConfigProvider */ - private function cleanTmpConfigFile() + public function shouldGetWithOffsetConfigParams(array $key, array $expected) { - /* clean config.json into tmp dir */ - $tmpConfigFile = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'config.json'; + $actual = $this->config->offsetGet($key); - if (file_exists($tmpConfigFile)) { - unlink($tmpConfigFile); - } + $this->assertSame($expected, $actual); } /** @test */ - public function directiveShowsWithoutDuplicatedNamespace() + public function shouldUnsetConfigParams() { - $config = new Config(); - $config->set('ext.some_key', 'some_value', 'test'); + $initials = $this->config->get('test_setting_1', 'globals'); + + $this->assertTrue($this->config->offsetExists('globals')); + $this->assertSame( + [ + 'type' => 'bool', + 'default' => true, + ], + $initials + ); + + $this->config->offsetUnset('globals'); + $actual = $this->config->get('test_setting_1', 'globals'); + + $this->assertFalse($this->config->offsetExists('globals')); + $this->assertNull($actual); + } - $this->assertSame($config->get('ext.some_key', 'test'), 'some_value'); - $this->assertSame($config->get('test.my_setting_1', 'test'), null); + /** @test */ + public function shouldGetBannerFromConfig() + { + $this->assertSame($this->stubsBanner(), $this->config->getBanner()); + + $this->config->offsetUnset('stubs'); + $this->assertSame('', $this->config->getBanner()); + } + + /** @test */ + public function shouldSaveConfigOnExit() + { + chdir(sys_get_temp_dir()); + + $this->config->set('name', 'foo'); + $this->config->dumpToFile(); + + $configJson = json_decode(file_get_contents('config.json'), true); + + $this->assertInternalType('array', $configJson); + $this->assertSame($configJson['name'], 'foo'); + + $this->cleanTmpConfigFile(); } } diff --git a/unit-tests/Zephir/Test/TypesTest.php b/unit-tests/Zephir/Test/TypesTest.php new file mode 100644 index 0000000000..c00bd94536 --- /dev/null +++ b/unit-tests/Zephir/Test/TypesTest.php @@ -0,0 +1,225 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Zephir\Test; + +use PHPUnit\Framework\TestCase; +use Zephir\ClassDefinition; +use Zephir\ClassMethod; +use Zephir\Types; + +class TypesTest extends TestCase +{ + private function baseClassDefinition(array $types): array + { + return [ + 'type' => 'return-type', + 'list' => array_map( + function ($type) { + return [ + 'type' => 'return-type-parameter', + 'data-type' => $type, + 'mandatory' => 0, + 'file' => 'stubs.zep', + 'line' => 1, + 'char' => 42, + ]; + }, + $types + ), + ]; + } + + private function objectClassDefinition(array $types): array + { + $ret = []; + + foreach ($types as $alias) { + $ret[$alias] = 'Stubs\\'.$alias; + } + + return $ret; + } + + private function processVariableReturnTypes(array $types): array + { + return [ + 'type' => 'return-type', + 'list' => array_map( + function ($type) { + return [ + 'type' => 'return-type-parameter', + 'cast' => [ + 'type' => 'variable', + 'value' => $type, + 'file' => 'stubs.zep', + 'line' => 8, + 'char' => 5, + ], + 'collection' => 0, + 'file' => 'stubs.zep', + 'line' => 8, + 'char' => 5, + ]; + }, + $types + ), + ]; + } + + private function buildMethod(array $testData, string $definition): ClassMethod + { + return new ClassMethod( + new ClassDefinition('Zephir', 'testMethod'), + ['public'], + 'exampleMethodName', + null, + null, + null, + $this->$definition($testData) + ); + } + + public function typesDataProvider(): array + { + return [ + [ + ['int'], 'int', + ], + [ + ['uint'], 'int', + ], + [ + ['char'], 'int', + ], + [ + ['uchar'], 'int', + ], + [ + ['long'], 'int', + ], + [ + ['ulong'], 'int', + ], + [ + ['double'], 'float', + ], + [ + ['float'], 'float', + ], + [ + ['number'], 'mixed', + ], + [ + ['null'], 'null', + ], + [ + ['bool'], 'bool', + ], + [ + ['string'], 'string', + ], + [ + ['istring'], 'string', + ], + [ + ['array'], 'array', + ], + [ + ['object'], 'object', + ], + [ + ['iterable'], 'iterable', + ], + [ + ['resource'], 'resource', + ], + [ + ['void'], 'void', + ], + [ + ['callable'], 'mixed', + ], + [ + ['var', 'null'], 'mixed|null', + ], + [ + ['string', 'null'], 'string|null', + ], + [ + ['char', 'ulong', 'int'], 'int', + ], + [ + ['mixed', 'string'], 'mixed|string', + ], + ]; + } + + /** + * @test + * @dataProvider typesDataProvider + */ + public function shouldResolveCompatibleTypeForBaseTypes(array $returnTypes, string $expected) + { + $testMethod = $this->buildMethod($returnTypes, 'baseClassDefinition'); + $testTypes = new Types(); + + $actual = $testTypes->getReturnTypeAnnotation($testMethod); + + $this->assertSame($actual, $expected); + } + + public function objectsDataProvider(): array + { + return [ + [ + ['EventsManagerInterface'], 'EventsManagerInterface', + ], + [ + ['EventsManagerInterface', 'StdClass'], 'EventsManagerInterface|StdClass', + ], + [ + ['EventsManagerInterface', 'null'], 'EventsManagerInterface|null', + ], + [ + ['SomeNamespace\EventsManagerInterface', 'null'], 'SomeNamespace\EventsManagerInterface|null', + ], + [ + ['\SomeNamespace\EventsManagerInterface'], '\SomeNamespace\EventsManagerInterface', + ], + ]; + } + + /** + * @test + * @dataProvider objectsDataProvider + */ + public function shouldResolveCompatibleTypeForObjects(array $returnTypes, string $expected) + { + // This processes into MethodDocBlock with AliasManager resolving + $processedReturnTypes = $this->objectClassDefinition($returnTypes); + + $testMethod = $this->buildMethod($returnTypes, 'objectClassDefinition'); + + $testMethod->setReturnTypes( + $this->processVariableReturnTypes($returnTypes) + ); + + $testTypes = new Types(); + + $actual = $testTypes->getReturnTypeAnnotation( + $testMethod, + $processedReturnTypes + ); + + $this->assertSame($actual, $expected); + } +} diff --git a/unit-tests/fixtures/stubs/issues/expected/Issue_1900.zep.php b/unit-tests/fixtures/stubs/issues/expected/Issue_1900.zep.php new file mode 100644 index 0000000000..e3e717d086 --- /dev/null +++ b/unit-tests/fixtures/stubs/issues/expected/Issue_1900.zep.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +class Issue_1900 implements AliasedManagerInterface +{ + + public $eventsManager; + + /** + * @var array $collect - descr for collect var. + */ + private $collect; + + + static protected $uniqueId = 0; + + + /** + * @return AliasedManagerInterface + */ + public function getEventsManager(): AliasedManagerInterface + { + } + + /** + * @return \StdClass + */ + public function test01(): \StdClass + { + } + + /** + * Attach a listener to the events manager + * + * @param object|callable $handler + * @param string $eventType + * @param int $priority + * @return void + */ + public function attach(string $eventType, $handler, int $priority = 1) + { + } + + /** + * Returns if priorities are enabled + * + * @return bool + */ + public function arePrioritiesEnabled(): bool + { + } + + /** + * Tells the event manager if it needs to collect all the responses returned + * by every registered listener in a single fire + * + * @param bool $collect + * @param string $eventType + * @param mixed $handler + * @return void + */ + public function collectResponses(bool $collect, string $eventType, $handler) + { + } + + /** + * Fires an event in the events manager causing the active listeners to be + * notified about it + * + * ```php + * $eventsManager->fire("db", $connection); + * ``` + * + * @param object $source + * @param mixed $data + * @return mixed + * @param string $eventType + * @param bool $cancelable + */ + public function fire(string $eventType, $source, $data = null, bool $cancelable = true) + { + } + + /** + * Internal handler to call a queue of events + * + * @return mixed + * @param SplPriorityQueue $queue + * @param \Psr\Http\Message\RequestInterface $event + */ + final public function fireQueue(SplPriorityQueue $queue, \Psr\Http\Message\RequestInterface $event): AliasedManagerInterface + { + } + + /** + * Returns all the attached listeners of a certain type + * + * @param string $type + * @return array + */ + public function getListeners(string $type): array + { + } + + /** + * @return resource + */ + public function getResources() + { + } + + /** + * Check whether certain type of event has listeners + * + * @param string $type + * @return AliasedManagerInterface + */ + public function hasListeners(string $type): AliasedManagerInterface + { + } + +} diff --git a/unit-tests/fixtures/stubs/issues/expected/Issue_1986.zep.php b/unit-tests/fixtures/stubs/issues/expected/Issue_1986.zep.php index 9143ec7b32..052dd6bb9a 100644 --- a/unit-tests/fixtures/stubs/issues/expected/Issue_1986.zep.php +++ b/unit-tests/fixtures/stubs/issues/expected/Issue_1986.zep.php @@ -22,6 +22,7 @@ public function getEventsManager(): EventsManagerInterface * * @param object|callable $handler * @param string $eventType + * @return void */ public function attach(string $eventType, $handler) { diff --git a/unit-tests/fixtures/stubs/issues/stubs/issue_1900.zep b/unit-tests/fixtures/stubs/issues/stubs/issue_1900.zep new file mode 100644 index 0000000000..f3c6ee2477 --- /dev/null +++ b/unit-tests/fixtures/stubs/issues/stubs/issue_1900.zep @@ -0,0 +1,113 @@ +/** + * This file is part of the Zephir. + * + * (c) Zephir Team + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Stubs; + +use Psr\Http\Message\RequestInterface; +use Stubs\Events\ManagerInterface as AliasedManagerInterface; + +class Issue_1900 implements AliasedManagerInterface +{ + public eventsManager; + + /** @var array $collect - descr for collect var. */ + private collect; + + protected static uniqueId = 0; + + /** + * @return AliasedManagerInterface + */ + public function getEventsManager() -> + { + } + + public function test01() -> <\StdClass> + { + return new \StdClass(); + } + + /** + * Attach a listener to the events manager + * + * @param object|callable handler + */ + public function attach(string! eventType, var handler, int! priority = 1) -> void + { + var priorityQueue; + + // Insert the handler in the queue + priorityQueue->insert(handler, priority); + } + + /** + * Returns if priorities are enabled + */ + public function arePrioritiesEnabled() -> bool + { + return true; + } + + /** + * Tells the event manager if it needs to collect all the responses returned + * by every registered listener in a single fire + */ + public function collectResponses(bool collect, string! eventType, var handler) -> void + { + let this->collect = collect; + } + + /** + * Fires an event in the events manager causing the active listeners to be + * notified about it + * + *```php + * $eventsManager->fire("db", $connection); + *``` + * + * @param object source + * @param mixed data + * @return mixed + */ + public function fire(string! eventType, object source, var data = null, bool cancelable = true) + { + return 1; + } + + /** + * Internal handler to call a queue of events + * + * @return mixed + */ + final public function fireQueue( queue, event) -> + { + return event; + } + + /** + * Returns all the attached listeners of a certain type + */ + public function getListeners(string! type) -> array + { + return []; + } + + public function getResources() -> Resource + { + return []; + } + + /** + * Check whether certain type of event has listeners + */ + public function hasListeners(string! type) -> + { + return []; + } +} diff --git a/unit-tests/sharness/.gitignore b/unit-tests/sharness/.gitignore index 5e59048ac5..1fb80c33f9 100644 --- a/unit-tests/sharness/.gitignore +++ b/unit-tests/sharness/.gitignore @@ -1,3 +1,6 @@ lib/sharness/ test-results/ trash directory.*.sh/ + +# prove saved state +.prove diff --git a/unit-tests/sharness/t0005-stubs.sh b/unit-tests/sharness/t0005-stubs.sh index 1b671d5b8a..9c5b4d36a2 100755 --- a/unit-tests/sharness/t0005-stubs.sh +++ b/unit-tests/sharness/t0005-stubs.sh @@ -1,49 +1,49 @@ #!/usr/bin/env bash # shellcheck disable=SC2034 -test_description="Test generate IDE stubs" +test_description="Test generates IDE stubs" # shellcheck disable=SC1091 source ./setup.sh +# shellcheck disable=SC2164,SC2069 +# Generate Stubs for test +zephir_stubs() { + cd "$FIXTURESDIR"/stubs/issues + zephirc generate --no-ansi 2>&1 >/dev/null + zephirc stubs --no-ansi 2>&1 >/dev/null +} + +# This test generates Stubs once and all other tests uses this build artifacts +test_expect_success "Should generate Stubs" \ + "zephir_stubs && test -d ./ide/0.0.1/Stubs" + # See: https://github.com/phalcon/zephir/issues/1922 -test_expect_success "Should properly generate type hint" " - cd $FIXTURESDIR/stubs/issues && - zephirc generate --no-ansi 2>&1 >/dev/null && - zephirc stubs --no-ansi 2>&1 >/dev/null && - test_cmp expected/Issue_1922.zep.php ide/0.0.1/Stubs/Issue_1922.zep.php -" +test_expect_success "Should properly generate type hint" \ + "test_cmp expected/Issue_1922.zep.php ide/0.0.1/Stubs/Issue_1922.zep.php" # See: https://github.com/phalcon/zephir/issues/1778 -test_expect_success "Should properly namespace imports (use block)" " - cd $FIXTURESDIR/stubs/issues && - zephirc generate --no-ansi 2>&1 >/dev/null && - zephirc stubs --no-ansi 2>&1 >/dev/null && - test_cmp expected/Issue_1778.zep.php ide/0.0.1/Stubs/Issue_1778.zep.php -" +test_expect_success "Should properly namespace imports (use block)" \ + "test_cmp expected/Issue_1778.zep.php ide/0.0.1/Stubs/Issue_1778.zep.php" + +# See: https://github.com/phalcon/zephir/issues/1900 +test_expect_success "Should properly generate return types for stubs" \ + "test_cmp expected/Issue_1900.zep.php ide/0.0.1/Stubs/Issue_1900.zep.php" # See: https://github.com/phalcon/zephir/issues/1907 -test_expect_success "Should properly generate Namespace for extends" " - cd $FIXTURESDIR/stubs/issues && - zephirc generate --no-ansi 2>&1 >/dev/null && - zephirc stubs --no-ansi 2>&1 >/dev/null && - test_cmp expected/Exception.zep.php ide/0.0.1/Stubs/Exception.zep.php -" +test_expect_success "Should properly generate Namespace for extends" \ + "test_cmp expected/Exception.zep.php ide/0.0.1/Stubs/Exception.zep.php" # See: https://github.com/phalcon/zephir/issues/1907 -test_expect_success "Should properly generate Namespace for extends (slash)" " - cd $FIXTURESDIR/stubs/issues && - zephirc generate --no-ansi 2>&1 >/dev/null && - zephirc stubs --no-ansi 2>&1 >/dev/null && - test_cmp expected/Issue_1907.zep.php ide/0.0.1/Stubs/Issue_1907.zep.php -" +test_expect_success "Should properly generate Namespace for extends (slash)" \ + "test_cmp expected/Issue_1907.zep.php ide/0.0.1/Stubs/Issue_1907.zep.php" # See: https://github.com/phalcon/zephir/issues/1986 -test_expect_success "Should properly generate Aliases for use statements" " - cd $FIXTURESDIR/stubs/issues && - zephirc generate --no-ansi 2>&1 >/dev/null && - zephirc stubs --no-ansi 2>&1 >/dev/null && - test_cmp expected/Issue_1986.zep.php ide/0.0.1/Stubs/Issue_1986.zep.php -" +test_expect_success "Should properly generate Aliases for use statements" \ + "test_cmp expected/Issue_1986.zep.php ide/0.0.1/Stubs/Issue_1986.zep.php" + +# See: https://github.com/phalcon/zephir/issues/1896 +test_expect_success "Should generage CamelCase folders for stubs" \ + "test $(ls ./ide/0.0.1/Stubs/Events/ManagerInterface.zep.php | sed -e 's~\/~\\~g') = .\ide\0.0.1\Stubs\Events\ManagerInterface.zep.php" test_done