diff --git a/conformance/results/mypy/specialtypes_promotions.toml b/conformance/results/mypy/specialtypes_promotions.toml index 864bc73e..04499e86 100644 --- a/conformance/results/mypy/specialtypes_promotions.toml +++ b/conformance/results/mypy/specialtypes_promotions.toml @@ -1,7 +1,12 @@ -conformant = "Pass" +conformant = "Partial" +notes = """ +Does not narrow from float to int after isinstance() check +""" output = """ -specialtypes_promotions.py:13: error: "float" has no attribute "numerator" [attr-defined] +specialtypes_promotions.py:17: error: "float" has no attribute "numerator" [attr-defined] +specialtypes_promotions.py:33: error: Incompatible return value type (got "complex", expected "float") [return-value] """ -conformance_automated = "Pass" +conformance_automated = "Fail" errors_diff = """ +Line 26: Expected 1 errors """ diff --git a/conformance/results/mypy/version.toml b/conformance/results/mypy/version.toml index 268bd31a..dc9b55ed 100644 --- a/conformance/results/mypy/version.toml +++ b/conformance/results/mypy/version.toml @@ -1,2 +1,2 @@ version = "mypy 1.10.0" -test_duration = 1.4 +test_duration = 1.5 diff --git a/conformance/results/pyre/specialtypes_promotions.toml b/conformance/results/pyre/specialtypes_promotions.toml index fb9055fb..f17ad77f 100644 --- a/conformance/results/pyre/specialtypes_promotions.toml +++ b/conformance/results/pyre/specialtypes_promotions.toml @@ -1,10 +1,13 @@ conformant = "Partial" notes = """ Does not reject use of attribute that is compatible only with float. +Does not narrow from float to int after isinstance() check """ output = """ +specialtypes_promotions.py:33:8 Incompatible return type [7]: Expected `float` but got `complex`. """ conformance_automated = "Fail" errors_diff = """ -Line 13: Expected 1 errors +Line 17: Expected 1 errors +Line 26: Expected 1 errors """ diff --git a/conformance/results/pyre/version.toml b/conformance/results/pyre/version.toml index 57ed5696..1fec89de 100644 --- a/conformance/results/pyre/version.toml +++ b/conformance/results/pyre/version.toml @@ -1,2 +1,2 @@ version = "pyre 0.9.21" -test_duration = 3.4 +test_duration = 2.9 diff --git a/conformance/results/pyright/specialtypes_promotions.toml b/conformance/results/pyright/specialtypes_promotions.toml index 20daf3a3..560cb119 100644 --- a/conformance/results/pyright/specialtypes_promotions.toml +++ b/conformance/results/pyright/specialtypes_promotions.toml @@ -1,7 +1,11 @@ conformant = "Pass" output = """ -specialtypes_promotions.py:13:7 - error: Cannot access attribute "numerator" for class "float" +specialtypes_promotions.py:17:7 - error: Cannot access attribute "numerator" for class "float"   Attribute "numerator" is unknown (reportAttributeAccessIssue) +specialtypes_promotions.py:26:16 - error: Expression of type "Literal['x']" is incompatible with return type "int" +  "Literal['x']" is incompatible with "int" (reportReturnType) +specialtypes_promotions.py:33:16 - error: Expression of type "complex" is incompatible with return type "float" +  "complex" is incompatible with "float" (reportReturnType) """ conformance_automated = "Pass" errors_diff = """ diff --git a/conformance/results/pyright/version.toml b/conformance/results/pyright/version.toml index 6302edbf..789c07a2 100644 --- a/conformance/results/pyright/version.toml +++ b/conformance/results/pyright/version.toml @@ -1,2 +1,2 @@ version = "pyright 1.1.364" -test_duration = 1.4 +test_duration = 1.6 diff --git a/conformance/results/pytype/specialtypes_promotions.toml b/conformance/results/pytype/specialtypes_promotions.toml index dc20943f..3d6ee073 100644 --- a/conformance/results/pytype/specialtypes_promotions.toml +++ b/conformance/results/pytype/specialtypes_promotions.toml @@ -1,7 +1,12 @@ -conformant = "Pass" +conformant = "Partial" +notes = """ +Does not narrow from float to int after isinstance() check +""" output = """ -File "specialtypes_promotions.py", line 13, in func1: No attribute 'numerator' on float [attribute-error] +File "specialtypes_promotions.py", line 17, in func1: No attribute 'numerator' on float [attribute-error] +File "specialtypes_promotions.py", line 33, in func2: bad return type [bad-return-type] """ -conformance_automated = "Pass" +conformance_automated = "Fail" errors_diff = """ +Line 26: Expected 1 errors """ diff --git a/conformance/results/pytype/version.toml b/conformance/results/pytype/version.toml index 68b8ac60..bbfb29cd 100644 --- a/conformance/results/pytype/version.toml +++ b/conformance/results/pytype/version.toml @@ -1,2 +1,2 @@ version = "pytype 2024.04.11" -test_duration = 30.1 +test_duration = 49.8 diff --git a/conformance/results/results.html b/conformance/results/results.html index 9693b2e8..bc90bed9 100644 --- a/conformance/results/results.html +++ b/conformance/results/results.html @@ -159,16 +159,16 @@

Python Type System Conformance Test Results

 
mypy 1.10.0
-
1.4sec
+
1.5sec
pyright 1.1.364
-
1.4sec
+
1.6sec
pyre 0.9.21
-
3.4sec
+
2.9sec
pytype 2024.04.11
-
30.1sec
+
49.8sec
diff --git a/conformance/tests/specialtypes_promotions.py b/conformance/tests/specialtypes_promotions.py index fc51e133..0216337d 100644 --- a/conformance/tests/specialtypes_promotions.py +++ b/conformance/tests/specialtypes_promotions.py @@ -2,15 +2,36 @@ Tests "type promotions" for float and complex when they appear in annotations. """ +from typing import assert_type + # Specification: https://typing.readthedocs.io/en/latest/spec/special-types.html#special-cases-for-float-and-complex -v1: float = 1 -v2: complex = 1.2 -v2 = 1 +v1: int = 1 +v2: float = 1 +v3: float = v1 +v4: complex = 1.2 +v4 = 1 + + +def func1(f: float) -> int: + f.numerator # E: attribute exists on int but not float + if isinstance(f, float): + f.hex() # OK (attribute exists on float but not int) + return 1 + else: + assert_type(f, int) + # Make sure type checkers don't treat this branch as unreachable + # and skip checking it. + return "x" # E -def func1(f: float): - f.numerator # E - if not isinstance(f, float): - f.numerator # OK +def func2(x: int) -> float: + if x == 0: + return 1 + elif x == 1: + return 1j # E + elif x > 10: + return x + else: + return 1.0 diff --git a/docs/spec/special-types.rst b/docs/spec/special-types.rst index b94222e6..b0339ae0 100644 --- a/docs/spec/special-types.rst +++ b/docs/spec/special-types.rst @@ -113,11 +113,25 @@ Special cases for ``float`` and ``complex`` Python's numeric types ``complex``, ``float`` and ``int`` are not subtypes of each other, but to support common use cases, the type -system contains a straightforward shortcut: -when an argument is annotated as having -type ``float``, an argument of type ``int`` is acceptable; similar, -for an argument annotated as having type ``complex``, arguments of -type ``float`` or ``int`` are acceptable. +system contains a special case. + +When a reference to the built-in type ``float`` appears in a :term:`type expression`, +it is interpreted as if it were a union of the built-in types ``float`` and ``int``. +Similarly, when a reference to the type ``complex`` appears, it is interpreted as +a union of the built-in types ``complex``, ``float`` and ``int``. +These implicit unions behave exactly like the corresponding explicit union types, +but type checkers may choose to display them differently in user-visible output +for clarity. + +Type checkers should support narrowing the type of a variable to exactly ``float`` +or ``int``, without the implicit union, through a call to ``isinstance()``:: + + def f(x: float) -> None: + reveal_type(x) # float | int, but type checkers may display just "float" + if isinstance(x, float): + reveal_type(x) # float + else: + reveal_type(x) # int .. _`type-brackets`: