Summary
Ty fails to correctly narrow the type of a variable when a is None check is stored in an intermediate boolean variable, and the original variable is then reassigned within a conditional block that uses that boolean.
In the example below, int_or_none starts as int | None. A boolean is_none is created to store the result of int_or_none is None. Inside an if is_none: block, int_or_none is reassigned to an int. After this conditional block, the type of int_or_none is guaranteed to be int. However, Ty still considers its type to be int | None, leading to a false positive invalid-argument-type error.
Other type checkers like Pyright correctly narrow the type in this scenario.
Minimal Reproducible Example
import random
def return_int_or_none() -> int | None:
if random.randint(0, 1):
return 1
return None
def take_int_only(must_be_int: int):
pass
int_or_none: int | None = return_int_or_none()
is_none = int_or_none is None
if is_none:
int_or_none = 1
# At this point, int_or_none is guaranteed to be an `int`.
# If it was None, it was reassigned to 1.
# If it was an int, it remains an int.
take_int_only(int_or_none)
Current Behavior (Ty's Output)
Ty incorrectly reports an error, failing to narrow the type of int_or_none.
$ uvx ty check test.py
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
error[invalid-argument-type]: Argument to function `take_int_only` is incorrect
--> test.py:17:15
|
15 | int_or_none = 1
16 | # At this point, int_or_none is guaranteed to be an `int`.
17 | take_int_only(int_or_none)
| ^^^^^^^^^^^ Expected `int`, found `int | None`
|
info: Function defined here
--> test.py:9:5
|
7 | return None
8 |
9 | def take_int_only(must_be_int: int):
| ^^^^^^^^^^^^^ ---------------- Parameter declared here
10 | pass
|
info: rule `invalid-argument-type` is enabled by default
Found 1 diagnostic
Expected Behavior
Ty should recognize that after the conditional block, the variable int_or_none can only be of type int and should not raise an error.
For comparison, Pyright correctly handles this control flow and reports no errors:
$ pyright test.py
0 errors, 0 warnings, 0 informations
Command and Settings
The command used was uvx ty check <filename>.py with default settings.
Playground Link
This issue can be reproduced in the Ty playground here:
https://play.ty.dev/eba9c0b6-0443-492f-be34-b8eea7d795c2
Version
ty 0.0.1-alpha.12
Summary
Ty fails to correctly narrow the type of a variable when a
is Nonecheck is stored in an intermediate boolean variable, and the original variable is then reassigned within a conditional block that uses that boolean.In the example below,
int_or_nonestarts asint | None. A booleanis_noneis created to store the result ofint_or_none is None. Inside anif is_none:block,int_or_noneis reassigned to anint. After this conditional block, the type ofint_or_noneis guaranteed to beint. However, Ty still considers its type to beint | None, leading to a false positiveinvalid-argument-typeerror.Other type checkers like Pyright correctly narrow the type in this scenario.
Minimal Reproducible Example
Current Behavior (Ty's Output)
Ty incorrectly reports an error, failing to narrow the type of
int_or_none.Expected Behavior
Ty should recognize that after the conditional block, the variable
int_or_nonecan only be of typeintand should not raise an error.For comparison, Pyright correctly handles this control flow and reports no errors:
Command and Settings
The command used was
uvx ty check <filename>.pywith default settings.Playground Link
This issue can be reproduced in the Ty playground here:
https://play.ty.dev/eba9c0b6-0443-492f-be34-b8eea7d795c2
Version
ty 0.0.1-alpha.12