Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix | 1990 return type annotation #2004

Merged
merged 22 commits into from
Nov 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
15 changes: 12 additions & 3 deletions Library/Stubs/MethodDocBlock.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use Zephir\AliasManager;
use Zephir\ClassMethod;
use Zephir\Types;

/**
* Stubs Generator.
Expand Down Expand Up @@ -40,14 +41,18 @@ 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);

$this->deprecated = $method->isDeprecated();
$this->aliasManager = $aliasManager;
$this->shortcutName = $method->isShortcut() ? $method->getShortcutName() : '';
$this->classMethod = $method;
$this->types = $types ?? new Types();
}

/**
Expand Down Expand Up @@ -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, ''];
}
}

Expand Down
286 changes: 285 additions & 1 deletion Library/Types.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Loading