From 97334a2d2f37675088eb4e22f7fa1baceaad2953 Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Mon, 24 Feb 2025 22:18:33 +0100 Subject: [PATCH] Feedback, add conformance test --- .../results/mypy/classes_classvar.toml | 29 +++++++-------- conformance/results/mypy/version.toml | 2 +- .../results/pyre/classes_classvar.toml | 25 ++++++------- conformance/results/pyre/version.toml | 2 +- .../results/pyright/classes_classvar.toml | 16 ++++----- conformance/results/pyright/version.toml | 2 +- .../results/pytype/classes_classvar.toml | 35 ++++++++++--------- conformance/results/pytype/version.toml | 2 +- conformance/results/results.html | 8 ++--- conformance/tests/classes_classvar.py | 10 ++++-- docs/spec/class-compat.rst | 12 ++----- 11 files changed, 71 insertions(+), 72 deletions(-) diff --git a/conformance/results/mypy/classes_classvar.toml b/conformance/results/mypy/classes_classvar.toml index 82c4756b..0d9662ec 100644 --- a/conformance/results/mypy/classes_classvar.toml +++ b/conformance/results/mypy/classes_classvar.toml @@ -4,7 +4,6 @@ Internal error if TypeVarTuple is used in ClassVar. Does not reject use of ParamSpec in ClassVar. Rejects ClassVar nested in Annotated. Does not reject use of ClassVar in TypeAlias definition. -Does not infer type of ClassVar from assignment if no type is provided. """ output = """ classes_classvar.py:38: error: ClassVar[...] must have at most one type argument [valid-type] @@ -15,24 +14,22 @@ classes_classvar.py:46: error: ClassVar cannot contain type variables [misc] classes_classvar.py:52: error: Incompatible types in assignment (expression has type "dict[Never, Never]", variable has type "list[str]") [assignment] classes_classvar.py:54: error: Variable should not be annotated with both ClassVar and Final [misc] classes_classvar.py:55: error: Invalid type: ClassVar nested inside other type [valid-type] -classes_classvar.py:61: error: Invalid type: ClassVar nested inside other type [valid-type] -classes_classvar.py:63: error: ClassVar can only be used for assignments in class body [misc] -classes_classvar.py:64: error: ClassVar can only be used for assignments in class body [misc] -classes_classvar.py:65: error: ClassVar can only be used for assignments in class body [misc] -classes_classvar.py:67: error: ClassVar can only be used for assignments in class body [misc] +classes_classvar.py:67: error: Invalid type: ClassVar nested inside other type [valid-type] +classes_classvar.py:69: error: ClassVar can only be used for assignments in class body [misc] +classes_classvar.py:70: error: ClassVar can only be used for assignments in class body [misc] classes_classvar.py:71: error: ClassVar can only be used for assignments in class body [misc] -classes_classvar.py:78: error: Expression is of type "Any", not "float" [assert-type] -classes_classvar.py:105: error: Cannot assign to class variable "stats" via instance [misc] -classes_classvar.py:134: error: Incompatible types in assignment (expression has type "ProtoAImpl", variable has type "ProtoA") [assignment] -classes_classvar.py:134: note: "ProtoAImpl" is missing following "ProtoA" protocol member: -classes_classvar.py:134: note: z -classes_classvar.py:134: note: Protocol member ProtoA.x expected class variable, got instance variable -classes_classvar.py:134: note: Protocol member ProtoA.y expected class variable, got instance variable +classes_classvar.py:73: error: ClassVar can only be used for assignments in class body [misc] +classes_classvar.py:77: error: ClassVar can only be used for assignments in class body [misc] +classes_classvar.py:111: error: Cannot assign to class variable "stats" via instance [misc] +classes_classvar.py:140: error: Incompatible types in assignment (expression has type "ProtoAImpl", variable has type "ProtoA") [assignment] +classes_classvar.py:140: note: "ProtoAImpl" is missing following "ProtoA" protocol member: +classes_classvar.py:140: note: z +classes_classvar.py:140: note: Protocol member ProtoA.x expected class variable, got instance variable +classes_classvar.py:140: note: Protocol member ProtoA.y expected class variable, got instance variable """ conformance_automated = "Fail" errors_diff = """ Line 47: Expected 1 errors -Line 72: Expected 1 errors -Line 61: Unexpected errors ['classes_classvar.py:61: error: Invalid type: ClassVar nested inside other type [valid-type]'] -Line 78: Unexpected errors ['classes_classvar.py:78: error: Expression is of type "Any", not "float" [assert-type]'] +Line 78: Expected 1 errors +Line 67: Unexpected errors ['classes_classvar.py:67: error: Invalid type: ClassVar nested inside other type [valid-type]'] """ diff --git a/conformance/results/mypy/version.toml b/conformance/results/mypy/version.toml index b3982776..6bd36c63 100644 --- a/conformance/results/mypy/version.toml +++ b/conformance/results/mypy/version.toml @@ -1,2 +1,2 @@ version = "mypy 1.15.0" -test_duration = 1.8 +test_duration = 2.1 diff --git a/conformance/results/pyre/classes_classvar.toml b/conformance/results/pyre/classes_classvar.toml index a44ae1aa..b3302051 100644 --- a/conformance/results/pyre/classes_classvar.toml +++ b/conformance/results/pyre/classes_classvar.toml @@ -3,6 +3,7 @@ notes = """ Does not reject use of TypeVar in ClassVar. Does not reject use of ParamSpec in ClassVar. Does not reject use of ClassVar as a generic type argument. +Does not allow ClassVar to be used without an assigned value. Does not reject use of ClassVar in parameter type annotation. Does not reject use of ClassVar in local variable annotation. Does not reject use of ClassVar in instance variable annotation. @@ -15,11 +16,11 @@ classes_classvar.py:38:10 Invalid type parameters [24]: Generic type `CV` expect classes_classvar.py:39:10 Invalid type [31]: Expression `typing.ClassVar[3]` is not a valid type. classes_classvar.py:40:13 Unbound name [10]: Name `var` is used but not defined in the current scope. classes_classvar.py:52:4 Incompatible attribute type [8]: Attribute `bad8` declared in class `ClassA` has type `List[str]` but is used as type `Dict[Variable[_KT], Variable[_VT]]`. -classes_classvar.py:65:8 Undefined attribute [16]: `ClassA` has no attribute `xx`. -classes_classvar.py:68:8 Incompatible return type [7]: Expected `CV[int]` but got `int`. -classes_classvar.py:78:0 Assert type [70]: Expected `float` but got `typing.Any`. -classes_classvar.py:105:0 Invalid assignment [41]: Assigning to class variable through instance, did you mean to assign to `Starship.stats` instead? -classes_classvar.py:134:0 Incompatible variable type [9]: a is declared to have type `ProtoA` but is used as type `ProtoAImpl`. +classes_classvar.py:66:4 Uninitialized attribute [13]: Attribute `good5` is declared in class `ClassA` to have type `typing.Any` but is never initialized. +classes_classvar.py:71:8 Undefined attribute [16]: `ClassA` has no attribute `xx`. +classes_classvar.py:74:8 Incompatible return type [7]: Expected `CV[int]` but got `int`. +classes_classvar.py:111:0 Invalid assignment [41]: Assigning to class variable through instance, did you mean to assign to `Starship.stats` instead? +classes_classvar.py:140:0 Incompatible variable type [9]: a is declared to have type `ProtoA` but is used as type `ProtoAImpl`. """ conformance_automated = "Fail" errors_diff = """ @@ -28,11 +29,11 @@ Line 46: Expected 1 errors Line 47: Expected 1 errors Line 54: Expected 1 errors Line 55: Expected 1 errors -Line 63: Expected 1 errors -Line 64: Expected 1 errors -Line 67: Expected 1 errors -Line 71: Expected 1 errors -Line 72: Expected 1 errors -Line 68: Unexpected errors ['classes_classvar.py:68:8 Incompatible return type [7]: Expected `CV[int]` but got `int`.'] -Line 78: Unexpected errors ['classes_classvar.py:78:0 Assert type [70]: Expected `float` but got `typing.Any`.'] +Line 69: Expected 1 errors +Line 70: Expected 1 errors +Line 73: Expected 1 errors +Line 77: Expected 1 errors +Line 78: Expected 1 errors +Line 66: Unexpected errors ['classes_classvar.py:66:4 Uninitialized attribute [13]: Attribute `good5` is declared in class `ClassA` to have type `typing.Any` but is never initialized.'] +Line 74: Unexpected errors ['classes_classvar.py:74:8 Incompatible return type [7]: Expected `CV[int]` but got `int`.'] """ diff --git a/conformance/results/pyre/version.toml b/conformance/results/pyre/version.toml index 028806c2..c60d2044 100644 --- a/conformance/results/pyre/version.toml +++ b/conformance/results/pyre/version.toml @@ -1,2 +1,2 @@ version = "pyre 0.9.23" -test_duration = 6.5 +test_duration = 3.9 diff --git a/conformance/results/pyright/classes_classvar.toml b/conformance/results/pyright/classes_classvar.toml index 42c7a249..1b03b11a 100644 --- a/conformance/results/pyright/classes_classvar.toml +++ b/conformance/results/pyright/classes_classvar.toml @@ -9,15 +9,15 @@ classes_classvar.py:47:20 - error: "ClassVar" type cannot include type variables classes_classvar.py:52:33 - error: Type "dict[Any, Any]" is not assignable to declared type "list[str]" (reportAssignmentType) classes_classvar.py:54:17 - error: "ClassVar" is not allowed in this context (reportInvalidTypeForm) classes_classvar.py:55:17 - error: "ClassVar" is not allowed in this context (reportInvalidTypeForm) -classes_classvar.py:63:26 - error: "ClassVar" is not allowed in this context (reportInvalidTypeForm) -classes_classvar.py:64:12 - error: "ClassVar" is not allowed in this context (reportInvalidTypeForm) -classes_classvar.py:65:18 - error: "ClassVar" is not allowed in this context (reportInvalidTypeForm) -classes_classvar.py:67:26 - error: "ClassVar" is not allowed in this context (reportInvalidTypeForm) -classes_classvar.py:71:8 - error: "ClassVar" is not allowed in this context (reportInvalidTypeForm) -classes_classvar.py:72:20 - error: "ClassVar" is not allowed in this context (reportInvalidTypeForm) -classes_classvar.py:105:14 - error: Cannot assign to attribute "stats" for class "Starship" +classes_classvar.py:69:26 - error: "ClassVar" is not allowed in this context (reportInvalidTypeForm) +classes_classvar.py:70:12 - error: "ClassVar" is not allowed in this context (reportInvalidTypeForm) +classes_classvar.py:71:18 - error: "ClassVar" is not allowed in this context (reportInvalidTypeForm) +classes_classvar.py:73:26 - error: "ClassVar" is not allowed in this context (reportInvalidTypeForm) +classes_classvar.py:77:8 - error: "ClassVar" is not allowed in this context (reportInvalidTypeForm) +classes_classvar.py:78:20 - error: "ClassVar" is not allowed in this context (reportInvalidTypeForm) +classes_classvar.py:111:14 - error: Cannot assign to attribute "stats" for class "Starship"   Attribute "stats" cannot be assigned through a class instance because it is a ClassVar (reportAttributeAccessIssue) -classes_classvar.py:134:13 - error: Type "ProtoAImpl" is not assignable to declared type "ProtoA" +classes_classvar.py:140:13 - error: Type "ProtoAImpl" is not assignable to declared type "ProtoA"   "ProtoAImpl" is incompatible with protocol "ProtoA"     "x" is defined as a ClassVar in protocol     "y" is defined as a ClassVar in protocol (reportAssignmentType) diff --git a/conformance/results/pyright/version.toml b/conformance/results/pyright/version.toml index bf9b4ead..ff8152cf 100644 --- a/conformance/results/pyright/version.toml +++ b/conformance/results/pyright/version.toml @@ -1,2 +1,2 @@ version = "pyright 1.1.394" -test_duration = 1.3 +test_duration = 2.3 diff --git a/conformance/results/pytype/classes_classvar.toml b/conformance/results/pytype/classes_classvar.toml index d042ebf8..3c88e5ef 100644 --- a/conformance/results/pytype/classes_classvar.toml +++ b/conformance/results/pytype/classes_classvar.toml @@ -4,6 +4,7 @@ Does not reject use of TypeVar in ClassVar. Does not reject use of ParamSpec in ClassVar. Does not reject use of ClassVar as a generic type argument. Rejects initialization of ClassVar if no type argument is provided. +Does not infer ClassVar with no type argument and no assigned value as Any. Does not reject use of ClassVar in parameter type annotation. Does not reject use of ClassVar in local variable annotation. Does not reject use of ClassVar in instance variable annotation. @@ -85,27 +86,27 @@ classes_classvar.py:52:5: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in ClassA bad8: ClassVar[list[str]] = {} # E: type violation in initialization \u001b[1m\u001b[31m~~~~\u001b[39m\u001b[0m -classes_classvar.py:60:5: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in ClassA: Type annotation for good4 does not match type of assignment [annotation-type-mismatch] +classes_classvar.py:63:5: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in ClassA: Type annotation for good4 does not match type of assignment [annotation-type-mismatch] good4: ClassVar = 3.1 \u001b[1m\u001b[31m~~~~~\u001b[39m\u001b[0m -classes_classvar.py:68:16: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in method2: bad return type [bad-return-type] +classes_classvar.py:74:16: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in method2: bad return type [bad-return-type] return 3 \u001b[1m\u001b[31m~\u001b[39m\u001b[0m -classes_classvar.py:78:1: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in : ClassVar [assert-type] +classes_classvar.py:84:1: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in : ClassVar [assert-type] -assert_type(ClassA.good4, float) -\u001b[1m\u001b[31m~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\u001b[39m\u001b[0m +assert_type(ClassA.good5, Any) +\u001b[1m\u001b[31m~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\u001b[39m\u001b[0m -classes_classvar.py:124:5: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in ProtoA: Type annotation for z does not match type of assignment [annotation-type-mismatch] +classes_classvar.py:130:5: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in ProtoA: Type annotation for z does not match type of assignment [annotation-type-mismatch] z: CV = [""] \u001b[1m\u001b[31m~\u001b[39m\u001b[0m -classes_classvar.py:134:1: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in : Type annotation for a does not match type of assignment [annotation-type-mismatch] +classes_classvar.py:140:1: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in : Type annotation for a does not match type of assignment [annotation-type-mismatch] a: ProtoA = ProtoAImpl() # E: y is not a ClassVar \u001b[1m\u001b[31m~\u001b[39m\u001b[0m @@ -118,17 +119,17 @@ Line 46: Expected 1 errors Line 47: Expected 1 errors Line 54: Expected 1 errors Line 55: Expected 1 errors -Line 63: Expected 1 errors -Line 64: Expected 1 errors -Line 65: Expected 1 errors -Line 67: Expected 1 errors +Line 69: Expected 1 errors +Line 70: Expected 1 errors Line 71: Expected 1 errors -Line 72: Expected 1 errors -Line 105: Expected 1 errors +Line 73: Expected 1 errors +Line 77: Expected 1 errors +Line 78: Expected 1 errors +Line 111: Expected 1 errors Line 7: Unexpected errors ['classes_classvar.py:7:1: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in : typing.TypeVarTuple not supported yet [not-supported-yet]'] Line 29: Unexpected errors ['classes_classvar.py:29:6: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in : Function TypeVarTuple.__init__ expects 1 arg(s), got 2 [wrong-arg-count]'] -Line 60: Unexpected errors ['classes_classvar.py:60:5: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in ClassA: Type annotation for good4 does not match type of assignment [annotation-type-mismatch]'] -Line 68: Unexpected errors ['classes_classvar.py:68:16: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in method2: bad return type [bad-return-type]'] -Line 78: Unexpected errors ['classes_classvar.py:78:1: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in : ClassVar [assert-type]'] -Line 124: Unexpected errors ['classes_classvar.py:124:5: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in ProtoA: Type annotation for z does not match type of assignment [annotation-type-mismatch]'] +Line 63: Unexpected errors ['classes_classvar.py:63:5: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in ClassA: Type annotation for good4 does not match type of assignment [annotation-type-mismatch]'] +Line 74: Unexpected errors ['classes_classvar.py:74:16: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in method2: bad return type [bad-return-type]'] +Line 84: Unexpected errors ['classes_classvar.py:84:1: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in : ClassVar [assert-type]'] +Line 130: Unexpected errors ['classes_classvar.py:130:5: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in ProtoA: Type annotation for z does not match type of assignment [annotation-type-mismatch]'] """ diff --git a/conformance/results/pytype/version.toml b/conformance/results/pytype/version.toml index 10c237d6..cdc0a953 100644 --- a/conformance/results/pytype/version.toml +++ b/conformance/results/pytype/version.toml @@ -1,2 +1,2 @@ version = "pytype 2024.10.11" -test_duration = 34.9 +test_duration = 43.3 diff --git a/conformance/results/results.html b/conformance/results/results.html index ba1f3d8e..0435c2ad 100644 --- a/conformance/results/results.html +++ b/conformance/results/results.html @@ -159,16 +159,16 @@

