-
Notifications
You must be signed in to change notification settings - Fork 662
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
False positive with assert-if-true/if-false #5248
Comments
I found these snippets: https://psalm.dev/r/91745556ee<?php
/**
* @psalm-assert-if-false string $value
* @psalm-assert-if-true int $value
*/
function isRight(string|int $value): bool
{
return is_int($value);
}
function testRight(int $v): void {}
function testLeft(string $v): void {}
/** @var int|string */
$value = 0;
isRight($value)
? testRight($value)
: testLeft($value);
if (isRight($value)) {
testRight($value);
} else {
testLeft($value);
}
!isRight($value)
? testLeft($value)
: testRight($value);
if (!isRight($value)) {
testLeft($value);
} else {
testRight($value);
}
https://psalm.dev/r/a5500b4ed2<?php
/**
* @psalm-assert-if-true int $value
*/
function isRight(string|int $value): bool
{
return is_int($value);
}
function testRight(int $v): void {}
function testLeft(string $v): void {}
/** @var int|string */
$value = 0;
isRight($value)
? testRight($value)
: testLeft($value);
if (isRight($value)) {
testRight($value);
} else {
testLeft($value);
}
!isRight($value)
? testLeft($value)
: testRight($value);
if (!isRight($value)) {
testLeft($value);
} else {
testRight($value);
}
https://psalm.dev/r/408248f46f<?php
/**
* @template L
* @template R
*/
interface Either {}
/**
* @template R
* @implements Either<empty, R>
*/
final class Right implements Either {}
/**
* @template L
* @implements Either<L, empty>
*/
final class Left implements Either {}
/**
* @template L
* @template R
*
* @param Either<L, R> $either
* @return bool
*
* @psalm-assert-if-true Left<L> $either
* @psalm-assert-if-false Right<L> $either
*/
function isLeft(Either $either): bool
{
throw new RuntimeException('???');
}
/** @var Either<int, string> $either */
$either = null;
if (!isLeft($either)) {
/** @psalm-trace $left */
$left = $either;
} else {
/** @psalm-trace $right */
$right = $either;
}
|
Also this doesn't seem right: https://psalm.dev/r/65cbc43cff |
I found these snippets: https://psalm.dev/r/65cbc43cff<?php
/** @psalm-assert-if-true string $value */
function isString(mixed $value): bool {
return is_string($value);
}
/** @var int|string */
$value = 0;
if (!isString($value)) {
/** @psalm-trace $value */; // should be int|string, as predicate does not declare anything for false
}
|
@muglug it looks like Psalm generates inverted assertion ( |
The same problem for Either monad. I'd like to do something like this, but unfortunately it's not working for both psalm-assert-if-true and psalm-assert-if-false at the same time. /**
* @psalm-assert-if-true Left<L, R> $this
* @psalm-assert-if-false Right<L, R> $this
*/
public function isLeft(): bool {}
/**
* @psalm-assert-if-true Right<L, R> $this
* @psalm-assert-if-false Left<L, R> $this
*/
public function isRight(): bool {} |
Issue is solved: https://psalm.dev/r/b0b6a03bd6 |
I found these snippets: https://psalm.dev/r/b0b6a03bd6<?php
/**
* @template L
* @template R
*/
interface Either {}
/**
* @template R
* @implements Either<never, R>
*/
final class Right implements Either {
/**
* @return R
*/
public function get(): mixed {throw new \RuntimeException('???');}
}
/**
* @template L
* @implements Either<L, never>
*/
final class Left implements Either {
/**
* @return L
*/
public function get(): mixed {throw new \RuntimeException('???');}
}
/**
* @template L
* @template R
*
* @param Either<L, R> $either
* @return bool
*
* @psalm-assert-if-true Left<L> $either
* @psalm-assert-if-false Right<L> $either
*/
function isLeft(Either $either): bool
{
throw new RuntimeException('???');
}
/** @var Either<int, string> $either */;
if (isLeft($either)) {
$_int = $either->get();
/** @psalm-check-type $_int = int */
} else {
$_string = $either->get();
/** @psalm-check-type $_string = int */
}
|
I hastened to close the issue. Sorry. Issue still exists. |
I found these snippets: https://psalm.dev/r/6125f6ef36<?php
/**
* @template L
* @template R
*/
abstract class Either
{
/**
* @psalm-assert-if-false Right<R> $this
* @psalm-assert-if-true Left<L> $this
*/
public function isLeft(): bool
{
throw new RuntimeException('???');
}
}
/**
* @template R
* @extends Either<never, R>
*/
final class Right extends Either {
/**
* @return R
*/
public function get(): mixed {throw new \RuntimeException('???');}
}
/**
* @template L
* @extends Either<L, never>
*/
final class Left extends Either {
/**
* @return L
*/
public function get(): mixed {throw new \RuntimeException('???');}
}
/** @var Either<int, string> $either */;
if ($either->isLeft()) {
$_int = $either->get();
/** @psalm-trace $either */;
} else {
$_string = $either->get();
/** @psalm-trace $either */;
}
|
If assert function has
@psalm-assert-if-false
and@psalm-assert-if-true
at the same time then it works incorrectly:https://psalm.dev/r/91745556ee
Quick workaround is drop
@psalm-assert-if-false
or@psalm-assert-if-true
from assert function:https://psalm.dev/r/a5500b4ed2
But above workaround doesn't work in more difficult cases:
https://psalm.dev/r/408248f46f
The text was updated successfully, but these errors were encountered: