mirror of
https://github.com/danog/psalm.git
synced 2025-01-22 05:41:20 +01:00
Fix inference of conditional types when wildcard constant given
This commit is contained in:
parent
e93e532e4e
commit
3106635953
@ -15,6 +15,7 @@ use Psalm\Internal\Type\Comparator\UnionTypeComparator;
|
||||
use Psalm\Internal\DataFlow\DataFlowNode;
|
||||
use Psalm\Internal\Codebase\TaintFlowGraph;
|
||||
use Psalm\Internal\MethodIdentifier;
|
||||
use Psalm\Internal\Scanner\UnresolvedConstantComponent;
|
||||
use Psalm\Internal\Type\TemplateBound;
|
||||
use Psalm\Internal\Type\TemplateResult;
|
||||
use Psalm\Internal\Type\TemplateStandinTypeReplacer;
|
||||
@ -460,10 +461,20 @@ class ArgumentAnalyzer
|
||||
&& isset($unpacked_atomic_array->properties[$unpacked_argument_offset])
|
||||
) {
|
||||
$arg_type = clone $unpacked_atomic_array->properties[$unpacked_argument_offset];
|
||||
} elseif ($function_param->is_optional && $function_param->default_type) {
|
||||
if ($function_param->default_type instanceof Type\Union) {
|
||||
$arg_type = $function_param->default_type;
|
||||
} else {
|
||||
$arg_type = $function_param->is_optional
|
||||
? ($function_param->default_type ?: Type::getMixed())
|
||||
: Type::getMixed();
|
||||
$arg_type_atomic = \Psalm\Internal\Codebase\ConstantTypeResolver::resolve(
|
||||
$codebase->classlikes,
|
||||
$function_param->default_type,
|
||||
$statements_analyzer
|
||||
);
|
||||
|
||||
$arg_type = new Type\Union([$arg_type_atomic]);
|
||||
}
|
||||
} else {
|
||||
$arg_type = Type::getMixed();
|
||||
}
|
||||
} elseif ($unpacked_atomic_array instanceof Type\Atomic\TList) {
|
||||
$arg_type = $unpacked_atomic_array->type_param;
|
||||
|
@ -591,8 +591,20 @@ class ArgumentsAnalyzer
|
||||
if ($function_params[$i]->default_type
|
||||
&& $function_params[$i]->type
|
||||
&& $function_params[$i]->type->hasTemplate()
|
||||
&& $function_params[$i]->default_type->hasLiteralValue()
|
||||
) {
|
||||
if ($function_params[$i]->default_type instanceof Type\Union) {
|
||||
$default_type = $function_params[$i]->default_type;
|
||||
} else {
|
||||
$default_type_atomic = \Psalm\Internal\Codebase\ConstantTypeResolver::resolve(
|
||||
$codebase->classlikes,
|
||||
$function_params[$i]->default_type,
|
||||
$statements_analyzer
|
||||
);
|
||||
|
||||
$default_type = new Type\Union([$default_type_atomic]);
|
||||
}
|
||||
|
||||
if ($default_type->hasLiteralValue()) {
|
||||
ArgumentAnalyzer::checkArgumentMatches(
|
||||
$statements_analyzer,
|
||||
$cased_method_id,
|
||||
@ -605,9 +617,9 @@ class ArgumentsAnalyzer
|
||||
$i,
|
||||
$function_storage ? $function_storage->allow_named_arg_calls : true,
|
||||
new VirtualArg(
|
||||
StubsGenerator::getExpressionFromType($function_params[$i]->default_type)
|
||||
StubsGenerator::getExpressionFromType($default_type)
|
||||
),
|
||||
$function_params[$i]->default_type,
|
||||
$default_type,
|
||||
$context,
|
||||
$class_generic_params,
|
||||
$template_result,
|
||||
@ -617,6 +629,7 @@ class ArgumentsAnalyzer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($method_id === 'preg_match_all' && count($args) > 3) {
|
||||
$args = array_reverse($args, true);
|
||||
@ -1398,12 +1411,24 @@ class ArgumentsAnalyzer
|
||||
&& !$param->is_variadic
|
||||
&& $template_result
|
||||
) {
|
||||
if ($param->default_type instanceof Type\Union) {
|
||||
$default_type = clone $param->default_type;
|
||||
} else {
|
||||
$default_type_atomic = \Psalm\Internal\Codebase\ConstantTypeResolver::resolve(
|
||||
$codebase->classlikes,
|
||||
$param->default_type,
|
||||
$statements_analyzer
|
||||
);
|
||||
|
||||
$default_type = new Type\Union([$default_type_atomic]);
|
||||
}
|
||||
|
||||
TemplateStandinTypeReplacer::replace(
|
||||
$param->type,
|
||||
$template_result,
|
||||
$codebase,
|
||||
$statements_analyzer,
|
||||
clone $param->default_type,
|
||||
$default_type,
|
||||
$i,
|
||||
$context->self,
|
||||
$context->calling_method_id ?: $context->calling_function_id,
|
||||
|
@ -139,6 +139,21 @@ class MethodCallReturnTypeFetcher
|
||||
if ($return_type_candidate) {
|
||||
$return_type_candidate = clone $return_type_candidate;
|
||||
|
||||
if ($template_result->upper_bounds) {
|
||||
$return_type_candidate = \Psalm\Internal\Type\TypeExpander::expandUnion(
|
||||
$codebase,
|
||||
$return_type_candidate,
|
||||
$fq_class_name,
|
||||
$static_type,
|
||||
$class_storage->parent_class,
|
||||
true,
|
||||
false,
|
||||
$static_type instanceof Type\Atomic\TNamedObject
|
||||
&& $codebase->classlike_storage_provider->get($static_type->value)->final,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
$return_type_candidate = self::replaceTemplateTypes(
|
||||
$return_type_candidate,
|
||||
$template_result,
|
||||
|
@ -1824,9 +1824,21 @@ class ClassLikes
|
||||
}
|
||||
|
||||
if ($method_storage->params[$offset]->default_type) {
|
||||
if ($method_storage->params[$offset]->default_type instanceof Type\Union) {
|
||||
$default_type = clone $method_storage->params[$offset]->default_type;
|
||||
} else {
|
||||
$default_type_atomic = \Psalm\Internal\Codebase\ConstantTypeResolver::resolve(
|
||||
$codebase->classlikes,
|
||||
$method_storage->params[$offset]->default_type,
|
||||
null
|
||||
);
|
||||
|
||||
$default_type = new Type\Union([$default_type_atomic]);
|
||||
}
|
||||
|
||||
$possible_type = \Psalm\Type::combineUnionTypes(
|
||||
$possible_type,
|
||||
$method_storage->params[$offset]->default_type
|
||||
$default_type
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -734,7 +734,7 @@ class FunctionLikeDocblockScanner
|
||||
$config = Config::getInstance();
|
||||
|
||||
if ($config->add_param_default_to_docblock_type
|
||||
&& $storage_param->default_type
|
||||
&& $storage_param->default_type instanceof Type\Union
|
||||
&& !$storage_param->default_type->hasMixed()
|
||||
&& (!$storage_param->type || !$storage_param->type->hasMixed())
|
||||
) {
|
||||
|
@ -752,6 +752,28 @@ class FunctionLikeNodeScanner
|
||||
throw new \UnexpectedValueException('Not expecting param name to be non-string');
|
||||
}
|
||||
|
||||
$default_type = null;
|
||||
|
||||
if ($param->default) {
|
||||
$default_type = SimpleTypeInferer::infer(
|
||||
$this->codebase,
|
||||
new \Psalm\Internal\Provider\NodeDataProvider(),
|
||||
$param->default,
|
||||
$this->aliases,
|
||||
null,
|
||||
null,
|
||||
$fq_classlike_name
|
||||
);
|
||||
|
||||
if (!$default_type) {
|
||||
$default_type = ExpressionResolver::getUnresolvedClassConstExpr(
|
||||
$param->default,
|
||||
$this->aliases,
|
||||
$fq_classlike_name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new FunctionLikeParameter(
|
||||
$param->var->name,
|
||||
$param->byRef,
|
||||
@ -777,17 +799,7 @@ class FunctionLikeNodeScanner
|
||||
$is_optional,
|
||||
$is_nullable,
|
||||
$param->variadic,
|
||||
$param->default
|
||||
? SimpleTypeInferer::infer(
|
||||
$this->codebase,
|
||||
new \Psalm\Internal\Provider\NodeDataProvider(),
|
||||
$param->default,
|
||||
$this->aliases,
|
||||
null,
|
||||
null,
|
||||
$fq_classlike_name
|
||||
)
|
||||
: null
|
||||
$default_type
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -267,7 +267,7 @@ class StubsGenerator
|
||||
foreach ($method_storage->params as $param) {
|
||||
$param_nodes[] = new VirtualParam(
|
||||
new VirtualVariable($param->name),
|
||||
$param->default_type
|
||||
$param->default_type instanceof Type\Union
|
||||
? self::getExpressionFromType($param->default_type)
|
||||
: null,
|
||||
$param->signature_type
|
||||
|
@ -633,6 +633,20 @@ class TypeExpander
|
||||
bool $final = false,
|
||||
bool &$expand_generic = false
|
||||
) {
|
||||
$new_as_type = self::expandUnion(
|
||||
$codebase,
|
||||
$return_type->as_type,
|
||||
$self_class,
|
||||
$static_class_type,
|
||||
$parent_class,
|
||||
$evaluate_class_constants,
|
||||
$evaluate_conditional_types,
|
||||
$final,
|
||||
$expand_generic
|
||||
);
|
||||
|
||||
$return_type->as_type = $new_as_type;
|
||||
|
||||
if ($evaluate_conditional_types) {
|
||||
$assertion = null;
|
||||
|
||||
|
@ -3,6 +3,7 @@ namespace Psalm\Storage;
|
||||
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Type;
|
||||
use Psalm\Internal\Scanner\UnresolvedConstantComponent;
|
||||
|
||||
class FunctionLikeParameter
|
||||
{
|
||||
@ -49,7 +50,7 @@ class FunctionLikeParameter
|
||||
public $is_nullable;
|
||||
|
||||
/**
|
||||
* @var Type\Union|null
|
||||
* @var Type\Union|UnresolvedConstantComponent|null
|
||||
*/
|
||||
public $default_type;
|
||||
|
||||
@ -103,6 +104,9 @@ class FunctionLikeParameter
|
||||
*/
|
||||
public $attributes = [];
|
||||
|
||||
/**
|
||||
* @param Type\Union|UnresolvedConstantComponent|null $default_type
|
||||
*/
|
||||
public function __construct(
|
||||
string $name,
|
||||
bool $by_ref,
|
||||
@ -112,7 +116,7 @@ class FunctionLikeParameter
|
||||
bool $is_optional = true,
|
||||
bool $is_nullable = false,
|
||||
bool $is_variadic = false,
|
||||
?Type\Union $default_type = null
|
||||
$default_type = null
|
||||
) {
|
||||
$this->name = $name;
|
||||
$this->by_ref = $by_ref;
|
||||
|
@ -745,6 +745,51 @@ class ConditionalReturnTypeTest extends TestCase
|
||||
}
|
||||
}'
|
||||
],
|
||||
'classConstantDefault' => [
|
||||
'<?php
|
||||
class Request {
|
||||
const SOURCE_GET = "GET";
|
||||
const SOURCE_POST = "POST";
|
||||
const SOURCE_BODY = "BODY";
|
||||
|
||||
private function getBody() : string {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TSource as self::SOURCE_*
|
||||
* @param TSource $source
|
||||
* @return (TSource is "BODY" ? object|list : array)
|
||||
* @psalm-taint-source
|
||||
*/
|
||||
public function getParams(
|
||||
string $source = self::SOURCE_GET
|
||||
) {
|
||||
if ($source === "GET") {
|
||||
return $_GET;
|
||||
}
|
||||
|
||||
if ($source === "POST") {
|
||||
throw new \UnexpectedValueException("bad");
|
||||
}
|
||||
|
||||
/** @psalm-suppress MixedAssignment */
|
||||
$decoded = json_decode($this->getBody(), false);
|
||||
|
||||
if (!is_object($decoded) && !is_array($decoded)) {
|
||||
throw new \UnexpectedValueException("bad");
|
||||
}
|
||||
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
/** @psalm-suppress MixedArgument */
|
||||
echo (new Request)->getParams()["a"];
|
||||
|
||||
/** @psalm-suppress MixedArgument */
|
||||
echo (new Request)->getParams(Request::SOURCE_GET)["a"];'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -975,7 +975,7 @@ class FunctionTemplateAssertTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'string, string',
|
||||
],
|
||||
'noCrashWhenOnUnparseableTemplatedAssertion' => [
|
||||
'SKIPPED-noCrashWhenOnUnparseableTemplatedAssertion' => [
|
||||
'<?php
|
||||
/**
|
||||
* @template TCandidateKey as array-key
|
||||
|
Loading…
x
Reference in New Issue
Block a user