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/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 c759e78d..6221116b 100644 --- a/conformance/results/pyre/version.toml +++ b/conformance/results/pyre/version.toml @@ -1,2 +1,2 @@ version = "pyre 0.9.23" -test_duration = 10.7 +test_duration = 10.4 diff --git a/conformance/results/pyright/specialtypes_promotions.toml b/conformance/results/pyright/specialtypes_promotions.toml index 20daf3a3..dbe43b05 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: Type "Literal['x']" is not assignable to return type "int" +  "Literal['x']" is not assignable to "int" (reportReturnType) +specialtypes_promotions.py:33:16 - error: Type "complex" is not assignable to return type "float" +  "complex" is not assignable to "float" (reportReturnType) """ conformance_automated = "Pass" errors_diff = """ diff --git a/conformance/results/pyright/version.toml b/conformance/results/pyright/version.toml index ca4eb79c..6686c4e5 100644 --- a/conformance/results/pyright/version.toml +++ b/conformance/results/pyright/version.toml @@ -1,2 +1,2 @@ version = "pyright 1.1.402" -test_duration = 1.8 +test_duration = 1.5 diff --git a/conformance/results/results.html b/conformance/results/results.html index 4cc4ea00..c60166c0 100644 --- a/conformance/results/results.html +++ b/conformance/results/results.html @@ -162,10 +162,10 @@

Python Type System Conformance Test Results

2.2sec
pyright 1.1.402
-
1.8sec
+
1.5sec
pyre 0.9.23
-
10.7sec
+
10.4sec
@@ -215,9 +215,9 @@

Python Type System Conformance Test Results

Pass      specialtypes_promotions +
Partial

Does not narrow from float to int after isinstance() check

Pass -Pass -
Partial

Does not reject use of attribute that is compatible only with float.

+
Partial

Does not reject use of attribute that is compatible only with float.

Does not narrow from float to int after isinstance() check

     specialtypes_type
Partial

Does not treat `type` same as `type[Any]` for assert_type.

Does not allow access to unknown attributes from object of type `type[Any]`.

@@ -324,7 +324,7 @@

Python Type System Conformance Test Results

     generics_type_erasure
Partial

Infers Node[Never] instead of Node[Any] when argument is not provided.

False negative on instance attribute access on type(node).

-
Partial

Missing error regarding `type(instance).generic_attribute`.

+Pass
Partial

Does not erase unspecified type variables to `Any` prior to `assert_type` handling.

False negatives on instance attribute access on the type.

Does not infer type of `DefaultDict` with explicit type parameters on constructor.

False negatives on assert_type uses.

     generics_typevartuple_args @@ -423,7 +423,7 @@

Python Type System Conformance Test Results

     aliases_newtype
Partial

`NewType`s are considered classes, not functions.

-
Partial

`NewType`s are considered classes, not functions.

+Pass
Partial

Does not reject use of NewType in `isinstance` call.

Does not reject use of NewType in class definition statement.

Does not report inconsistency between name of NewType and assigned identifier name.

Does not reject use of NewType with generic class with TypeVar.

Does not reject use of NewType with protocol class.

Does not reject use of NewType with TypedDict class.

Does not reject use of NewType with Any.

     aliases_recursive 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 ce6f336b..5d1229e4 100644 --- a/docs/spec/special-types.rst +++ b/docs/spec/special-types.rst @@ -110,11 +110,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`: