1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Merge pull request #7261 from vimeo/muglug-improve-negated-reconciliation-logic

Improve negated reconciliation logic
This commit is contained in:
orklah 2022-01-01 14:23:45 +01:00 committed by GitHub
commit 6c176bbdb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 116 additions and 123 deletions

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="dev-master@76bb8bc65567d2bc600fd4925c812589917b6ab1">
<files psalm-version="dev-master@16a61a758e44158c19c779c8b0a1c2143e40d83e">
<file src="examples/TemplateChecker.php">
<PossiblyUndefinedIntArrayOffset occurrences="2">
<code>$comment_block-&gt;tags['variablesfrom'][0]</code>
@ -186,8 +186,7 @@
</DeprecatedMethod>
</file>
<file src="src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php">
<PossiblyUndefinedIntArrayOffset occurrences="7">
<code>$result-&gt;existent_method_ids[0]</code>
<PossiblyUndefinedIntArrayOffset occurrences="6">
<code>$result-&gt;invalid_method_call_types[0]</code>
<code>$result-&gt;non_existent_class_method_ids[0]</code>
<code>$result-&gt;non_existent_class_method_ids[0]</code>
@ -399,13 +398,6 @@
<code>new TEmpty()</code>
</DeprecatedClass>
</file>
<file src="src/Psalm/Internal/Type/NegatedAssertionReconciler.php">
<DeprecatedMethod occurrences="3">
<code>Type::getEmpty()</code>
<code>Type::getEmpty()</code>
<code>Type::getEmpty()</code>
</DeprecatedMethod>
</file>
<file src="src/Psalm/Internal/Type/SimpleAssertionReconciler.php">
<DeprecatedClass occurrences="2">
<code>new TEmpty()</code>
@ -432,7 +424,10 @@
<code>new TEmpty</code>
<code>new TEmpty</code>
</DeprecatedClass>
<DeprecatedMethod occurrences="1">
<DeprecatedMethod occurrences="4">
<code>Type::getEmpty()</code>
<code>Type::getEmpty()</code>
<code>Type::getEmpty()</code>
<code>Type::getEmpty()</code>
</DeprecatedMethod>
</file>

View File

@ -7,10 +7,6 @@ use Psalm\Internal\Analyzer\StatementsAnalyzer;
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;
@ -20,7 +16,6 @@ use Psalm\Type\Atomic\TFloat;
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;
@ -82,113 +77,6 @@ class NegatedAssertionReconciler extends Reconciler
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();
@ -200,6 +88,7 @@ class NegatedAssertionReconciler extends Reconciler
$existing_var_type->addType(new TFalse);
} else {
$simple_negated_type = SimpleNegatedAssertionReconciler::reconcile(
$statements_analyzer->getCodebase(),
$assertion,
$existing_var_type,
$key,

View File

@ -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;
@ -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,
@ -67,6 +73,108 @@ class SimpleNegatedAssertionReconciler extends Reconciler
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,

View File

@ -811,6 +811,7 @@ class TypeExpander
);
$else_conditional_return_type = SimpleNegatedAssertionReconciler::reconcile(
$codebase,
$assertion,
$else_conditional_return_type
);