Skip to content

Commit

Permalink
Merge pull request #7287 from orklah/classconst3
Browse files Browse the repository at this point in the history
Identify ints from TClassConstant when checking for LiteralEquality
  • Loading branch information
orklah committed Jan 3, 2022
2 parents 7227007 + a53b521 commit 4d5098f
Showing 1 changed file with 211 additions and 133 deletions.
344 changes: 211 additions & 133 deletions src/Psalm/Internal/Type/AssertionReconciler.php
Expand Up @@ -20,6 +20,7 @@
use Psalm\Type;
use Psalm\Type\Atomic;
use Psalm\Type\Atomic\TArray;
use Psalm\Type\Atomic\TClassConstant;
use Psalm\Type\Atomic\TClassString;
use Psalm\Type\Atomic\TEnumCase;
use Psalm\Type\Atomic\TFloat;
Expand Down Expand Up @@ -189,6 +190,7 @@ public static function reconcile(

if ($bracket_pos) {
return self::handleLiteralEquality(
$statements_analyzer,
$assertion,
$bracket_pos,
$is_loose_equality,
Expand Down Expand Up @@ -958,18 +960,19 @@ private static function filterTypeWithAnother(
}

/**
* @param string[] $suppressed_issues
* @param string[] $suppressed_issues
*/
private static function handleLiteralEquality(
string $assertion,
int $bracket_pos,
bool $is_loose_equality,
Union $existing_var_type,
string $old_var_type_string,
?string $var_id,
bool $negated,
?CodeLocation $code_location,
array $suppressed_issues
StatementsAnalyzer $statements_analyzer,
string $assertion,
int $bracket_pos,
bool $is_loose_equality,
Union $existing_var_type,
string $old_var_type_string,
?string $var_id,
bool $negated,
?CodeLocation $code_location,
array $suppressed_issues
): Union {
$value = substr($assertion, $bracket_pos + 1, -1);

Expand All @@ -978,129 +981,18 @@ private static function handleLiteralEquality(
$existing_var_atomic_types = $existing_var_type->getAtomicTypes();

if ($scalar_type === 'int') {
$value = (int) $value;

if ($existing_var_type->hasMixed()
|| $existing_var_type->hasScalar()
|| $existing_var_type->hasNumeric()
|| $existing_var_type->hasArrayKey()
) {
if ($is_loose_equality) {
return $existing_var_type;
}

return new Union([new TLiteralInt($value)]);
}

$has_int = false;

foreach ($existing_var_atomic_types as $existing_var_atomic_type) {
if ($existing_var_atomic_type instanceof TInt) {
$has_int = true;
} elseif ($existing_var_atomic_type instanceof TTemplateParam) {
if ($existing_var_atomic_type->as->hasMixed()
|| $existing_var_atomic_type->as->hasScalar()
|| $existing_var_atomic_type->as->hasNumeric()
|| $existing_var_atomic_type->as->hasArrayKey()
) {
if ($is_loose_equality) {
return $existing_var_type;
}

return new Union([new TLiteralInt($value)]);
}

if ($existing_var_atomic_type->as->hasInt()) {
$has_int = true;
}
}
}

if ($has_int) {
$existing_int_types = $existing_var_type->getLiteralInts();

if ($existing_int_types) {
$can_be_equal = false;
$did_remove_type = false;

foreach ($existing_var_atomic_types as $atomic_key => $atomic_type) {
if ($atomic_key !== $assertion
&& !($atomic_type instanceof TPositiveInt && $value > 0)
&& !($atomic_type instanceof TIntRange && $atomic_type->contains($value))
) {
$existing_var_type->removeType($atomic_key);
$did_remove_type = true;
} else {
$can_be_equal = true;
}
}

if ($var_id
&& $code_location
&& (!$can_be_equal || (!$did_remove_type && count($existing_var_atomic_types) === 1))
) {
self::triggerIssueForImpossible(
$existing_var_type,
$old_var_type_string,
$var_id,
$assertion,
$can_be_equal,
$negated,
$code_location,
$suppressed_issues
);
}
} else {
$existing_var_type = new Union([new TLiteralInt($value)]);
}
} elseif ($var_id && $code_location && !$is_loose_equality) {
self::triggerIssueForImpossible(
$existing_var_type,
$old_var_type_string,
$var_id,
$assertion,
false,
$negated,
$code_location,
$suppressed_issues
);
} elseif ($is_loose_equality && $existing_var_type->hasFloat()) {
// convert floats to ints
$existing_float_types = $existing_var_type->getLiteralFloats();

if ($existing_float_types) {
$can_be_equal = false;
$did_remove_type = false;

foreach ($existing_var_atomic_types as $atomic_key => $_) {
if (strpos($atomic_key, 'float(') === 0) {
$atomic_key = 'int(' . substr($atomic_key, 6);
}
if ($atomic_key !== $assertion) {
$existing_var_type->removeType($atomic_key);
$did_remove_type = true;
} else {
$can_be_equal = true;
}
}

if ($var_id
&& $code_location
&& (!$can_be_equal || (!$did_remove_type && count($existing_var_atomic_types) === 1))
) {
self::triggerIssueForImpossible(
$existing_var_type,
$old_var_type_string,
$var_id,
$assertion,
$can_be_equal,
$negated,
$code_location,
$suppressed_issues
);
}
}
}
return self::handleLiteralEqualityWithInt(
$statements_analyzer,
$assertion,
$bracket_pos,
$is_loose_equality,
$existing_var_type,
$old_var_type_string,
$var_id,
$negated,
$code_location,
$suppressed_issues
);
} elseif ($scalar_type === 'string'
|| $scalar_type === 'class-string'
|| $scalar_type === 'interface-string'
Expand Down Expand Up @@ -1143,6 +1035,7 @@ private static function handleLiteralEquality(
$existing_var_atomic_type = clone $existing_var_atomic_type;

$existing_var_atomic_type->as = self::handleLiteralEquality(
$statements_analyzer,
$assertion,
$bracket_pos,
false,
Expand Down Expand Up @@ -1359,6 +1252,191 @@ private static function handleLiteralEquality(
return $existing_var_type;
}

/**
* @param string[] $suppressed_issues
*/
private static function handleLiteralEqualityWithInt(
StatementsAnalyzer $statements_analyzer,
string $assertion,
int $bracket_pos,
bool $is_loose_equality,
Union $existing_var_type,
string $old_var_type_string,
?string $var_id,
bool $negated,
?CodeLocation $code_location,
array $suppressed_issues
): Union {
$value = (int) substr($assertion, $bracket_pos + 1, -1);

$compatible_int_type = self::getCompatibleIntType($existing_var_type, $value, $is_loose_equality);
if ($compatible_int_type !== null) {
return $compatible_int_type;
}

$has_int = false;
$existing_var_atomic_types = $existing_var_type->getAtomicTypes();
foreach ($existing_var_atomic_types as $existing_var_atomic_type) {
if ($existing_var_atomic_type instanceof TInt) {
$has_int = true;
} elseif ($existing_var_atomic_type instanceof TTemplateParam) {
$compatible_int_type = self::getCompatibleIntType($existing_var_type, $value, $is_loose_equality);
if ($compatible_int_type !== null) {
return $compatible_int_type;
}

if ($existing_var_atomic_type->as->hasInt()) {
$has_int = true;
}
} elseif ($existing_var_atomic_type instanceof TClassConstant) {
$expanded = TypeExpander::expandAtomic(
$statements_analyzer->getCodebase(),
$existing_var_atomic_type,
$existing_var_atomic_type->fq_classlike_name,
$existing_var_atomic_type->fq_classlike_name,
null,
true,
true
);

if ($expanded instanceof Atomic) {
$compatible_int_type = self::getCompatibleIntType($existing_var_type, $value, $is_loose_equality);
if ($compatible_int_type !== null) {
return $compatible_int_type;
}

if ($expanded instanceof TInt) {
$has_int = true;
}
} else {
foreach ($expanded as $expanded_type) {
$compatible_int_type = self::getCompatibleIntType(
$existing_var_type,
$value,
$is_loose_equality
);

if ($compatible_int_type !== null) {
return $compatible_int_type;
}

if ($expanded_type instanceof TInt) {
$has_int = true;
}
}
}
}
}

if ($has_int) {
$existing_int_types = $existing_var_type->getLiteralInts();

if ($existing_int_types) {
$can_be_equal = false;
$did_remove_type = false;

foreach ($existing_var_atomic_types as $atomic_key => $atomic_type) {
if ($atomic_key !== $assertion
&& !($atomic_type instanceof TPositiveInt && $value > 0)
&& !($atomic_type instanceof TIntRange && $atomic_type->contains($value))
) {
$existing_var_type->removeType($atomic_key);
$did_remove_type = true;
} else {
$can_be_equal = true;
}
}

if ($var_id
&& $code_location
&& (!$can_be_equal || (!$did_remove_type && count($existing_var_atomic_types) === 1))
) {
self::triggerIssueForImpossible(
$existing_var_type,
$old_var_type_string,
$var_id,
$assertion,
$can_be_equal,
$negated,
$code_location,
$suppressed_issues
);
}
} else {
$existing_var_type = new Union([new TLiteralInt($value)]);
}
} elseif ($var_id && $code_location && !$is_loose_equality) {
self::triggerIssueForImpossible(
$existing_var_type,
$old_var_type_string,
$var_id,
$assertion,
false,
$negated,
$code_location,
$suppressed_issues
);
} elseif ($is_loose_equality && $existing_var_type->hasFloat()) {
// convert floats to ints
$existing_float_types = $existing_var_type->getLiteralFloats();

if ($existing_float_types) {
$can_be_equal = false;
$did_remove_type = false;

foreach ($existing_var_atomic_types as $atomic_key => $_) {
if (strpos($atomic_key, 'float(') === 0) {
$atomic_key = 'int(' . substr($atomic_key, 6);
}
if ($atomic_key !== $assertion) {
$existing_var_type->removeType($atomic_key);
$did_remove_type = true;
} else {
$can_be_equal = true;
}
}

if ($var_id
&& $code_location
&& (!$can_be_equal || (!$did_remove_type && count($existing_var_atomic_types) === 1))
) {
self::triggerIssueForImpossible(
$existing_var_type,
$old_var_type_string,
$var_id,
$assertion,
$can_be_equal,
$negated,
$code_location,
$suppressed_issues
);
}
}
}

return $existing_var_type;
}

private static function getCompatibleIntType(
Union $existing_var_type,
int $value,
bool $is_loose_equality
): ?Union {
if ($existing_var_type->hasMixed()
|| $existing_var_type->hasScalar()
|| $existing_var_type->hasNumeric()
|| $existing_var_type->hasArrayKey()
) {
if ($is_loose_equality) {
return $existing_var_type;
}

return new Union([new TLiteralInt($value)]);
}

return null;
}

/**
* @param array<string, array<string, Union>> $template_type_map
* @param array<string> $suppressed_issues
Expand Down

0 comments on commit 4d5098f

Please sign in to comment.