Skip to content

Commit

Permalink
Fix #2932 - don’t crash when trying to expand callable object-like array
Browse files Browse the repository at this point in the history
  • Loading branch information
muglug committed Mar 9, 2020
1 parent 59dce5d commit 3c41648
Show file tree
Hide file tree
Showing 9 changed files with 49 additions and 14 deletions.
19 changes: 13 additions & 6 deletions src/Psalm/Internal/Analyzer/FunctionAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,29 +119,36 @@ public static function getReturnTypeFromCallMapWithArgs(

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
]);
} elseif ($atomic_types['array'] instanceof Type\Atomic\TNonEmptyList) {
}

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
]);
} elseif ($atomic_types['array'] instanceof Type\Atomic\ObjectLike
}

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))
]);
}
} elseif (isset($atomic_types['callable-array'])
|| isset($atomic_types['callable-list'])
) {
return Type::getInt(false, 2);
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/Psalm/Internal/Type/AssertionReconciler.php
Original file line number Diff line number Diff line change
Expand Up @@ -1778,7 +1778,7 @@ private static function reconcileArray(
}

if ($array_types) {
return new Type\Union($array_types);
return \Psalm\Internal\Type\TypeCombination::combineTypes($array_types);
}

$failed_reconciliation = 2;
Expand Down Expand Up @@ -1866,7 +1866,7 @@ private static function reconcileList(
}

if ($array_types) {
return new Type\Union($array_types);
return \Psalm\Internal\Type\TypeCombination::combineTypes($array_types);
}

$failed_reconciliation = 2;
Expand Down Expand Up @@ -1987,7 +1987,7 @@ private static function reconcileIntArrayAccess(
}

if ($array_types) {
return new Type\Union($array_types);
return \Psalm\Internal\Type\TypeCombination::combineTypes($array_types);
}

$failed_reconciliation = 2;
Expand Down Expand Up @@ -2078,7 +2078,7 @@ private static function reconcileCallable(
}

if ($callable_types) {
return new Type\Union($callable_types);
return \Psalm\Internal\Type\TypeCombination::combineTypes($callable_types);
}

$failed_reconciliation = 2;
Expand Down
6 changes: 5 additions & 1 deletion src/Psalm/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ function (ParseTree $child_tree) use ($template_type_map) {
$properties[$property_key] = $property_type;
}

if ($type !== 'array' && $type !== 'object') {
if ($type !== 'array' && $type !== 'object' && $type !== 'callable-array') {
throw new TypeParseTreeException('Unexpected brace character');
}

Expand All @@ -578,6 +578,10 @@ function (ParseTree $child_tree) use ($template_type_map) {
return new TObjectWithProperties($properties);
}

if ($type === 'callable-array') {
return new Atomic\TCallableObjectLikeArray($properties);
}

return new ObjectLike($properties);
}

Expand Down
5 changes: 5 additions & 0 deletions src/Psalm/Type/Atomic/TCallableArray.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,9 @@ class TCallableArray extends TNonEmptyArray
* @var string
*/
public $value = 'callable-array';

public function getKey(bool $show_extra = true)
{
return 'array';
}
}
5 changes: 5 additions & 0 deletions src/Psalm/Type/Atomic/TCallableObjectLikeArray.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,9 @@
class TCallableObjectLikeArray extends ObjectLike
{
const KEY = 'callable-array';

public function getKey(bool $include_extra = true)
{
return 'array';
}
}
2 changes: 1 addition & 1 deletion src/Psalm/Type/Union.php
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,7 @@ public function hasType($type_string)
*/
public function hasArray()
{
return isset($this->types['array']) || isset($this->types['callable-array']);
return isset($this->types['array']);
}

/**
Expand Down
9 changes: 8 additions & 1 deletion tests/FunctionCallTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1702,7 +1702,14 @@ function returnsStr(): string {
return $str;
}',
'error_message' => 'NullableReturnStatement'
]
],
'noCrashWithPattern' => [
'<?php
echo !\is_callable($loop_callback)
|| (\is_array($loop_callback)
&& !\method_exists(...$loop_callback));',
'error_message' => 'UndefinedGlobalVariable'
],
];
}
}
7 changes: 7 additions & 0 deletions tests/TypeCombinationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,13 @@ public function providerTestValidTypeCombination()
'callable'
],
],
'combineCallableArrayAndArray' => [
'array<array-key, mixed>',
[
'callable-array{class-string, string}',
'array',
],
],
];
}

Expand Down
2 changes: 1 addition & 1 deletion tests/TypeReconciliation/ReconcilerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public function providerTestReconcilation()
'iterableToArray' => ['array<int, int>', 'array', 'iterable<int, int>'],
'iterableToTraversable' => ['Traversable<int, int>', 'Traversable', 'iterable<int, int>'],
'callableToCallableArray' => ['callable-array{0: class-string|object, 1: string}', 'array', 'callable'],
'callableOrArrayToCallableArray' => ['array<array-key, mixed>|callable-array{0: class-string|object, 1: string}', 'array', 'callable|array'],
'callableOrArrayToCallableArray' => ['array<array-key, mixed>', 'array', 'callable|array'],
'traversableToIntersection' => ['Countable&Traversable', 'Traversable', 'Countable'],
'iterableWithoutParamsToTraversableWithoutParams' => ['Traversable', '!array', 'iterable'],
'iterableWithParamsToTraversableWithParams' => ['Traversable<int, string>', '!array', 'iterable<int, string>'],
Expand Down

0 comments on commit 3c41648

Please sign in to comment.