-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
warn-unreachable false-positive when method updates attribute after assert #11969
Comments
In this scenario mypy pretty much assumes everything is immutable and only takes into consideration explicit mutation, not side effects from any function calls. This is because otherwise most narrowing would be invalidated instantly. And to check each function to see if and what variables it mutates would be extremely slow and complicated. |
Yes, I agree with @KotlinIsland. This is intentional. You can use |
Methods that mutate attributes are surely a common occurrence, and I feel that assuming that they don't exist for the purpose of detecting unreachable code is the wrong thing to do, as it would lead to too many false positives. I don't know what sort of narrowing would be invalidated by not assuming everything is immutable, but could it be possible to only "invalidate" it for warn-unreachable purposes? |
I agree with @jwodder - this is common and its a bad assumption variables can't change. Typescript for example also has type narrowing and does not have this issue: https://www.typescriptlang.org/docs/handbook/2/narrowing.html
|
TypeScript also assumes that variables are not changed "behind the back" of the type checker in another execution scope. Without this assumption, it wouldn't be feasible to apply type narrowing to any member access expressions. class Foo {
bar: number | string = 0;
setBarToString() {
this.bar = '';
}
}
function test() {
const f = new Foo();
f.bar = 1;
const val1 = f.bar; // Type of "val1" is revealed as "number"
f.setBarToString();
const val2 = f.bar; // Type of "val2" is still revealed as "number"
} |
Kotlin handles this correctly by only narrowing class Foo(
val foo: Any,
var bar: Any,
)
fun test() {
val f = Foo(1, 1);
if (f.foo is String) {
val v2 = f.foo // Type of "v2" is revealed as "String"
}
if (f.bar is String) {
val v2 = f.bar // Type of "v2" is still revealed as "Any"
}
(f.bar as? String)?.let {
val v2 = it // Type of "v2" is now revealed as "String"
}
} This way Kotlin is able to maintain perfect correctness while still achieving useful narrowing. |
I asked about this in SO where user sytech introduced a workaround using type guard functions using TypeGuard. They're essentially functions that must return a boolean, and if they return True the first positional argument (or first after self in methods) is guaranteed to have type I wrote two helper functions,
Full example: import sys
from typing import Type, TypeVar
if sys.version_info < (3, 10):
from typing_extensions import TypeGuard
else:
from typing import TypeGuard
from dataclasses import dataclass
@dataclass
class Foo:
value: int
modified: bool = False
def update(self) -> None:
self.value += 1
self.modified = True
_T = TypeVar("_T")
def object_is(obj, value: _T) -> TypeGuard[_T]:
return obj is value
def is_instance(val, klass: Type[_T]) -> TypeGuard[_T]:
return isinstance(val, klass)
foo = Foo(42)
assert object_is(foo.modified, False)
foo.update()
assert object_is(foo.modified, True)
print("Reached") This uses typing-extensions to make the solution to work on older versions, as TypeGuard was introduced in Python 3.10. I tested this with mypy 1.9.0 and it works well on Python 3.7 to 3.12. |
Consider the following code:
When this is run, the
print("Reached")
line is reached, but mypy doesn't seem to realize that. Runningmypy --warn-unreachable --pretty
on the above code gives:Your Environment
--warn-unreachable --pretty
mypy.ini
(and other config files): noneThe text was updated successfully, but these errors were encountered: