Skip to content

Commit

Permalink
Fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
danog committed Mar 30, 2024
1 parent a1b24db commit 0be474e
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Union;

use function assert;
use function count;
use function explode;
use function implode;
Expand Down Expand Up @@ -508,6 +509,7 @@ private static function checkFunctionLikeTypeMatches(
} elseif ($unpacked_atomic_array instanceof TClassStringMap) {
$arg_value_type = Type::getMixed();
} else {
assert($unpacked_atomic_array instanceof TArray);
if (!$allow_named_args && !$unpacked_atomic_array->type_params[0]->isInt()) {
$arg_key_allowed = false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\TemplateStandinTypeReplacer;
use Psalm\Internal\Type\TypeCombiner;
use Psalm\Internal\Type\TypeExpander;
use Psalm\Issue\ArgumentTypeCoercion;
use Psalm\Issue\InvalidArgument;
Expand Down Expand Up @@ -337,6 +336,7 @@ public static function handleSplice(
) === false) {
return false;
}
$codebase = $statements_analyzer->getCodebase();

$array_types = [];
$max_array_size = null;
Expand Down Expand Up @@ -555,7 +555,7 @@ public static function handleSplice(
}
}

$by_ref_type = TypeCombiner::combine([$array_type, ...$replacement_arg_type->getArrayValueTypes()]);
$by_ref_type = Type::combineUnionTypeArray([new Union([$array_type]), ...$replacement_arg_type->getArrayValueTypes()], $codebase);

AssignmentAnalyzer::assignByRefParam(
$statements_analyzer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ public static function getFunctionParams(FunctionParamsProviderEvent $event): ?a
$key_type = Type::getArrayKey();
$inner_type = Type::getMixed();
} else {
$inner_type = Type::combineUnionTypeArray($first_arg_type->getArrayValueTypes(), $codebase);
$key_type = Type::combineUnionTypeArray($first_arg_type->getArrayKeyTypes(), $codebase);
$inner_type = $first_arg_type->getArrayValueType($codebase);
$key_type = $first_arg_type->getArrayKeyType($codebase);
}

$has_both = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev
new Union([
$preserve_keys
? new TNonEmptyArray([
Type::combineUnionTypeArray($array_arg_type->getArrayKeyTypes(), $codebase),
Type::combineUnionTypeArray($array_arg_type->getArrayValueTypes(), $codebase),
$array_arg_type->getArrayKeyType($codebase),
$array_arg_type->getArrayValueType($codebase),
])
: Type::getNonEmptyListAtomic(Type::combineUnionTypeArray($array_arg_type->getArrayValueTypes(), $codebase)),
: Type::getNonEmptyListAtomic($array_arg_type->getArrayValueType($codebase)),
]),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,42 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev
}
}

$r = [];
foreach ($statements_source->node_data->getType($call_args[0]->value)?->getAtomicTypes() ?? [] as $t) {
$r []= self::handleInner(
$statements_source,
$context,
$code_location,
$t,
$key_column_name,
$key_column_name_is_null,
$value_column_name,
$value_column_name_is_null,
$third_arg_type,
);
}
return new Union($r);
}

