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

Fix assertions with empty lists #11312

Merged
merged 20 commits into from
Mar 3, 2025
24 changes: 24 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,30 @@

- [BC] Method finish() of class Psalm\Progress\Progress changed from concrete to abstract

- [BC] The return type of Psalm\Type::getListAtomic() changed from Psalm\Type\Atomic\TKeyedArray to the non-covariant Psalm\Type\Atomic\TKeyedArray|Psalm\Type\Atomic\TArray

- [BC] The return type of Psalm\Type::getListAtomic() changed from Psalm\Type\Atomic\TKeyedArray to Psalm\Type\Atomic\TKeyedArray|Psalm\Type\Atomic\TArray

- [BC] The return type of Psalm\Type::getNonEmptyListAtomic() changed from Psalm\Type\Atomic\TKeyedArray to the non-covariant Psalm\Type\Atomic\TKeyedArray|Psalm\Type\Atomic\TArray

- [BC] The return type of Psalm\Type::getNonEmptyListAtomic() changed from Psalm\Type\Atomic\TKeyedArray to Psalm\Type\Atomic\TKeyedArray|Psalm\Type\Atomic\TArray

- [BC] Class Psalm\Type\Atomic\TKeyedArray became final

- [BC] Class Psalm\Type\Atomic\TKeyedArray can only be created using the new `make` or `makeCallable` factory methods, the constructor was rendered private.

- [BC] Class Psalm\Type\Atomic\TCallableKeyedArray has been deleted, and replaced with a new `is_callable` flag in Psalm\Type\Atomic\TKeyedArray

- [BC] Class Psalm\Type\Atomic\TCallableInterface has been deleted, use `\Psalm\Type\Atomic::isCallableType()` instead

## Removed

- [BC] Constant Psalm\Type\Atomic\TKeyedArray::NAME_ARRAY was removed

- [BC] Constant Psalm\Type\Atomic\TKeyedArray::NAME_LIST was removed

- [BC] Psalm\Type\Atomic\TKeyedArray#__construct() was made private

# Upgrading from Psalm 5 to Psalm 6
## Changed

Expand Down
2 changes: 1 addition & 1 deletion docs/running_psalm/plugins/plugins_type_system.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ More complex types can be constructed as follows. The following represents an as

