Skip to content

1.x: Fix negation types for Inspector #861

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 15, 2025

Conversation

Niklan
Copy link
Contributor

@Niklan Niklan commented Apr 14, 2025

Port of #860

Comment on lines 12 to 29
// Inspector::assertAll()
$callable = fn (string $value): bool => $value === 'foo';
$input = mixed_function();
assert(Inspector::assertAll($callable, $input));
assertType("iterable", $input);
if (Inspector::assertAll($callable, $input)) {
assertType("iterable", $input);
}
else {
assertType('mixed~iterable', $input);
}

$input = mixed_function();
$callable = is_string(...);
assert(Inspector::assertAll($callable, $input));
assertType('iterable', $input);
if (Inspector::assertAll($callable, $input)) {
assertType('iterable', $input);
}
else {
assertType('mixed~iterable', $input);
}
Copy link
Contributor Author

@Niklan Niklan Apr 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mglaman it's kind of works, but I still don't like it.

$input = mixed_function();
$callable = is_string(...);
if (Inspector::assertAll($callable, $input)) {
    assertType('iterable', $input);
} else {
    assertType('mixed~iterable', $input);
}

Actually, it doesn't mean that $input is not iterable after that ::assertAll(). It just means it does not contain strings, but it can still be iterable... I think in else, it should be mixed, as we can't guarantee it is not iterable. We can't narrow it down to mixed~iterable<string>, so we better fallback to mixed. But if it passes that condition, it is clearly iterable, so we need this method support, but it should be improved.

Copy link
Owner

@mglaman mglaman Apr 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean, but it is never intended to be used in conditionals like this – it's meant to be used in asserts which fail if false. So I think it's OK that the condition fails. Because none of the type specifying assert extensions would work

EDIT: Unless there are examples in PHPStan for is_string that make it feasible. I only read for assert

@Niklan Niklan marked this pull request as draft April 14, 2025 17:36
Comment on lines +110 to 129
// In a negation context, we cannot precisely narrow types because we do
// not know the exact logic of the callable function. This means we
// cannot safely return 'mixed~iterable' since the value might still be
// a valid iterable.
//
// For example, a negation context with an 'is_string(...)' callable
// does not necessarily mean that the value cannot be an
// 'iterable<int>'. In such cases, it is safer to skip type narrowing
// altogether to prevent introducing new bugs into the code.
if ($context->false()) {
return new SpecifiedTypes();
}

return $this->typeSpecifier->create(
$node->getArgs()[1]->value,
new IterableType(new MixedType(), new MixedType()),
TypeSpecifierContext::createTruthy(),
$context,
false,
$scope,
);
Copy link
Contributor Author

@Niklan Niklan Apr 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mglaman now it works much safer:

// Inspector::assertAll()
$callable = fn (string $value): bool => $value === 'foo';
$input = mixed_function();
if (Inspector::assertAll($callable, $input)) {
    assertType("iterable", $input);
}
else {
    assertType('mixed', $input);
}

We can only confirm that it is iterable if the result is TRUE, but when it's FALSE, we cannot be sure what the type is, so we should keep its original type, which is mixed.

It might be possible to narrow it based on the callable, but I think for now it is too complex and not worth the effort. We can always improve it later.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works for me, and good call on the comment

@Niklan Niklan marked this pull request as ready for review April 15, 2025 04:13
@mglaman mglaman merged commit 0a73fb2 into mglaman:1.x Apr 15, 2025
16 checks passed
@Niklan Niklan deleted the 1.x-improve-inspector branch April 15, 2025 15:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants