Skip to content

Commit

Permalink
Improve negated reconciliation logic
Browse files Browse the repository at this point in the history
  • Loading branch information
muglug committed Jan 1, 2022
1 parent 2a6f122 commit 11f322c
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 112 deletions.
113 changes: 1 addition & 112 deletions src/Psalm/Internal/Type/NegatedAssertionReconciler.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@
use Psalm\Internal\Analyzer\TraitAnalyzer;
use Psalm\Internal\Type\Comparator\AtomicTypeComparator;
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\Issue\DocblockTypeContradiction;
use Psalm\Issue\RedundantPropertyInitializationCheck;
use Psalm\Issue\TypeDoesNotContainType;
use Psalm\IssueBuffer;
use Psalm\Type;
use Psalm\Type\Atomic\TArray;
use Psalm\Type\Atomic\TEmptyMixed;
Expand All @@ -20,7 +16,6 @@
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TIterable;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TNonEmptyMixed;
use Psalm\Type\Atomic\TNonEmptyString;
use Psalm\Type\Atomic\TString;
use Psalm\Type\Atomic\TTrue;
Expand Down Expand Up @@ -82,113 +77,6 @@ public static function reconcile(
return $existing_var_type;
}

if (!$is_equality) {
if ($assertion === 'isset') {
if ($existing_var_type->possibly_undefined) {
return Type::getEmpty();
}

if (!$existing_var_type->isNullable()
&& $key
&& strpos($key, '[') === false
) {
foreach ($existing_var_type->getAtomicTypes() as $atomic) {
if (!$existing_var_type->hasMixed()
|| $atomic instanceof TNonEmptyMixed
) {
$failed_reconciliation = Reconciler::RECONCILIATION_EMPTY;

if ($code_location) {
if ($existing_var_type->from_static_property) {
IssueBuffer::maybeAdd(
new RedundantPropertyInitializationCheck(
'Static property ' . $key . ' with type '
. $existing_var_type
. ' has unexpected isset check — should it be nullable?',
$code_location
),
$suppressed_issues
);
} elseif ($existing_var_type->from_property) {
IssueBuffer::maybeAdd(
new RedundantPropertyInitializationCheck(
'Property ' . $key . ' with type '
. $existing_var_type . ' should already be set in the constructor',
$code_location
),
$suppressed_issues
);
} elseif ($existing_var_type->from_docblock) {
IssueBuffer::maybeAdd(
new DocblockTypeContradiction(
'Cannot resolve types for ' . $key . ' with docblock-defined type '
. $existing_var_type . ' and !isset assertion',
$code_location,
null
),
$suppressed_issues
);
} else {
IssueBuffer::maybeAdd(
new TypeDoesNotContainType(
'Cannot resolve types for ' . $key . ' with type '
. $existing_var_type . ' and !isset assertion',
$code_location,
null
),
$suppressed_issues
);
}
}

return $existing_var_type->from_docblock
? Type::getNull()
: Type::getEmpty();
}
}
}

return Type::getNull();
}

if ($assertion === 'array-key-exists') {
return Type::getEmpty();
}

if (strpos($assertion, 'in-array-') === 0) {
$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 = Reconciler::RECONCILIATION_EMPTY;
}

return $existing_var_type;
}

if (strpos($assertion, 'has-array-key-') === 0) {
return $existing_var_type;
}
}

$existing_var_atomic_types = $existing_var_type->getAtomicTypes();

Expand All @@ -200,6 +88,7 @@ public static function reconcile(
$existing_var_type->addType(new TFalse);
} else {
$simple_negated_type = SimpleNegatedAssertionReconciler::reconcile(
$statements_analyzer->getCodebase(),
$assertion,
$existing_var_type,
$key,
Expand Down
108 changes: 108 additions & 0 deletions src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
namespace Psalm\Internal\Type;

use Psalm\CodeLocation;
use Psalm\Codebase;
use Psalm\Internal\Codebase\InternalCallMapHandler;
use Psalm\Issue\DocblockTypeContradiction;
use Psalm\Issue\RedundantPropertyInitializationCheck;
use Psalm\Issue\TypeDoesNotContainType;
use Psalm\IssueBuffer;
use Psalm\Type;
use Psalm\Type\Atomic\Scalar;
use Psalm\Type\Atomic\TArray;
Expand Down Expand Up @@ -57,6 +62,7 @@ class SimpleNegatedAssertionReconciler extends Reconciler
* @param 0|1|2 $failed_reconciliation
*/
public static function reconcile(
Codebase $codebase,
string $assertion,
Union $existing_var_type,
?string $key = null,
Expand All @@ -67,6 +73,108 @@ public static function reconcile(
bool $is_equality = false,
bool $inside_loop = false
): ?Union {
if ($assertion === 'isset') {
if ($existing_var_type->possibly_undefined) {
return Type::getEmpty();
}

if (!$existing_var_type->isNullable()
&& $key
&& strpos($key, '[') === false
) {
foreach ($existing_var_type->getAtomicTypes() as $atomic) {
if (!$existing_var_type->hasMixed()
|| $atomic instanceof TNonEmptyMixed
) {
$failed_reconciliation = Reconciler::RECONCILIATION_EMPTY;

if ($code_location) {
if ($existing_var_type->from_static_property) {
IssueBuffer::maybeAdd(
new RedundantPropertyInitializationCheck(
'Static property ' . $key . ' with type '
. $existing_var_type
. ' has unexpected isset check — should it be nullable?',
$code_location
),
$suppressed_issues
);
} elseif ($existing_var_type->from_property) {
IssueBuffer::maybeAdd(
new RedundantPropertyInitializationCheck(
'Property ' . $key . ' with type '
. $existing_var_type . ' should already be set in the constructor',
$code_location
),
$suppressed_issues
);
} elseif ($existing_var_type->from_docblock) {
IssueBuffer::maybeAdd(
new DocblockTypeContradiction(
'Cannot resolve types for ' . $key . ' with docblock-defined type '
. $existing_var_type . ' and !isset assertion',
$code_location,
null
),
$suppressed_issues
);
} else {
IssueBuffer::maybeAdd(
new TypeDoesNotContainType(
'Cannot resolve types for ' . $key . ' with type '
. $existing_var_type . ' and !isset assertion',
$code_location,
null
),
$suppressed_issues
);
}
}

return $existing_var_type->from_docblock
? Type::getNull()
: Type::getEmpty();
}
}
}

return Type::getNull();
}

if ($assertion === 'array-key-exists') {
return Type::getEmpty();
}

if (strpos($assertion, 'in-array-') === 0) {
$assertion = substr($assertion, 9);
$new_var_type = Type::parseString($assertion);

$intersection = Type::intersectUnionTypes(
$new_var_type,
$existing_var_type,
$codebase
);

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 = Reconciler::RECONCILIATION_EMPTY;
}

return $existing_var_type;
}

if ($assertion === 'object' && !$existing_var_type->hasMixed()) {
return self::reconcileObject(
$existing_var_type,
Expand Down
1 change: 1 addition & 0 deletions src/Psalm/Internal/Type/TypeExpander.php
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,7 @@ private static function expandConditional(
);

$else_conditional_return_type = SimpleNegatedAssertionReconciler::reconcile(
$codebase,
$assertion,
$else_conditional_return_type
);
Expand Down

0 comments on commit 11f322c

Please sign in to comment.