diff --git a/conformance/results/mypy/annotations_forward_refs.toml b/conformance/results/mypy/annotations_forward_refs.toml index 15211054e..b74ef2246 100644 --- a/conformance/results/mypy/annotations_forward_refs.toml +++ b/conformance/results/mypy/annotations_forward_refs.toml @@ -2,7 +2,14 @@ conformant = "Partial" notes = """ Does not report error for a forward reference that is not enclosed in quotes. Does not report error for use of quoted type with `|` operator (runtime error). -Incorrectly generates error for quoted type defined in class scope. +Resolves some forward references in type annotations in classes at the point of definition instead of end-of-scope +""" +conformance_automated = "Fail" +errors_diff = """ +Line 24: Expected 1 errors +Line 25: Expected 1 errors +Line 94: Expected 1 errors +Line 96: Expected 1 errors """ output = """ annotations_forward_refs.py:41: error: Invalid type comment or annotation [valid-type] @@ -22,17 +29,9 @@ annotations_forward_refs.py:53: error: Invalid type comment or annotation [vali annotations_forward_refs.py:54: error: Invalid type comment or annotation [valid-type] annotations_forward_refs.py:55: error: Module "types" is not valid as a type [valid-type] annotations_forward_refs.py:55: note: Perhaps you meant to use a protocol matching the module structure? -annotations_forward_refs.py:80: error: Name "ClassF" is not defined; did you mean "ClassA", "ClassB", or "ClassC"? [name-defined] -annotations_forward_refs.py:87: error: Function "annotations_forward_refs.ClassD.int" is not valid as a type [valid-type] -annotations_forward_refs.py:87: note: Perhaps you need "Callable[...]" or a callback protocol? -annotations_forward_refs.py:89: error: Function "annotations_forward_refs.ClassD.int" is not valid as a type [valid-type] -annotations_forward_refs.py:89: note: Perhaps you need "Callable[...]" or a callback protocol? -annotations_forward_refs.py:96: error: Expression is of type int?, not "int" [assert-type] -""" -conformance_automated = "Fail" -errors_diff = """ -Line 24: Expected 1 errors -Line 25: Expected 1 errors -Line 87: Unexpected errors ['annotations_forward_refs.py:87: error: Function "annotations_forward_refs.ClassD.int" is not valid as a type [valid-type]'] -Line 96: Unexpected errors ['annotations_forward_refs.py:96: error: Expression is of type int?, not "int" [assert-type]'] +annotations_forward_refs.py:92: error: Name "ClassF" is not defined; did you mean "ClassA", "ClassB", or "ClassC"? [name-defined] +annotations_forward_refs.py:101: error: Function "annotations_forward_refs.ClassD.int" is not valid as a type [valid-type] +annotations_forward_refs.py:101: note: Perhaps you need "Callable[...]" or a callback protocol? +annotations_forward_refs.py:103: error: Function "annotations_forward_refs.ClassD.int" is not valid as a type [valid-type] +annotations_forward_refs.py:103: note: Perhaps you need "Callable[...]" or a callback protocol? """ diff --git a/conformance/results/mypy/annotations_future_import.toml b/conformance/results/mypy/annotations_future_import.toml new file mode 100644 index 000000000..88012982f --- /dev/null +++ b/conformance/results/mypy/annotations_future_import.toml @@ -0,0 +1,15 @@ +conformant = "Partial" +notes = """ +Resolves some forward references in type annotations in classes at the point of definition instead of end-of-scope +""" +conformance_automated = "Fail" +errors_diff = """ +Line 37: Expected 1 errors +Line 39: Expected 1 errors +""" +output = """ +annotations_future_import.py:44: error: Function "annotations_future_import.ClassB.int" is not valid as a type [valid-type] +annotations_future_import.py:44: note: Perhaps you need "Callable[...]" or a callback protocol? +annotations_future_import.py:46: error: Function "annotations_future_import.ClassB.int" is not valid as a type [valid-type] +annotations_future_import.py:46: note: Perhaps you need "Callable[...]" or a callback protocol? +""" diff --git a/conformance/results/pycroscope/annotations_forward_refs.toml b/conformance/results/pycroscope/annotations_forward_refs.toml index 63fbfc009..f1bdcad94 100644 --- a/conformance/results/pycroscope/annotations_forward_refs.toml +++ b/conformance/results/pycroscope/annotations_forward_refs.toml @@ -1,19 +1,22 @@ conformant = "Partial" notes = """ Fails to reject `"x" | int` annotations that fail at runtime. +Resolves forward references in type annotations in classes at the point of definition instead of end-of-scope Rejects some valid quoted annotations. """ conformance_automated = "Fail" errors_diff = """ Line 24: Expected 1 errors Line 25: Expected 1 errors +Line 94: Expected 1 errors +Line 96: Expected 1 errors Line 14: Unexpected errors ['./annotations_forward_refs.py:14:8: Undefined name: ClassA [undefined_name]', './annotations_forward_refs.py:14:22: Undefined name: ClassA [undefined_name]', './annotations_forward_refs.py:14:42: Undefined name: ClassA [undefined_name]', './annotations_forward_refs.py:14:62: Undefined name: ClassA [undefined_name]'] Line 16: Unexpected errors ['./annotations_forward_refs.py:16:16: Any[error] is not equivalent to ./annotations_forward_refs.py.ClassA'] Line 17: Unexpected errors ['./annotations_forward_refs.py:17:16: list[Any[error]] is not equivalent to list[./annotations_forward_refs.py.ClassA]'] Line 18: Unexpected errors ['./annotations_forward_refs.py:18:16: list[Any[error]] is not equivalent to list[./annotations_forward_refs.py.ClassA]'] Line 19: Unexpected errors ['./annotations_forward_refs.py:19:16: list[int | Any[error]] is not equivalent to list[./annotations_forward_refs.py.ClassA | int]'] -Line 87: Unexpected errors ['./annotations_forward_refs.py:87:7: Unrecognized annotation (self: ./annotations_forward_refs.py.ClassD) -> None [invalid_annotation]'] -Line 96: Unexpected errors ['./annotations_forward_refs.py:96:12: Any[error] is not equivalent to int'] +Line 84: Unexpected errors ['./annotations_forward_refs.py:84:12: Undefined name: ClassInner [undefined_name]'] +Line 112: Unexpected errors ['./annotations_forward_refs.py:112:16: Any[error] is not equivalent to ./annotations_forward_refs.py.ClassD.ClassInner'] """ output = """ ./annotations_forward_refs.py:14:8: Undefined name: ClassA [undefined_name] @@ -41,8 +44,11 @@ output = """ ./annotations_forward_refs.py:53:0: Invalid type annotation [invalid_annotation] ./annotations_forward_refs.py:54:0: Invalid type annotation [invalid_annotation] ./annotations_forward_refs.py:55:9: Invalid type annotation [invalid_annotation] -./annotations_forward_refs.py:80:12: Undefined name: ClassF [undefined_name] -./annotations_forward_refs.py:87:7: Unrecognized annotation (self: ./annotations_forward_refs.py.ClassD) -> None [invalid_annotation] -./annotations_forward_refs.py:89:7: Unrecognized annotation (self: ./annotations_forward_refs.py.ClassD) -> None [invalid_annotation] -./annotations_forward_refs.py:96:12: Any[error] is not equivalent to int +./annotations_forward_refs.py:83:12: Undefined name: ClassInner [undefined_name] +./annotations_forward_refs.py:84:12: Undefined name: ClassInner [undefined_name] +./annotations_forward_refs.py:92:12: Undefined name: ClassF [undefined_name] +./annotations_forward_refs.py:101:7: Unrecognized annotation (self: ./annotations_forward_refs.py.ClassD) -> None [invalid_annotation] +./annotations_forward_refs.py:103:7: Unrecognized annotation (self: ./annotations_forward_refs.py.ClassD) -> None [invalid_annotation] +./annotations_forward_refs.py:111:16: Any[error] is not equivalent to ./annotations_forward_refs.py.ClassD.ClassInner +./annotations_forward_refs.py:112:16: Any[error] is not equivalent to ./annotations_forward_refs.py.ClassD.ClassInner """ diff --git a/conformance/results/pycroscope/annotations_future_import.toml b/conformance/results/pycroscope/annotations_future_import.toml new file mode 100644 index 000000000..7576dc678 --- /dev/null +++ b/conformance/results/pycroscope/annotations_future_import.toml @@ -0,0 +1,27 @@ +conformant = "Partial" +notes = """ +Forward references to nested classes are not resolved +Nested classes are resolved, but its type is Any +Resolves some class annotations to outer names instead of shadowing class bindings +""" +conformance_automated = "Fail" +errors_diff = """ +Line 37: Expected 1 errors +Line 39: Expected 1 errors +Line 26: Unexpected errors ['./annotations_future_import.py:26:12: Undefined name: ClassInner [undefined_name]'] +Line 27: Unexpected errors ['./annotations_future_import.py:27:12: Undefined name: ClassInner [undefined_name]'] +Line 55: Unexpected errors ['./annotations_future_import.py:55:16: Any[inference] is not equivalent to annotations_future_import.ClassB.ClassInner'] +Line 56: Unexpected errors ['./annotations_future_import.py:56:16: Any[inference] is not equivalent to annotations_future_import.ClassB.ClassInner'] +Line 57: Unexpected errors ['./annotations_future_import.py:57:16: Any[inference] is not equivalent to annotations_future_import.ClassB.ClassInner'] +Line 58: Unexpected errors ['./annotations_future_import.py:58:16: Any[inference] is not equivalent to annotations_future_import.ClassB.ClassInner'] +""" +output = """ +./annotations_future_import.py:26:12: Undefined name: ClassInner [undefined_name] +./annotations_future_import.py:27:12: Undefined name: ClassInner [undefined_name] +./annotations_future_import.py:44:7: Invalid type annotation [invalid_annotation] +./annotations_future_import.py:46:7: Invalid type annotation [invalid_annotation] +./annotations_future_import.py:55:16: Any[inference] is not equivalent to annotations_future_import.ClassB.ClassInner +./annotations_future_import.py:56:16: Any[inference] is not equivalent to annotations_future_import.ClassB.ClassInner +./annotations_future_import.py:57:16: Any[inference] is not equivalent to annotations_future_import.ClassB.ClassInner +./annotations_future_import.py:58:16: Any[inference] is not equivalent to annotations_future_import.ClassB.ClassInner +""" diff --git a/conformance/results/pyrefly/annotations_forward_refs.toml b/conformance/results/pyrefly/annotations_forward_refs.toml index 8807cb346..f0d3f84fb 100644 --- a/conformance/results/pyrefly/annotations_forward_refs.toml +++ b/conformance/results/pyrefly/annotations_forward_refs.toml @@ -1,12 +1,14 @@ conformant = "Partial" notes = """ -Types in quotes incorrectly refer to shadowing class member. -Does not reject some type forms that require quotes. +Resolves forward references in type annotations in classes at the point of definition instead of end-of-scope +Fails to resolve a forward reference to a nested class """ conformance_automated = "Fail" errors_diff = """ -Line 87: Unexpected errors ['Expected a type form, got instance of `(self: Self@ClassD) -> None` [not-a-type]'] -Line 96: Unexpected errors ['assert_type(Unknown, int) failed [assert-type]'] +Line 94: Expected 1 errors +Line 96: Expected 1 errors +Line 84: Unexpected errors ['Could not find name `ClassInner` [unknown-name]'] +Line 112: Unexpected errors ['assert_type(Unknown, ClassD.ClassInner) failed [assert-type]'] """ output = """ ERROR annotations_forward_refs.py:23:12-18: `ClassA` is uninitialized [unbound-name] @@ -28,8 +30,11 @@ ERROR annotations_forward_refs.py:52:11-13: Unary operation cannot be used in an ERROR annotations_forward_refs.py:53:11-21: Boolean operation cannot be used in annotations [invalid-annotation] ERROR annotations_forward_refs.py:54:11-17: F-string cannot be used in annotations [invalid-annotation] ERROR annotations_forward_refs.py:55:11-16: Expected a type form, got instance of `Module[types]` [not-a-type] -ERROR annotations_forward_refs.py:80:14-20: Could not find name `ClassF` [unknown-name] -ERROR annotations_forward_refs.py:87:9-12: Expected a type form, got instance of `(self: Self@ClassD) -> None` [not-a-type] -ERROR annotations_forward_refs.py:89:8-11: Expected a type form, got instance of `(self: Self@ClassD) -> None` [not-a-type] -ERROR annotations_forward_refs.py:96:12-27: assert_type(Unknown, int) failed [assert-type] +ERROR annotations_forward_refs.py:83:13-23: Could not find name `ClassInner` [unknown-name] +ERROR annotations_forward_refs.py:84:14-24: Could not find name `ClassInner` [unknown-name] +ERROR annotations_forward_refs.py:92:14-20: Could not find name `ClassF` [unknown-name] +ERROR annotations_forward_refs.py:101:8-11: Expected a type form, got instance of `(self: Self@ClassD) -> None` [not-a-type] +ERROR annotations_forward_refs.py:103:9-12: Expected a type form, got instance of `(self: Self@ClassD) -> None` [not-a-type] +ERROR annotations_forward_refs.py:111:16-45: assert_type(Unknown, ClassD.ClassInner) failed [assert-type] +ERROR annotations_forward_refs.py:112:16-45: assert_type(Unknown, ClassD.ClassInner) failed [assert-type] """ diff --git a/conformance/results/pyrefly/annotations_future_import.toml b/conformance/results/pyrefly/annotations_future_import.toml new file mode 100644 index 000000000..83a8ee5ef --- /dev/null +++ b/conformance/results/pyrefly/annotations_future_import.toml @@ -0,0 +1,22 @@ +conformant = "Partial" +notes = """ +Forward references to nested classes are not resolved +Resolves forward references in type annotations in classes at the point of definition instead of end-of-scope +""" +conformance_automated = "Fail" +errors_diff = """ +Line 37: Expected 1 errors +Line 39: Expected 1 errors +Line 26: Unexpected errors ['Could not find name `ClassInner` [unknown-name]'] +Line 27: Unexpected errors ['Could not find name `ClassInner` [unknown-name]'] +Line 55: Unexpected errors ['assert_type(Unknown, ClassB.ClassInner) failed [assert-type]'] +Line 56: Unexpected errors ['assert_type(Unknown, ClassB.ClassInner) failed [assert-type]'] +""" +output = """ +ERROR annotations_future_import.py:26:13-23: Could not find name `ClassInner` [unknown-name] +ERROR annotations_future_import.py:27:14-24: Could not find name `ClassInner` [unknown-name] +ERROR annotations_future_import.py:44:8-11: Expected a type form, got instance of `(self: Self@ClassB) -> None` [not-a-type] +ERROR annotations_future_import.py:46:9-12: Expected a type form, got instance of `(self: Self@ClassB) -> None` [not-a-type] +ERROR annotations_future_import.py:55:16-45: assert_type(Unknown, ClassB.ClassInner) failed [assert-type] +ERROR annotations_future_import.py:56:16-45: assert_type(Unknown, ClassB.ClassInner) failed [assert-type] +""" diff --git a/conformance/results/pyright/annotations_forward_refs.toml b/conformance/results/pyright/annotations_forward_refs.toml index ca35b5bb1..cb4a1a727 100644 --- a/conformance/results/pyright/annotations_forward_refs.toml +++ b/conformance/results/pyright/annotations_forward_refs.toml @@ -1,4 +1,13 @@ -conformant = "Pass" +conformant = "Partial" +notes = """ +Prefers to resolve forward references in classes outside of the class instead of end-of-scope +""" +conformance_automated = "Fail" +errors_diff = """ +Line 94: Expected 1 errors +Line 96: Expected 1 errors +Line 103: Expected 1 errors +""" output = """ annotations_forward_refs.py:22:7 - error: "ClassA" is not defined (reportUndefinedVariable) annotations_forward_refs.py:23:12 - error: "ClassA" is not defined (reportUndefinedVariable) @@ -29,11 +38,10 @@ annotations_forward_refs.py:52:11 - error: Unary operator not allowed in type ex annotations_forward_refs.py:53:11 - error: Binary operator not allowed in type expression (reportInvalidTypeForm) annotations_forward_refs.py:54:11 - error: Type expressions cannot use format string literals (f-strings) (reportGeneralTypeIssues) annotations_forward_refs.py:55:10 - error: Module cannot be used as a type (reportGeneralTypeIssues) -annotations_forward_refs.py:66:26 - error: "ClassB" is not defined (reportUndefinedVariable) -annotations_forward_refs.py:80:14 - error: Type of "ClassF" could not be determined because it refers to itself (reportGeneralTypeIssues) -annotations_forward_refs.py:80:14 - error: Variable not allowed in type expression (reportInvalidTypeForm) -annotations_forward_refs.py:89:8 - error: Expected class but received "(self: Self@ClassD) -> None" (reportGeneralTypeIssues) -""" -conformance_automated = "Pass" -errors_diff = """ +annotations_forward_refs.py:65:26 - error: "ClassB" is not defined (reportUndefinedVariable) +annotations_forward_refs.py:83:13 - error: "ClassInner" is not defined (reportUndefinedVariable) +annotations_forward_refs.py:92:14 - error: Type of "ClassF" could not be determined because it refers to itself (reportGeneralTypeIssues) +annotations_forward_refs.py:92:14 - error: Variable not allowed in type expression (reportInvalidTypeForm) +annotations_forward_refs.py:101:8 - error: Expected class but received "(self: Self@ClassD) -> None" (reportGeneralTypeIssues) +annotations_forward_refs.py:111:17 - error: "assert_type" mismatch: expected "ClassInner" but received "Unknown" (reportAssertTypeFailure) """ diff --git a/conformance/results/pyright/annotations_future_import.toml b/conformance/results/pyright/annotations_future_import.toml new file mode 100644 index 000000000..baa4287eb --- /dev/null +++ b/conformance/results/pyright/annotations_future_import.toml @@ -0,0 +1,13 @@ +conformant = "Partial" +notes = """ +Prefers to resolve forward references in classes outside of the class instead of end-of-scope +""" +conformance_automated = "Fail" +errors_diff = """ +Line 37: Expected 1 errors +Line 39: Expected 1 errors +Line 44: Expected 1 errors +Line 46: Expected 1 errors +""" +output = """ +""" diff --git a/conformance/results/results.html b/conformance/results/results.html index 08d7cd197..48c3631b7 100644 --- a/conformance/results/results.html +++ b/conformance/results/results.html @@ -294,34 +294,84 @@

Python Type System Conformance Test Results

  • Does not report error for a forward reference that is not enclosed in quotes.
  • Does not report error for use of quoted type with | operator (runtime error).
  • -
  • Incorrectly generates error for quoted type defined in class scope.
  • +
  • Resolves some forward references in type annotations in classes at the point of definition instead of end-of-scope
Partial
  • Fails to reject "x" | int annotations that fail at runtime.
  • +
  • Resolves forward references in type annotations in classes at the point of definition instead of end-of-scope
  • Rejects some valid quoted annotations.
Partial
    -
  • Types in quotes incorrectly refer to shadowing class member.
  • -
  • Does not reject some type forms that require quotes.
  • +
  • Resolves forward references in type annotations in classes at the point of definition instead of end-of-scope
  • +
  • Fails to resolve a forward reference to a nested class
  • +
+ + + Partial +
    +
  • Prefers to resolve forward references in classes outside of the class instead of end-of-scope
  • +
+ + + Partial +
    +
  • Fails to reject str: "str" forward references
  • +
+ + + Partial +
    +
  • Fails to resolve forward references in classes from builtins
  • +
  • Incorrectly resolves type parameter syntax names in forward references
  • +
+ + + + annotations_future_import + + Partial +
    +
  • Resolves some forward references in type annotations in classes at the point of definition instead of end-of-scope
  • +
+ + + Partial +
    +
  • Forward references to nested classes are not resolved
  • +
  • Nested classes are resolved, but its type is Any
  • +
  • Resolves some class annotations to outer names instead of shadowing class bindings
  • +
+ + + Partial +
    +
  • Forward references to nested classes are not resolved
  • +
  • Resolves forward references in type annotations in classes at the point of definition instead of end-of-scope
  • +
+ + + Partial +
    +
  • Prefers to resolve forward references in classes outside of the class instead of end-of-scope
- Pass Partial Partial
    -
  • Incorrectly generates error for quoted type defined in class scope.
  • +
  • Fails to resolve forward references in classes from builtins
  • +
  • Resolves some class annotations to outer names instead of shadowing class bindings
@@ -374,12 +424,12 @@

Python Type System Conformance Test Results

- 4 / 5 • 80.0% - 4 / 5 • 80.0% - 4.5 / 5 • 90.0% - 5 / 5 • 100.0% - 4.5 / 5 • 90.0% - 4.5 / 5 • 90.0% + 4.5 / 6 • 75.0% + 4.5 / 6 • 75.0% + 5 / 6 • 83.3% + 5 / 6 • 83.3% + 5 / 6 • 83.3% + 5 / 6 • 83.3% @@ -2561,12 +2611,12 @@

Python Type System Conformance Test Results

- 108.5 / 145 • 74.8% - 138 / 145 • 95.2% - 140.5 / 145 • 96.9% - 135.5 / 145 • 93.4% - 136 / 145 • 93.8% - 144.5 / 145 • 99.7% + 109 / 146 • 74.7% + 138.5 / 146 • 94.9% + 141 / 146 • 96.6% + 135.5 / 146 • 92.8% + 136.5 / 146 • 93.5% + 145 / 146 • 99.3% diff --git a/conformance/results/ty/annotations_forward_refs.toml b/conformance/results/ty/annotations_forward_refs.toml index d176548df..2760eb69d 100644 --- a/conformance/results/ty/annotations_forward_refs.toml +++ b/conformance/results/ty/annotations_forward_refs.toml @@ -1,12 +1,10 @@ conformance_automated = "Fail" conformant = "Partial" notes = """ -Resolves references in type annotations as referring to end-of-scope types (, ) +Fails to reject `str: "str"` forward references """ errors_diff = """ -Line 87: Unexpected errors ['annotations_forward_refs.py:87:9: error[invalid-type-form] Function `int` is not valid in a type expression'] -Line 95: Unexpected errors ['annotations_forward_refs.py:95:1: error[type-assertion-failure] Type `Divergent` does not match asserted type `str`'] -Line 96: Unexpected errors ['annotations_forward_refs.py:96:1: error[type-assertion-failure] Type `Unknown` does not match asserted type `int`'] +Line 94: Expected 1 errors """ output = """ annotations_forward_refs.py:22:7: error[unresolved-reference] Name `ClassA` used when not defined @@ -28,10 +26,11 @@ annotations_forward_refs.py:52:11: error[invalid-type-form] Unary operations are annotations_forward_refs.py:53:11: error[invalid-type-form] Boolean operations are not allowed in parameter annotations annotations_forward_refs.py:54:11: error[invalid-type-form] F-strings are not allowed in parameter annotations annotations_forward_refs.py:55:11: error[invalid-type-form] Module `types` is not valid in a parameter annotation -annotations_forward_refs.py:66:26: error[unresolved-reference] Name `ClassB` used when not defined -annotations_forward_refs.py:80:14: error[unresolved-reference] Name `ClassF` used when not defined -annotations_forward_refs.py:87:9: error[invalid-type-form] Function `int` is not valid in a type expression -annotations_forward_refs.py:89:8: error[invalid-type-form] Function `int` is not valid in a type expression -annotations_forward_refs.py:95:1: error[type-assertion-failure] Type `Divergent` does not match asserted type `str` -annotations_forward_refs.py:96:1: error[type-assertion-failure] Type `Unknown` does not match asserted type `int` +annotations_forward_refs.py:65:26: error[unresolved-reference] Name `ClassB` used when not defined +annotations_forward_refs.py:83:13: error[unresolved-reference] Name `ClassInner` used when not defined +annotations_forward_refs.py:92:14: error[unresolved-reference] Name `ClassF` used when not defined +annotations_forward_refs.py:96:9: error[invalid-type-form] Function `int` is not valid in a type expression +annotations_forward_refs.py:101:8: error[invalid-type-form] Function `int` is not valid in a type expression +annotations_forward_refs.py:103:9: error[invalid-type-form] Function `int` is not valid in a type expression +annotations_forward_refs.py:111:5: error[type-assertion-failure] Type `Unknown` does not match asserted type `ClassInner` """ diff --git a/conformance/results/ty/annotations_future_import.toml b/conformance/results/ty/annotations_future_import.toml new file mode 100644 index 000000000..9744d55de --- /dev/null +++ b/conformance/results/ty/annotations_future_import.toml @@ -0,0 +1,13 @@ +conformant = "Partial" +notes = """ +Fails to reject `str: "str"` forward references +""" +conformance_automated = "Fail" +errors_diff = """ +Line 37: Expected 1 errors +""" +output = """ +annotations_future_import.py:39:8: error[invalid-type-form] Function `int` is not valid in a type expression +annotations_future_import.py:44:8: error[invalid-type-form] Function `int` is not valid in a type expression +annotations_future_import.py:46:9: error[invalid-type-form] Function `int` is not valid in a type expression +""" diff --git a/conformance/results/zuban/annotations_forward_refs.toml b/conformance/results/zuban/annotations_forward_refs.toml index 9dce37fac..f994ad5b2 100644 --- a/conformance/results/zuban/annotations_forward_refs.toml +++ b/conformance/results/zuban/annotations_forward_refs.toml @@ -1,13 +1,13 @@ conformant = "Partial" notes = """ -Incorrectly generates error for quoted type defined in class scope. +Fails to resolve forward references in classes from builtins +Incorrectly resolves type parameter syntax names in forward references """ conformance_automated = "Fail" errors_diff = """ -Line 82: Unexpected errors ['annotations_forward_refs.py:82: error: Name "str" is not defined [name-defined]'] -Line 87: Unexpected errors ['annotations_forward_refs.py:87: error: Function "tests.annotations_forward_refs.ClassD.int" is not valid as a type [valid-type]'] -Line 95: Unexpected errors ['annotations_forward_refs.py:95: error: Expression is of type "Any", not "str" [misc]'] -Line 96: Unexpected errors ['annotations_forward_refs.py:96: error: Expression is of type "Any", not "int" [misc]'] +Line 82: Unexpected errors ['annotations_forward_refs.py:82: error: Name "bytes" is not defined [name-defined]'] +Line 110: Unexpected errors ['annotations_forward_refs.py:110: error: Expression is of type "Any", not "bytes" [misc]'] +Line 130: Unexpected errors ['annotations_forward_refs.py:130: error: Expression is of type "T", not "int" [misc]', 'annotations_forward_refs.py:130: error: Argument 1 to "identity2" of "ClassE" has incompatible type "int"; expected "T" [arg-type]'] """ output = """ annotations_forward_refs.py:24: error: Forward reference unions cause runtime errors, consider wrapping the whole annotation with a string [misc] @@ -31,12 +31,16 @@ annotations_forward_refs.py:53: error: Invalid type comment or annotation [vali annotations_forward_refs.py:54: error: Invalid type comment or annotation [valid-type] annotations_forward_refs.py:55: error: Module "types" is not valid as a type [valid-type] annotations_forward_refs.py:55: note: Perhaps you meant to use a protocol matching the module structure? -annotations_forward_refs.py:80: error: Name "ClassF" is not defined [name-defined] -annotations_forward_refs.py:82: error: Name "str" is not defined [name-defined] -annotations_forward_refs.py:87: error: Function "tests.annotations_forward_refs.ClassD.int" is not valid as a type [valid-type] -annotations_forward_refs.py:87: note: Perhaps you need "Callable[...]" or a callback protocol? -annotations_forward_refs.py:89: error: Function "tests.annotations_forward_refs.ClassD.int" is not valid as a type [valid-type] -annotations_forward_refs.py:89: note: Perhaps you need "Callable[...]" or a callback protocol? -annotations_forward_refs.py:95: error: Expression is of type "Any", not "str" [misc] -annotations_forward_refs.py:96: error: Expression is of type "Any", not "int" [misc] +annotations_forward_refs.py:82: error: Name "bytes" is not defined [name-defined] +annotations_forward_refs.py:92: error: Name "ClassF" is not defined [name-defined] +annotations_forward_refs.py:94: error: Name "str" is not defined [name-defined] +annotations_forward_refs.py:96: error: Function "tests.annotations_forward_refs.ClassD.int" is not valid as a type [valid-type] +annotations_forward_refs.py:96: note: Perhaps you need "Callable[...]" or a callback protocol? +annotations_forward_refs.py:101: error: Function "tests.annotations_forward_refs.ClassD.int" is not valid as a type [valid-type] +annotations_forward_refs.py:101: note: Perhaps you need "Callable[...]" or a callback protocol? +annotations_forward_refs.py:103: error: Function "tests.annotations_forward_refs.ClassD.int" is not valid as a type [valid-type] +annotations_forward_refs.py:103: note: Perhaps you need "Callable[...]" or a callback protocol? +annotations_forward_refs.py:110: error: Expression is of type "Any", not "bytes" [misc] +annotations_forward_refs.py:130: error: Expression is of type "T", not "int" [misc] +annotations_forward_refs.py:130: error: Argument 1 to "identity2" of "ClassE" has incompatible type "int"; expected "T" [arg-type] """ diff --git a/conformance/results/zuban/annotations_future_import.toml b/conformance/results/zuban/annotations_future_import.toml new file mode 100644 index 000000000..c5cc5c2fc --- /dev/null +++ b/conformance/results/zuban/annotations_future_import.toml @@ -0,0 +1,18 @@ +conformant = "Partial" +notes = """ +Fails to resolve forward references in classes from builtins +Resolves some class annotations to outer names instead of shadowing class bindings +""" +conformance_automated = "Fail" +errors_diff = """ +Line 37: Expected 1 errors +Line 39: Expected 1 errors +Line 28: Unexpected errors ['annotations_future_import.py:28: error: Invalid type comment or annotation [valid-type]'] +""" +output = """ +annotations_future_import.py:28: error: Invalid type comment or annotation [valid-type] +annotations_future_import.py:44: error: Function "tests.annotations_future_import.ClassB.int" is not valid as a type [valid-type] +annotations_future_import.py:44: note: Perhaps you need "Callable[...]" or a callback protocol? +annotations_future_import.py:46: error: Function "tests.annotations_future_import.ClassB.int" is not valid as a type [valid-type] +annotations_future_import.py:46: note: Perhaps you need "Callable[...]" or a callback protocol? +""" diff --git a/conformance/tests/annotations_forward_refs.py b/conformance/tests/annotations_forward_refs.py index 70c644700..a3f69b9d3 100644 --- a/conformance/tests/annotations_forward_refs.py +++ b/conformance/tests/annotations_forward_refs.py @@ -57,10 +57,9 @@ def invalid_annotations( pass -# > It should evaluate without errors once the module has been fully loaded. -# > The local and global namespace in which it is evaluated should be the same -# > namespaces in which default arguments to the same function would be evaluated. - +# > Names within the expression are looked up in the same way as they would be +# > looked up at runtime in Python 3.14 and higher if the annotation was not +# > enclosed in a string literal. class ClassB: def method1(self) -> ClassB: # E?: Runtime error prior to 3.14 @@ -75,25 +74,60 @@ class ClassC: class ClassD: - ClassC: "ClassC" # OK + # OK + simple_attr: ClassA + ClassB: ClassB + ClassC: "ClassC" + bytes_direct: bytes + bytes: "bytes" + inner1: ClassInner # E?: Runtime error prior to 3.14: requires quotes + inner2: "ClassInner" + + class ClassInner: + ... + + inner_after1: ClassInner + inner_after2: "ClassInner" ClassF: "ClassF" # E: circular reference - str: "str" = "" # OK + str: "str" = "" # E: circular reference + + z: "int" = 0 # E: Refers to the local int function, which isn't a valid type def int(self) -> None: # OK ... - x: "int" = 0 # OK + y: int = 0 # E: Refers to the local int function, which isn't a valid type - y: int = 0 # E: Refers to local int, which isn't a legal type expression + x: "int" = 0 # E: Refers to the local int function, which isn't a valid type def __init__(self) -> None: self.ClassC = ClassC() -assert_type(ClassD.str, str) -assert_type(ClassD.x, int) +def check_valid_attributes(d: ClassD) -> None: + assert_type(d.bytes, bytes) + assert_type(d.inner1, ClassD.ClassInner) # E?: Runtime error prior to 3.14: requires quotes + assert_type(d.inner2, ClassD.ClassInner) + assert_type(d.inner_after1, ClassD.ClassInner) + assert_type(d.inner_after2, ClassD.ClassInner) + + +class T: + ... + + +class ClassE[T]: + def identity1(self, value: T) -> T: + return value + + def identity2(self, value: "T") -> "T": + return value + + +assert_type(ClassE[int]().identity1(1), int) +assert_type(ClassE[int]().identity2(1), int) # > If a triple quote is used, the string should be parsed as though it is implicitly diff --git a/conformance/tests/annotations_future_import.py b/conformance/tests/annotations_future_import.py new file mode 100644 index 000000000..778a46925 --- /dev/null +++ b/conformance/tests/annotations_future_import.py @@ -0,0 +1,58 @@ +""" +Tests the handling name-resolution in annotations while from __future__ import +annotations is present. +""" + +from __future__ import annotations + +from typing import assert_type + +# > For purposes of type checking, annotations in a file containing from +# > __future__ import annotations must use the same name-resolution rules as +# > explicitly stringified annotations, regardless of the target Python version. + +forward1: ClassA +forward2: list[ClassA] +forward3: "ClassA" + + +class ClassA: + ... + + +class ClassB: + ClassA: ClassA + ClassC: ClassC + inner1: ClassInner + inner2: "ClassInner" + bytes_direct: bytes + bytes: bytes + + class ClassInner: + ... + + inner_after1: ClassInner + inner_after2: "ClassInner" + + str: str = "" # E: circular reference + + z: int = 0 # E: Refers to the local int function, which isn't a valid type + + def int(self) -> None: # OK + ... + + y: int = 0 # E: Refers to the local int function, which isn't a valid type + + x: "int" = 0 # E: Refers to the local int function, which isn't a valid type + + +class ClassC: + ... + + +def check_valid_attributes(b: ClassB) -> None: + assert_type(b.ClassC, ClassC) + assert_type(b.inner1, ClassB.ClassInner) + assert_type(b.inner2, ClassB.ClassInner) + assert_type(b.inner_after1, ClassB.ClassInner) + assert_type(b.inner_after2, ClassB.ClassInner) diff --git a/docs/spec/annotations.rst b/docs/spec/annotations.rst index 558b9b700..813766bd1 100644 --- a/docs/spec/annotations.rst +++ b/docs/spec/annotations.rst @@ -223,32 +223,14 @@ String annotations When a type hint cannot be evaluated at runtime, that definition may be expressed as a string literal, to be resolved later. -A situation where this occurs commonly is the definition of a -container class, where the class being defined occurs in the signature -of some of the methods. For example, the following code (the start of -a simple binary tree implementation) does not work:: - - class Tree: - def __init__(self, left: Tree, right: Tree): - self.left = left - self.right = right - -To address this, we write:: - - class Tree: - def __init__(self, left: 'Tree', right: 'Tree'): - self.left = left - self.right = right - -The string literal should contain a valid Python expression (i.e., -``compile(lit, '', 'eval')`` should be a valid code object) and it -should evaluate without errors once the module has been fully loaded. -The local and global namespace in which it is evaluated should be the -same namespaces in which default arguments to the same function would -be evaluated. - -Moreover, the expression should be parseable as a valid type hint, i.e., -it is constrained by the rules from :ref:`the expression grammar `. +The string literal should contain a syntactically valid Python expression +(i.e., ``compile(lit, '', 'eval')`` should succeed) that is a valid +:term:`annotation expression`. Regardless of the Python version used, names +within the expression are looked up in the same way as they would be looked up +at runtime in Python 3.14 and higher if the annotation was not enclosed in a +string literal. Thus, name lookup follows general rules (e.g., the current +function, class, or module scope first, and the builtin scope last), but names +defined later within the same scope can be used in an earlier annotation. If a triple quote is used, the string should be parsed as though it is implicitly surrounded by parentheses. This allows newline characters to be @@ -308,6 +290,13 @@ _module_._class_ name:: from models.a import A from models.b import B +from __future__ import annotations +---------------------------------- + +For purposes of type checking, annotations in a file containing ``from +__future__ import annotations`` must use the same name-resolution rules as +explicitly stringified annotations, regardless of the target Python version. + Annotating generator functions and coroutines ---------------------------------------------