``` php
new Union([
new TKeyedArray([
TKeyedArray::make([
'key_1' => new Union([new TString()]),
'key_2' => new Union([new TInt()]),
'key_3' => new Union([new TBool()])])]);
Expand Down
27 changes: 26 additions & 1 deletion psalm-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="6.x-dev@049d3523d462c4e2039608d41487347b9394c70c">
<files psalm-version="6.x-dev@4258dc813b28c2b03865f34a17ddf072f006b357">
<file src="examples/TemplateChecker.php">
<PossiblyUndefinedIntArrayOffset>
<code><![CDATA[$comment_block->tags['variablesfrom'][0]]]></code>
Expand Down Expand Up @@ -2379,6 +2379,14 @@
<code><![CDATA[$properties[$property]]]></code>
<code><![CDATA[$property_type]]></code>
</ImplicitToStringCast>
<InvalidArgument>
<code><![CDATA[$class_strings]]></code>
<code><![CDATA[$class_strings]]></code>
<code><![CDATA[$extra_params ?? ($sealed
? null
: [$is_list ? Type::getListKey() : Type::getArrayKey(), Type::getMixed()]
)]]></code>
</InvalidArgument>
<InvalidOperand>
<code><![CDATA[$property_key - 1]]></code>
</InvalidOperand>
Expand Down Expand Up @@ -2689,11 +2697,28 @@
<ImpurePropertyAssignment>
<code><![CDATA[$key_type->possibly_undefined]]></code>
</ImpurePropertyAssignment>
<InvalidReturnStatement>
<code><![CDATA[new TArray([
$never, $never,
], $this->from_docblock)]]></code>
<code><![CDATA[new TArray([
$replaced_list_type, $replaced_list_type,
], $this->from_docblock)]]></code>
<code><![CDATA[new TArray([
$never, $never,
], $this->from_docblock)]]></code>
</InvalidReturnStatement>
<InvalidReturnType>
<code><![CDATA[self|TArray]]></code>
<code><![CDATA[self|TArray]]></code>
</InvalidReturnType>
<PossiblyInvalidOperand>
<code><![CDATA[$name]]></code>
<code><![CDATA[$name]]></code>
</PossiblyInvalidOperand>
<PossiblyUndefinedIntArrayOffset>
<code><![CDATA[$fallback_params[1]]]></code>
<code><![CDATA[$fallback_params[1]]]></code>
<code><![CDATA[$this->properties[0]]]></code>
<code><![CDATA[$this->properties[0]]]></code>
</PossiblyUndefinedIntArrayOffset>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public static function analyze(

// if this array looks like an object-like array, let's return that instead
if (count($array_creation_info->property_types) !== 0) {
$atomic_type = new TKeyedArray(
$atomic_type = TKeyedArray::make(
$array_creation_info->property_types,
$array_creation_info->class_strings,
$array_creation_info->can_create_objectlike
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ private static function updateTypeWithKeyValues(
$classStrings[$key_value->value] = true;
}
}
$object_like = new TKeyedArray(
$object_like = TKeyedArray::make(
$properties,
$classStrings ?: null,
);
Expand Down Expand Up @@ -607,7 +607,7 @@ private static function updateArrayAssignmentChildType(
);
} elseif ($prop_count !== null) {
assert($array_atomic_type_list !== null);
$array_atomic_type = new TKeyedArray(
$array_atomic_type = TKeyedArray::make(
array_fill(
0,
$prop_count,
Expand Down Expand Up @@ -637,7 +637,7 @@ private static function updateArrayAssignmentChildType(
$array_atomic_type_list,
);
assert(count($array_atomic_type) > 0);
$array_atomic_type = new TKeyedArray(
$array_atomic_type = TKeyedArray::make(
$array_atomic_type,
null,
null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ private static function analyzeOperands(
$fallback_params = $left_type_part->fallback_params ?: $right_type_part->fallback_params;
}

$new_keyed_array = new TKeyedArray(
$new_keyed_array = TKeyedArray::make(
$properties,
null,
$fallback_params,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -998,7 +998,7 @@ public static function verifyType(

$param_types_without_callable = array_filter(
$param_type->getAtomicTypes(),
static fn(Atomic $atomic) => !$atomic instanceof Atomic\TCallableInterface,
static fn(Atomic $atomic) => !$atomic->isCallableType(),
);
$param_type_without_callable = [] !== $param_types_without_callable
? new Union($param_types_without_callable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
use Psalm\Type;
use Psalm\Type\Atomic\TArray;
use Psalm\Type\Atomic\TCallable;
use Psalm\Type\Atomic\TCallableKeyedArray;
use Psalm\Type\Atomic\TClosure;
use Psalm\Type\Atomic\TKeyedArray;
use Psalm\Type\Atomic\TLiteralString;
Expand Down Expand Up @@ -1645,9 +1644,7 @@ private static function checkArgCount(

foreach ($arg_value_type->getAtomicTypes() as $atomic_arg_type) {
$packed_var_definite_args_tmp = [];
if ($atomic_arg_type instanceof TCallableKeyedArray) {
$packed_var_definite_args_tmp[] = 2;
} elseif ($atomic_arg_type instanceof TKeyedArray) {
if ($atomic_arg_type instanceof TKeyedArray) {
if ($atomic_arg_type->fallback_params !== null) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ public static function handleAddition(

$by_ref_type = new Union([$objectlike_list->setProperties($properties)]);
} elseif ($array_type instanceof TArray && $array_type->isEmptyArray()) {
$by_ref_type = new Union([new TKeyedArray([
$by_ref_type = new Union([TKeyedArray::make([
$arg_value_type,
], null, null, true)]);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
use Psalm\Type;
use Psalm\Type\Atomic\TArray;
use Psalm\Type\Atomic\TCallable;
use Psalm\Type\Atomic\TCallableKeyedArray;
use Psalm\Type\Atomic\TClassString;
use Psalm\Type\Atomic\TClosure;
use Psalm\Type\Atomic\TFalse;
Expand Down Expand Up @@ -328,7 +327,7 @@ private static function getReturnTypeFromCallMapWithArgs(
if (!$call_args) {
switch ($call_map_key) {
case 'hrtime':
$keyed_array = new TKeyedArray([
$keyed_array = TKeyedArray::make([
Type::getInt(),
Type::getInt(),
], null, null, true);
Expand Down Expand Up @@ -364,10 +363,6 @@ private static function getReturnTypeFromCallMapWithArgs(

if (count($atomic_types) === 1) {
if (isset($atomic_types['array'])) {
if ($atomic_types['array'] instanceof TCallableKeyedArray) {
return Type::getInt(false, 2);
}

if ($atomic_types['array'] instanceof TNonEmptyArray) {
return new Union([
$atomic_types['array']->count !== null
Expand All @@ -377,6 +372,9 @@ private static function getReturnTypeFromCallMapWithArgs(
}

if ($atomic_types['array'] instanceof TKeyedArray) {
if ($atomic_types['array']->is_callable) {
return Type::getInt(false, 2);
}
$min = $atomic_types['array']->getMinCount();
$max = $atomic_types['array']->getMaxCount();

Expand Down Expand Up @@ -407,7 +405,7 @@ private static function getReturnTypeFromCallMapWithArgs(
return Type::getInt(true);
}

$keyed_array = new TKeyedArray([
$keyed_array = TKeyedArray::make([
Type::getInt(),
Type::getInt(),
], null, null, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ private static function isSupported(FunctionLikeParameter $container_param): boo
}

if ($a instanceof Type\Atomic\TCallableString ||
$a instanceof Type\Atomic\TCallableKeyedArray
($a instanceof Type\Atomic\TKeyedArray && $a->is_callable)
) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ public static function analyze(

foreach ($stmt_expr_type->getAtomicTypes() as $type) {
if ($type instanceof Scalar) {
$keyed_array = new TKeyedArray([new Union([$type])], null, null, true);
$keyed_array = TKeyedArray::make([new Union([$type])], null, null, true);
$permissible_atomic_types[] = $keyed_array;
} elseif ($type instanceof TNull) {
$permissible_atomic_types[] = new TArray([Type::getNever(), Type::getNever()]);
Expand All @@ -258,7 +258,7 @@ public static function analyze(
} elseif ($type instanceof TObjectWithProperties) {
$array_type = $type->properties === []
? Type::getArrayAtomic()
: new TKeyedArray(
: TKeyedArray::make(
$type->properties,
null,
[Type::getArrayKey(), Type::getMixed()],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1169,7 +1169,7 @@ private static function handleArrayAccessOnArray(
$from_mixed_array = $type->type_params[1]->isMixed();

// ok, type becomes a TKeyedArray
$type = new TKeyedArray(
$type = TKeyedArray::make(
[
$single_atomic->value => $from_mixed_array ? Type::getMixed() : Type::getNever(),
],
Expand All @@ -1179,7 +1179,7 @@ private static function handleArrayAccessOnArray(
$from_empty_array ? null : $type->type_params,
);
} elseif (!$stmt->dim && $from_empty_array && $replacement_type) {
$type = new TKeyedArray(
$type = TKeyedArray::make(
[$replacement_type],
null,
null,
Expand Down Expand Up @@ -1730,7 +1730,7 @@ private static function handleArrayAccessOnKeyedArray(

if (!$stmt->dim) {
if ($type->is_list) {
$type = new TKeyedArray(
$type = TKeyedArray::make(
$type->properties,
null,
[$new_key_type, $generic_params],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,7 @@ private static function getGlobalTypeInner(string $var_id, bool $files_full_path
$arr['argc'] = $argc_helper;
}

$detailed_type = new TKeyedArray(
$detailed_type = TKeyedArray::make(
$arr,
null,
[Type::getNonEmptyString(), Type::getString()],
Expand All @@ -814,7 +814,7 @@ private static function getGlobalTypeInner(string $var_id, bool $files_full_path
$values['full_path'] = $str;
}

$type = new Union([new TKeyedArray($values)]);
$type = new Union([TKeyedArray::make($values)]);
$parent = new TArray([Type::getNonEmptyString(), $type]);

return new Union([$parent]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ private static function inferArrayType(
&& $array_creation_info->can_create_objectlike
&& $array_creation_info->property_types
) {
$objectlike = new TKeyedArray(
$objectlike = TKeyedArray::make(
$array_creation_info->property_types,
$array_creation_info->class_strings,
null,
Expand Down
4 changes: 2 additions & 2 deletions src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public static function analyze(
;
}
} else {
$root_types []= new TKeyedArray(
$root_types []= TKeyedArray::make(
$properties,
null,
$atomic_root_type->fallback_params ? [
Expand All @@ -135,7 +135,7 @@ public static function analyze(
foreach ($atomic_root_type->properties as $key => $type) {
$properties[$key] = $type->setPossiblyUndefined(true);
}
$root_types []= new TKeyedArray(
$root_types []= TKeyedArray::make(
$properties,
null,
$atomic_root_type->fallback_params,
Expand Down
6 changes: 3 additions & 3 deletions src/Psalm/Internal/Codebase/ConstantTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public static function resolve(
}

if ($left instanceof TKeyedArray && $right instanceof TKeyedArray) {
$type = new TKeyedArray(
$type = TKeyedArray::make(
$left->properties + $right->properties,
null,
);
Expand Down Expand Up @@ -268,7 +268,7 @@ public static function resolve(
if (empty($properties)) {
$resolved_type = Type::getEmptyArrayAtomic();
} else {
$resolved_type = new TKeyedArray($properties, null, null, $is_list);
$resolved_type = TKeyedArray::make($properties, null, null, $is_list);
}

return $resolved_type;
Expand Down Expand Up @@ -381,7 +381,7 @@ public static function getLiteralTypeFromScalarValue(array|string|int|float|bool
foreach ($value as $key => $val) {
$types[$key] = new Union([self::getLiteralTypeFromScalarValue($val)]);
}
return new TKeyedArray($types, null);
return TKeyedArray::make($types, null);
}

if (is_string($value)) {
Expand Down
2 changes: 1 addition & 1 deletion src/Psalm/Internal/Codebase/Methods.php
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ public function getMethodReturnType(
$types[] = new Union([new TEnumCase($original_fq_class_name, $case_name)]);
}

$list = new TKeyedArray($types, null, null, true);
$list = TKeyedArray::make($types, null, null, true);
return new Union([$list]);
}
}
Expand Down
12 changes: 5 additions & 7 deletions src/Psalm/Internal/Preloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,15 @@ public static function preload(?Progress $progress = null, bool $hasJit = false)
return;
}

if ($hasJit) {
$progress?->startPhase(Phase::JIT_COMPILATION);
$progress?->expand(count(PreloaderList::CLASSES)+1);
}
$progress?->startPhase($hasJit ? Phase::JIT_COMPILATION : Phase::PRELOADING);
$progress?->expand(count(PreloaderList::CLASSES)+1);

foreach (PreloaderList::CLASSES as $class) {
$progress?->taskDone(0);
class_exists($class);
}
if ($hasJit) {
$progress?->finish();
}

$progress?->finish();
self::$preloaded = true;
}
}
2 changes: 0 additions & 2 deletions src/Psalm/Internal/PreloaderList.php
Original file line number Diff line number Diff line change
Expand Up @@ -1687,8 +1687,6 @@ final class PreloaderList {
\Psalm\Type\Atomic\TArrayKey::class,
\Psalm\Type\Atomic\TBool::class,
\Psalm\Type\Atomic\TCallable::class,
\Psalm\Type\Atomic\TCallableInterface::class,
\Psalm\Type\Atomic\TCallableKeyedArray::class,
\Psalm\Type\Atomic\TCallableObject::class,
\Psalm\Type\Atomic\TCallableString::class,
\Psalm\Type\Atomic\TClassConstant::class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev
if (!$properties) {
return Type::getEmptyArray();
}
return new Union([new TKeyedArray(
return new Union([TKeyedArray::make(
$properties,
null,
$input_array->fallback_params,
Expand Down
Loading