Repository: python-attrs/attrs (python)

Issue:
Support ClassVar string annotations
The following doesn't work in Python 3.7

```
from __future__ import annotations
import attr
from typing import ClassVar

@attr.dataclass
class A:
    x: ClassVar[int]

a = A()
```

Because `ClassVar[int]` will be `'ClassVar[int]` in `A.__annotations__` and so `return str(annot).startswith("typing.ClassVar")` returns `False`.

You can repro without 3.7 using this:

```
import attr
from typing import ClassVar

@attr.dataclass
class A:
    x: 'ClassVar[int]'

a = A()
```

Note: It's weird I'm not giving it a value but if I do then attrs will think it's a default value and the code won't error out.

Oh and the following works fine:
```
import attr
import typing

@attr.dataclass
class A:
    x: 'typing.ClassVar[int]'

a = A()
```


Pull request description:
Support `from typing import ClassVar`
Also updated the docstring to reflect why exactly we're doing what we're doing.
The previous comment was incorrect (`typing` is already imported in
applications using annotations).

I added `t.ClassVar` as well which is the only third alternative import I found
in use for typing-related classes.

Fixes #361

Patch:
diff --git a/src/attr/_make.py b/src/attr/_make.py
index 063c7c7..ac3dc31 100644
--- a/src/attr/_make.py
+++ b/src/attr/_make.py
@@ -23,6 +23,8 @@ _obj_setattr = object.__setattr__
 _init_converter_pat = "__attr_converter_{}"
 _init_factory_pat = "__attr_factory_{}"
 _tuple_property_pat = "    {attr_name} = property(itemgetter({index}))"
+_classvar_prefixes = ("typing.ClassVar", "t.ClassVar", "ClassVar")
+
 _empty_metadata_singleton = metadata_proxy({})
 
 
@@ -232,10 +234,11 @@ def _is_class_var(annot):
     """
     Check whether *annot* is a typing.ClassVar.
 
-    The implementation is gross but importing `typing` is slow and there are
-    discussions to remove it from the stdlib alltogether.
+    The string comparison hack is used to avoid evaluating all string
+    annotations which would put attrs-based classes at a performance
+    disadvantage compared to plain old classes.
     """
-    return str(annot).startswith("typing.ClassVar")
+    return str(annot).startswith(_classvar_prefixes)
 
 
 def _get_annotations(cls):


Test patch:
diff --git a/tests/test_annotations.py b/tests/test_annotations.py
index 642d5cf..f56424c 100644
--- a/tests/test_annotations.py
+++ b/tests/test_annotations.py
@@ -11,6 +11,7 @@ import pytest
 
 import attr
 
+from attr._make import _classvar_prefixes
 from attr.exceptions import UnannotatedAttributeError
 
 
@@ -204,13 +205,14 @@ class TestAnnotations:
         assert A.__init__.__annotations__ == {'return': None}
 
     @pytest.mark.parametrize("slots", [True, False])
-    def test_annotations_strings(self, slots):
+    @pytest.mark.parametrize("classvar", _classvar_prefixes)
+    def test_annotations_strings(self, slots, classvar):
         """
         String annotations are passed into __init__ as is.
         """
         @attr.s(auto_attribs=True, slots=slots)
         class C:
-            cls_var: 'typing.ClassVar[int]' = 23
+            cls_var: classvar + '[int]' = 23
             a: 'int'
             x: 'typing.List[int]' = attr.Factory(list)
             y: 'int' = 2