Skip to content

Commit

Permalink
Merge pull request #6233 from supersmile2009/fix-in-array-assert
Browse files Browse the repository at this point in the history
  • Loading branch information
weirdan authored Aug 12, 2021
2 parents c4def5d + a4b6fbc commit 7ff2a66
Show file tree
Hide file tree
Showing 6 changed files with 527 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3465,42 +3465,41 @@ private static function getInarrayAssertions(
|| $atomic_type instanceof Type\Atomic\TKeyedArray
|| $atomic_type instanceof Type\Atomic\TList
) {
$is_sealed = false;
if ($atomic_type instanceof Type\Atomic\TList) {
$value_type = $atomic_type->type_param;
} elseif ($atomic_type instanceof Type\Atomic\TKeyedArray) {
$value_type = $atomic_type->getGenericValueType();
$is_sealed = $atomic_type->sealed;
} else {
$value_type = $atomic_type->type_params[1];
}

$array_literal_types = \array_filter(
$value_type->getAtomicTypes(),
function ($type) {
return $type instanceof Type\Atomic\TLiteralInt
|| $type instanceof Type\Atomic\TLiteralString
|| $type instanceof Type\Atomic\TLiteralFloat
|| $type instanceof Type\Atomic\TEnumCase;
}
);

if ($array_literal_types
&& count($value_type->getAtomicTypes())
) {
$literal_assertions = [];
$assertions = [];

foreach ($array_literal_types as $array_literal_type) {
$literal_assertions[] = '=' . $array_literal_type->getAssertionString();
if (!$is_sealed) {
if ($value_type->getId() !== '') {
$assertions[] = 'in-array-' . $value_type->getId();
}

if ($value_type->isFalsable()) {
$literal_assertions[] = 'false';
}

if ($value_type->isNullable()) {
$literal_assertions[] = 'null';
} else {
foreach ($value_type->getAtomicTypes() as $atomic_value_type) {
if ($atomic_value_type instanceof Type\Atomic\TLiteralInt
|| $atomic_value_type instanceof Type\Atomic\TLiteralString
|| $atomic_value_type instanceof Type\Atomic\TLiteralFloat
|| $atomic_value_type instanceof Type\Atomic\TEnumCase
) {
$assertions[] = '=' . $atomic_value_type->getAssertionString();
} elseif ($atomic_value_type instanceof Type\Atomic\TFalse
|| $atomic_value_type instanceof Type\Atomic\TTrue
|| $atomic_value_type instanceof Type\Atomic\TNull
) {
$assertions[] = $atomic_value_type->getAssertionString();
}
}
}

$if_types[$first_var_name] = [$literal_assertions];
if ($assertions !== []) {
$if_types[$first_var_name] = [$assertions];
}
}
}
Expand Down
26 changes: 26 additions & 0 deletions src/Psalm/Internal/Type/NegatedAssertionReconciler.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,32 @@ public static function reconcile(
} elseif ($assertion === 'array-key-exists') {
return Type::getEmpty();
} elseif (substr($assertion, 0, 9) === 'in-array-') {
$assertion = substr($assertion, 9);
$new_var_type = Type::parseString($assertion);

$intersection = Type::intersectUnionTypes(
$new_var_type,
$existing_var_type,
$statements_analyzer->getCodebase()
);

if ($intersection === null) {
if ($key && $code_location) {
self::triggerIssueForImpossible(
$existing_var_type,
$existing_var_type->getId(),
$key,
'!' . $assertion,
true,
$negated,
$code_location,
$suppressed_issues
);
}

$failed_reconciliation = 2;
}

return $existing_var_type;
} elseif (substr($assertion, 0, 14) === 'has-array-key-') {
return $existing_var_type;
Expand Down
72 changes: 42 additions & 30 deletions src/Psalm/Internal/Type/SimpleAssertionReconciler.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use Psalm\CodeLocation;
use Psalm\Codebase;
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\Exception\TypeParseTreeException;
use Psalm\Issue\ParadoxicalCondition;
use Psalm\Issue\RedundantCondition;
use Psalm\IssueBuffer;
Expand Down Expand Up @@ -91,7 +91,12 @@ public static function reconcile(
return self::reconcileInArray(
$codebase,
$existing_var_type,
substr($assertion, 9)
substr($assertion, 9),
$key,
$negated,
$code_location,
$suppressed_issues,
$failed_reconciliation
);
}

Expand Down Expand Up @@ -1494,44 +1499,51 @@ private static function reconcileIterable(
return Type::getMixed();
}

/**
* @param string[] $suppressed_issues
* @param 0|1|2 $failed_reconciliation
*/
private static function reconcileInArray(
Codebase $codebase,
Union $existing_var_type,
string $assertion
string $assertion,
?string $key,
bool $negated,
?CodeLocation $code_location,
array $suppressed_issues,
int &$failed_reconciliation
) : Union {
if (strpos($assertion, '::')) {
[$fq_classlike_name, $const_name] = explode('::', $assertion);
try {
$new_var_type = Type::parseString($assertion);
} catch (TypeParseTreeException $e) {
// Not all assertions can be parsed as type, it's fine.
// One particular case is variable array key (e. g. $arr[$key]), which end up as in-array-$arr assertion

$class_constant_type = $codebase->classlikes->getClassConstantType(
$fq_classlike_name,
$const_name,
\ReflectionProperty::IS_PRIVATE
);
return $existing_var_type;
}

if ($class_constant_type) {
foreach ($class_constant_type->getAtomicTypes() as $const_type_atomic) {
if ($const_type_atomic instanceof Type\Atomic\TKeyedArray
|| $const_type_atomic instanceof Type\Atomic\TArray
) {
if ($const_type_atomic instanceof Type\Atomic\TKeyedArray) {
$const_type_atomic = $const_type_atomic->getGenericArrayType();
}
$intersection = Type::intersectUnionTypes($new_var_type, $existing_var_type, $codebase);

if (UnionTypeComparator::isContainedBy(
$codebase,
$const_type_atomic->type_params[0],
$existing_var_type
)) {
return clone $const_type_atomic->type_params[0];
}
}
}
if ($intersection === null) {
if ($key && $code_location) {
self::triggerIssueForImpossible(
$existing_var_type,
$existing_var_type->getId(),
$key,
'!' . $assertion,
true,
$negated,
$code_location,
$suppressed_issues
);
}
}

$existing_var_type->removeType('null');
$failed_reconciliation = 2;

return $existing_var_type;
return Type::getMixed();
}

return $intersection;
}

private static function reconcileHasArrayKey(
Expand Down
Loading

0 comments on commit 7ff2a66

Please sign in to comment.