private static function handleInner(
StatementsAnalyzer $statements_source,
Context $context,
CodeLocation $code_location,
?Atomic $input_array,
string|int|null $key_column_name,
bool $key_column_name_is_null,
string|int|null $value_column_name,
bool $value_column_name_is_null,
?Union $third_arg_type,
): Atomic {
$row_type = $row_shape = null;
$input_array_not_empty = false;

// calculate row shape
if (($first_arg_type = $statements_source->node_data->getType($call_args[0]->value))
&& $first_arg_type->isSingle()
&& $first_arg_type->hasArray()
) {
$input_array = $first_arg_type->getArray();
if ($input_array) {
if ($input_array instanceof TKeyedArray && !$input_array->fallback_params
&& ($value_column_name !== null || $value_column_name_is_null)
&& !($third_arg_type && !$key_column_name)
&& !($third_arg_type && $key_column_name === null)
) {
$properties = [];
$ok = true;
Expand Down Expand Up @@ -167,14 +190,14 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev
}
if ($ok) {
if (!$properties) {
return Type::getEmptyArray();
return Type::getEmptyArrayAtomic();
}
return new Union([new TKeyedArray(
return new TKeyedArray(
$properties,
null,
$input_array->fallback_params,
$is_list,
)]);
);
}
}

Expand Down Expand Up @@ -228,7 +251,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev
: Type::getListAtomic($result_element_type ?? Type::getMixed());
}

return new Union([$type]);
return $type;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ static function (array $sub) use ($null) {

if (count($call_args) === 2) {
$generic_key_type = $array_arg_type
? Type::combineUnionTypeArray($array_arg_union_type->getArrayKeyTypes(), $codebase)
? $array_arg_union_type->getArrayKeyType($codebase)
: Type::getArrayKey();
} else {
$generic_key_type = Type::getInt();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Psalm\Internal\Analyzer\Statements\Expression\Fetch\VariableFetchAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Type\TypeCombiner;
use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent;
use Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface;
use Psalm\Type;
Expand Down Expand Up @@ -209,45 +210,46 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev
// @todo eventually this needs to be changed when we fully support filter_has_var
$global_type = VariableFetchAnalyzer::getGlobalType($global_name, $codebase->analysis_php_version_id);

$input_type = null;
if ($global_type->isArray() && $global_type->getArray() instanceof TKeyedArray) {
$array_instance = $global_type->getArray();
if ($second_arg_type->isSingleStringLiteral()) {
$key = $second_arg_type->getSingleStringLiteral()->value;
$res = [];
foreach ($global_type->getArrays() as $array_atomic) {
$input_type = null;
if ($array_atomic instanceof TKeyedArray) {
if ($second_arg_type->isSingleStringLiteral()) {
$key = $second_arg_type->getSingleStringLiteral()->value;

if (isset($array_instance->properties[ $key ])) {
$input_type = $array_instance->properties[ $key ];
if (isset($array_atomic->properties[ $key ])) {
$input_type = $array_atomic->properties[ $key ];
}
}
}

if ($input_type === null) {
$input_type = $array_instance->getGenericValueType();
if ($input_type === null) {
$input_type = $array_atomic->getGenericValueType();
$input_type = $input_type->setPossiblyUndefined(true);
}
} elseif ($array_atomic instanceof TArray) {
[$_, $input_type] = $array_atomic->type_params;
$input_type = $input_type->setPossiblyUndefined(true);
} else {
// this is impossible
throw new UnexpectedValueException('This should not happen');
}
} elseif ($global_type->isArray()
&& ($array_atomic = $global_type->getArray())
&& $array_atomic instanceof TArray) {
[$_, $input_type] = $array_atomic->type_params;
$input_type = $input_type->setPossiblyUndefined(true);
} else {
// this is impossible
throw new UnexpectedValueException('This should not happen');
}

return FilterUtils::getReturnType(
$filter_int_used,
$flags_int_used,
$input_type,
$fails_type,
$not_set_type,
$statements_analyzer,
$code_location,
$codebase,
$function_id,
$has_range,
$min_range,
$max_range,
$regexp,
);
$res []= FilterUtils::getReturnType(
$filter_int_used,
$flags_int_used,
$input_type,
$fails_type,
$not_set_type,
$statements_analyzer,
$code_location,
$codebase,
$function_id,
$has_range,
$min_range,
$max_range,
$regexp,
);
}
return TypeCombiner::combine($res, $codebase);
}
}
37 changes: 20 additions & 17 deletions src/Psalm/Internal/Provider/ReturnTypeProvider/FilterUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public static function getFilterArgValueOrError(
return $filter_int_used;
}

/** @return array{flags_int_used: int, options: TKeyedArray|null}|Union|null */
/** @return array{flags_int_used: int, options: array<Union>|null}|Union|null */
public static function getOptionsArgValueOrError(
Arg $options_arg,
StatementsAnalyzer $statements_analyzer,
Expand All @@ -170,9 +170,10 @@ public static function getOptionsArgValueOrError(
'options' => null,
);

$atomic_type = $options_arg_type->getArray();
if ($atomic_type instanceof TKeyedArray) {
$redundant_keys = array_diff(array_keys($atomic_type->properties), array('flags', 'options'));
$properties = Type::mergeKeyedArrayProperties($options_arg_type, $codebase);

if ($properties) {
$redundant_keys = array_diff(array_keys($properties), array('flags', 'options'));
if ($redundant_keys !== array()) {
// reported as it's usually an oversight/misunderstanding of how the function works
// it's silently ignored by the function though
Expand All @@ -186,10 +187,10 @@ public static function getOptionsArgValueOrError(
);
}

if (isset($atomic_type->properties['options'])) {
if (isset($properties['options'])) {
if ($filter_int_used === FILTER_CALLBACK) {
$only_callables = true;
foreach ($atomic_type->properties['options']->getAtomicTypes() as $option_atomic) {
foreach ($properties['options']->getAtomicTypes() as $option_atomic) {
if ($option_atomic->isCallableType()) {
continue;
}
Expand All @@ -205,7 +206,7 @@ public static function getOptionsArgValueOrError(

$only_callables = false;
}
if ($atomic_type->properties['options']->possibly_undefined) {
if ($properties['options']->possibly_undefined) {
$only_callables = false;
}

Expand All @@ -230,7 +231,7 @@ public static function getOptionsArgValueOrError(
);
}

if (! $atomic_type->properties['options']->isArray()) {
if (! $properties['options']->isArray()) {
// silently ignored by the function, but this usually indicates a bug
IssueBuffer::maybeAdd(
new InvalidArgument(
Expand All @@ -241,19 +242,18 @@ public static function getOptionsArgValueOrError(
),
$statements_analyzer->getSuppressedIssues(),
);
} elseif (($options_array = $atomic_type->properties['options']->getArray())
&& $options_array instanceof TKeyedArray) {
} elseif ($options_array = Type::mergeKeyedArrayProperties($properties['options'], $codebase)) {
$defaults['options'] = $options_array;
} else {
// cannot infer a 100% correct specific return type
$return_null = true;
}
}

if (isset($atomic_type->properties['flags'])) {
if ($atomic_type->properties['flags']->isSingleIntLiteral()) {
$defaults['flags_int_used'] = $atomic_type->properties['flags']->getSingleIntLiteral()->value;
} elseif ($atomic_type->properties['flags']->isInt()) {
if (isset($properties['flags'])) {
if ($properties['flags']->isSingleIntLiteral()) {
$defaults['flags_int_used'] = $properties['flags']->getSingleIntLiteral()->value;
} elseif ($properties['flags']->isInt()) {
// cannot infer a 100% correct specific return type
$return_null = true;
} else {
Expand Down Expand Up @@ -494,11 +494,14 @@ public static function checkRedundantFlags(
return null;
}

/** @return array{Union|null, float|int|null, float|int|null, bool, non-falsy-string|true|null} */
/**
* @param ?array<Union> $options
* @return array{Union|null, float|int|null, float|int|null, bool, non-falsy-string|true|null}
*/
public static function getOptions(
int $filter_int_used,
int $flags_int_used,
?TKeyedArray $options,
?array $options,
StatementsAnalyzer $statements_analyzer,
CodeLocation $code_location,
Codebase $codebase,
Expand All @@ -515,7 +518,7 @@ public static function getOptions(
}

$all_filters = self::getFilters($codebase);
foreach ($options->properties as $option => $option_value) {
foreach ($options as $option => $option_value) {
if (! isset($all_filters[ $filter_int_used ]['options'][ $option ])) {
IssueBuffer::maybeAdd(
new RedundantFlag(
Expand Down
20 changes: 20 additions & 0 deletions src/Psalm/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,26 @@

abstract class Type
{
/**
* @internal
* @return array<Union>
*/
public static function mergeKeyedArrayProperties(Union $union, Codebase $codebase): array
{
$properties = [];
foreach ($union->getArrays() as $atomic_type) {
if (!$atomic_type instanceof TKeyedArray) {
continue;
}
foreach ($atomic_type->properties as $key => $val) {
if (isset($properties[$key])) {
$val = self::combineUnionTypes($properties[$key], $val, $codebase);
}
$properties[$key] = $val;
}
}
return $properties;
}
/**
* Parses a string type representation
*
Expand Down

0 comments on commit 0be474e

Please sign in to comment.