Python Type System Conformance Test Results

 
mypy 1.15.0
-
1.8sec
+
2.1sec
pyright 1.1.394
-
1.3sec
+
2.3sec
pyre 0.9.23
-
6.5sec
+
3.9sec
pytype 2024.10.11
-
34.9sec
+
43.3sec
diff --git a/conformance/tests/classes_classvar.py b/conformance/tests/classes_classvar.py index 5e0c963b..dca180af 100644 --- a/conformance/tests/classes_classvar.py +++ b/conformance/tests/classes_classvar.py @@ -57,8 +57,14 @@ class ClassA(Generic[T, P]): good1: CV[int] = 1 good2: ClassVar[list[str]] = [] good3: ClassVar[Any] = 1 + # > If an assigned value is available, the type should be inferred as some type + # > to which this value is assignable. + # Here, type checkers could infer good4 as `float` or `Any`, for example. good4: ClassVar = 3.1 - good5: Annotated[ClassVar[list[int]], ""] = [] + # > If the `ClassVar` qualifier is used without any assigned value, the type + # > should be inferred as `Any`: + good5: ClassVar + good6: Annotated[ClassVar[list[int]], ""] = [] def method1(self, a: ClassVar[int]): # E: ClassVar not allowed here x: ClassVar[str] = "" # E: ClassVar not allowed here @@ -75,7 +81,7 @@ def method2(self) -> ClassVar[int]: # E: ClassVar not allowed here assert_type(ClassA.good1, int) assert_type(ClassA.good2, list[str]) assert_type(ClassA.good3, Any) -assert_type(ClassA.good4, float) +assert_type(ClassA.good5, Any) class BasicStarship: diff --git a/docs/spec/class-compat.rst b/docs/spec/class-compat.rst index 3744970a..f898d6a9 100644 --- a/docs/spec/class-compat.rst +++ b/docs/spec/class-compat.rst @@ -14,9 +14,6 @@ The :py:data:`typing.ClassVar` :term:`type qualifier` is used to annotate class variables that should not be set on class instances. This restriction is enforced by static checkers, but not at runtime. -Syntax -^^^^^^ - :py:data:`~typing.ClassVar` may be used in one of several forms: * With an explicit type, using the syntax ``ClassVar[]``. Example:: @@ -31,14 +28,11 @@ Syntax z: ClassVar If an assigned value is available (e.g. with ``y``), the type should be - inferred as some type to which this value is :term:`assignable` (in this - case, either ``int``, ``Literal[2]``, or ``Any``). + inferred as some type to which this value is :term:`assignable` (for example, + ``int``, ``Literal[2]``, or ``Any``). If the ``ClassVar`` qualifier is used without any assigned value, the type - should be inferred to an unknown static type (such as :ref:`Any`). - -Semantics and examples -^^^^^^^^^^^^^^^^^^^^^^ + should be inferred as :ref:`any`. Type annotations can be used to annotate class and instance variables in class bodies and methods. In particular, the value-less notation ``a: int``