Skip to content

Commit

Permalink
Fix #4309 - improve reuse of callmap callable inference
Browse files Browse the repository at this point in the history
  • Loading branch information
muglug committed Oct 12, 2020
1 parent 58426d3 commit c23ef78
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 235 deletions.
1 change: 1 addition & 0 deletions src/Psalm/Internal/Analyzer/FileAnalyzer.php
Expand Up @@ -504,6 +504,7 @@ public static function clearCache()
\Psalm\Internal\Provider\ClassLikeStorageProvider::deleteAll();
\Psalm\Internal\Provider\FileStorageProvider::deleteAll();
\Psalm\Internal\Provider\FileReferenceProvider::clearCache();
\Psalm\Internal\Codebase\InternalCallMapHandler::clearCache();
}

public function getFileName(): string
Expand Down
233 changes: 0 additions & 233 deletions src/Psalm/Internal/Analyzer/FunctionAnalyzer.php
Expand Up @@ -43,239 +43,6 @@ public function __construct(PhpParser\Node\Stmt\Function_ $function, SourceAnaly
parent::__construct($function, $source, $storage);
}

/**
* @param array<PhpParser\Node\Arg> $call_args
*/
public static function getReturnTypeFromCallMapWithArgs(
StatementsAnalyzer $statements_analyzer,
string $function_id,
array $call_args,
Context $context
): Type\Union {
$call_map_key = strtolower($function_id);

$call_map = InternalCallMapHandler::getCallMap();

$codebase = $statements_analyzer->getCodebase();

if (!isset($call_map[$call_map_key])) {
throw new \InvalidArgumentException('Function ' . $function_id . ' was not found in callmap');
}

if (!$call_args) {
switch ($call_map_key) {
case 'hrtime':
return new Type\Union([
new Type\Atomic\ObjectLike([
Type::getInt(),
Type::getInt()
])
]);

case 'get_called_class':
return new Type\Union([
new Type\Atomic\TClassString(
$context->self ?: 'object',
$context->self ? new Type\Atomic\TNamedObject($context->self, true) : null
)
]);

case 'get_parent_class':
if ($context->self && $codebase->classExists($context->self)) {
$classlike_storage = $codebase->classlike_storage_provider->get($context->self);

if ($classlike_storage->parent_classes) {
return new Type\Union([
new Type\Atomic\TClassString(
array_values($classlike_storage->parent_classes)[0]
)
]);
}
}
}
} else {
switch ($call_map_key) {
case 'count':
if (($first_arg_type = $statements_analyzer->node_data->getType($call_args[0]->value))) {
$atomic_types = $first_arg_type->getAtomicTypes();

if (count($atomic_types) === 1) {
if (isset($atomic_types['array'])) {
if ($atomic_types['array'] instanceof Type\Atomic\TCallableArray
|| $atomic_types['array'] instanceof Type\Atomic\TCallableList
|| $atomic_types['array'] instanceof Type\Atomic\TCallableObjectLikeArray
) {
return Type::getInt(false, 2);
}

if ($atomic_types['array'] instanceof Type\Atomic\TNonEmptyArray) {
return new Type\Union([
$atomic_types['array']->count !== null
? new Type\Atomic\TLiteralInt($atomic_types['array']->count)
: new Type\Atomic\TInt
]);
}

if ($atomic_types['array'] instanceof Type\Atomic\TNonEmptyList) {
return new Type\Union([
$atomic_types['array']->count !== null
? new Type\Atomic\TLiteralInt($atomic_types['array']->count)
: new Type\Atomic\TInt
]);
}

if ($atomic_types['array'] instanceof Type\Atomic\ObjectLike
&& $atomic_types['array']->sealed
) {
return new Type\Union([
new Type\Atomic\TLiteralInt(count($atomic_types['array']->properties))
]);
}
}
}
}

break;

case 'hrtime':
if (($first_arg_type = $statements_analyzer->node_data->getType($call_args[0]->value))) {
if ((string) $first_arg_type === 'true') {
$int = Type::getInt();
$int->from_calculation = true;
return $int;
}

if ((string) $first_arg_type === 'false') {
return new Type\Union([
new Type\Atomic\ObjectLike([
Type::getInt(),
Type::getInt()
])
]);
}

return new Type\Union([
new Type\Atomic\ObjectLike([
Type::getInt(),
Type::getInt()
]),
new Type\Atomic\TInt()
]);
}

$int = Type::getInt();
$int->from_calculation = true;
return $int;

case 'min':
case 'max':
if (isset($call_args[0])) {
$first_arg = $call_args[0]->value;

if ($first_arg_type = $statements_analyzer->node_data->getType($first_arg)) {
if ($first_arg_type->hasArray()) {
/** @psalm-suppress PossiblyUndefinedStringArrayOffset */
$array_type = $first_arg_type->getAtomicTypes()['array'];
if ($array_type instanceof Type\Atomic\ObjectLike) {
return $array_type->getGenericValueType();
}

if ($array_type instanceof Type\Atomic\TArray) {
return clone $array_type->type_params[1];
}

if ($array_type instanceof Type\Atomic\TList) {
return clone $array_type->type_param;
}
} elseif ($first_arg_type->hasScalarType()
&& ($second_arg = ($call_args[1]->value ?? null))
&& ($second_arg_type = $statements_analyzer->node_data->getType($second_arg))
&& $second_arg_type->hasScalarType()
) {
return Type::combineUnionTypes($first_arg_type, $second_arg_type);
}
}
}

break;

case 'get_parent_class':
// this is unreliable, as it's hard to know exactly what's wanted - attempted this in
// https://github.com/vimeo/psalm/commit/355ed831e1c69c96bbf9bf2654ef64786cbe9fd7
// but caused problems where it didn’t know exactly what level of child we
// were receiving.
//
// Really this should only work on instances we've created with new Foo(),
// but that requires more work
break;

case 'fgetcsv':
$string_type = Type::getString();
$string_type->addType(new Type\Atomic\TNull);
$string_type->ignore_nullable_issues = true;

$call_map_return_type = new Type\Union([
new Type\Atomic\TNonEmptyList(
$string_type
),
new Type\Atomic\TFalse,
new Type\Atomic\TNull
]);

if ($codebase->config->ignore_internal_nullable_issues) {
$call_map_return_type->ignore_nullable_issues = true;
}

if ($codebase->config->ignore_internal_falsable_issues) {
$call_map_return_type->ignore_falsable_issues = true;
}

return $call_map_return_type;
}
}

if (!$call_map[$call_map_key][0]) {
return Type::getMixed();
}

$call_map_return_type = Type::parseString($call_map[$call_map_key][0]);

switch ($call_map_key) {
case 'mb_strpos':
case 'mb_strrpos':
case 'mb_stripos':
case 'mb_strripos':
case 'strpos':
case 'strrpos':
case 'stripos':
case 'strripos':
case 'strstr':
case 'stristr':
case 'strrchr':
case 'strpbrk':
case 'array_search':
break;

default:
if ($call_map_return_type->isFalsable()
&& $codebase->config->ignore_internal_falsable_issues
) {
$call_map_return_type->ignore_falsable_issues = true;
}
}

switch ($call_map_key) {
case 'array_replace':
case 'array_replace_recursive':
if ($codebase->config->ignore_internal_nullable_issues) {
$call_map_return_type->ignore_nullable_issues = true;
}
break;
}

return $call_map_return_type;
}

/**
* @return non-empty-lowercase-string
*/
Expand Down

0 comments on commit c23ef78

Please sign in to comment.