Skip to content

Commit

Permalink
correctly handle null assertions (only worked for !null from SimpleNe…
Browse files Browse the repository at this point in the history
…gatedAssertionReconciler)
  • Loading branch information
kkmuffme committed Nov 23, 2023
1 parent 923f4bc commit 9a178ba
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 3 deletions.
7 changes: 4 additions & 3 deletions src/Psalm/Internal/Codebase/ConstantTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -395,10 +395,11 @@ public static function getLiteralTypeFromScalarValue($value): Atomic
return new TTrue();
}

if ($value === null) {
return new TNull();
/** @psalm-suppress RedundantConditionGivenDocblockType */

Check failure on line 398 in src/Psalm/Internal/Codebase/ConstantTypeResolver.php

View workflow job for this annotation

GitHub Actions / build

UnusedPsalmSuppress

src/Psalm/Internal/Codebase/ConstantTypeResolver.php:398:29: UnusedPsalmSuppress: This suppression is never used (see https://psalm.dev/207)
if ($value !== null) {

Check failure on line 399 in src/Psalm/Internal/Codebase/ConstantTypeResolver.php

View workflow job for this annotation

GitHub Actions / build

DocblockTypeContradiction

src/Psalm/Internal/Codebase/ConstantTypeResolver.php:399:13: DocblockTypeContradiction: Docblock-defined type null for $value is always null (see https://psalm.dev/155)
throw new InvalidArgumentException('$value must be a scalar.');
}

throw new InvalidArgumentException('$value must be a scalar.');
return new TNull();
}
}
127 changes: 127 additions & 0 deletions src/Psalm/Internal/Type/SimpleAssertionReconciler.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
use Psalm\Type\Atomic\TNonEmptyString;
use Psalm\Type\Atomic\TNonFalsyString;
use Psalm\Type\Atomic\TNonspecificLiteralString;
use Psalm\Type\Atomic\TNull;
use Psalm\Type\Atomic\TNumeric;
use Psalm\Type\Atomic\TNumericString;
use Psalm\Type\Atomic\TObject;
Expand Down Expand Up @@ -515,6 +516,19 @@ public static function reconcile(
);
}

if ($assertion_type instanceof TNull) {
return self::reconcileNull(
$assertion,
$existing_var_type,
$key,
$negated,
$code_location,
$suppressed_issues,
$failed_reconciliation,
$is_equality,
);
}

if ($existing_var_type->isSingle()
&& $existing_var_type->hasTemplate()
) {
Expand Down Expand Up @@ -613,6 +627,119 @@ private static function reconcileIsset(
return $existing_var_type->freeze();
}

/**
* @param string[] $suppressed_issues
* @param Reconciler::RECONCILIATION_* $failed_reconciliation
*/
private static function reconcileNull(
Assertion $assertion,
Union $existing_var_type,
?string $key,
bool $negated,
?CodeLocation $code_location,
array $suppressed_issues,
int &$failed_reconciliation,
bool $is_equality
): Union {
$types = $existing_var_type->getAtomicTypes();
$old_var_type_string = $existing_var_type->getId();

if ($existing_var_type->isEmptyMixed() || $existing_var_type->isVanillaMixed()) {
if ($assertion instanceof IsLooselyEqual) {
return $existing_var_type;
}

return Type::getNull();
}

$redundant = true;
if ($existing_var_type->possibly_undefined
|| $existing_var_type->possibly_undefined_from_try) {
$redundant = false;
} elseif ($existing_var_type->isNull()) {
$redundant = true;
} elseif ($assertion instanceof IsLooselyEqual && $existing_var_type->isAlwaysFalsy()) {
$redundant = true;
} elseif (!$existing_var_type->isNullable()) {
$redundant = false;
}

foreach ($types as $existing_var_type_key => $existing_var_type_part) {
if ($assertion instanceof IsLooselyEqual && $existing_var_type_part->isFalsy()) {
continue;
}

if ($existing_var_type_part instanceof TTemplateParam) {
if ($existing_var_type_part->as->isNull()) {
continue;
}

$as = self::reconcileNull(
$assertion,
$existing_var_type_part->as,
null,
false,
null,
$suppressed_issues,
$failed_reconciliation,
$is_equality,
);

if ($as !== $existing_var_type_part->as) {
$redundant = false;
$types[$existing_var_type_key] = $existing_var_type_part->replaceAs($as);
continue;
}

unset($types[$existing_var_type_key]);
continue;
}

if ($existing_var_type_key === 'null') {
continue;
}

if ($existing_var_type_key === 'mixed'
&& !($existing_var_type_part instanceof TNonEmptyMixed)
&& !($assertion instanceof IsLooselyEqual)
&& !$redundant
&& !$existing_var_type->isNullable()) {
$types[$existing_var_type_key] = new TNull();
continue;
}

$redundant = false;
unset($types[$existing_var_type_key]);
}

if (!$types || $redundant) {
if ($key && $code_location && !$is_equality) {
self::triggerIssueForImpossible(
$existing_var_type,
$old_var_type_string,
$key,
$assertion,
$redundant,
$negated,
$code_location,
$suppressed_issues,
);

if ($redundant) {
$failed_reconciliation = Reconciler::RECONCILIATION_REDUNDANT;
}
}
}

if ($types) {
return $existing_var_type->setTypes($types);
}

$failed_reconciliation = Reconciler::RECONCILIATION_EMPTY;

return Type::getNever();
}

/**
* @param NonEmptyCountable|HasAtLeastCount $assertion
* @param string[] $suppressed_issues
Expand Down

0 comments on commit 9a178ba

Please sign in to comment.