Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Implement ClassVar semantics (fixes #2771) #2873

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Mar 9, 2017
Merged

Conversation

miedzinski
Copy link
Contributor

@miedzinski miedzinski commented Feb 15, 2017

Support ClassVar annotations

Implements ClassVar introduced in PEP 526. Resolves #2771, #2879.

  • Support annotating class attributes with ClassVar[...].
  • Prohibit ClassVar annotations outside class bodies (regular variable annotations, function and method signatures).
  • Prohibit assignments to class variables accessed from instances (including assignments on self).
  • Add checks for overriding class variables with instance variables and vice versa when subclassing.
  • Prohibit ClassVars nested inside other types.
  • Add is_classvar flag to Var.
  • Add nesting_level attribute to TypeAnalyser and use it inside helper methods anal_type and anal_array. Move analyzing implicit TupleTypes from SemanticAnalyzer to TypeAnalyser.
  • Analyze ClassVar[T] as T and bare ClassVar as Any.
  • Prohibit generic ClassVars and accessing generic instance variables via class (see Generic class atributes aren't fully instantiated #2878 comments). Add types.get_type_vars helper, which returns all type variables present in analyzed type, similar to TypeAnalyser.get_type_var_names.

@@ -2218,6 +2218,8 @@ def bool_type(self) -> Instance:
return self.named_type('builtins.bool')

def narrow_type_from_binder(self, expr: Expression, known_type: Type) -> Type:
if isinstance(known_type, ClassVarType):
known_type = known_type.item
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This indeed looks strange, and probably would be not necessary if class variables would be implemented as flags for types or Var nodes.

mypy/subtypes.py Outdated
@@ -52,6 +53,9 @@ def is_subtype(left: Type, right: Type,
return any(is_subtype(left, item, type_parameter_checker,
ignore_pos_arg_names=ignore_pos_arg_names)
for item in right.items)
elif isinstance(right, ClassVarType):
return is_subtype(left, right.item, type_parameter_checker,
ignore_pos_arg_names=ignore_pos_arg_names)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think ClassVar represents actually a new type, so that it should not appear here, otherwise you would need to implement also meet_types, join_types, etc. ClassVar is rather an "access modifier" for a class member.

mypy/checker.py Outdated
@@ -1575,6 +1575,9 @@ def check_member_assignment(self, instance_type: Type, attribute_type: Type,

Return the inferred rvalue_type and whether to infer anything about the attribute type
"""
if isinstance(instance_type, Instance) and isinstance(attribute_type, ClassVarType):
self.msg.fail("Illegal assignment to class variable", context)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this code should rather appear in analyze_member_access (there is also a special flag is_lvalue to indicate that an assignment to a member is being analysed rather that reading from it).

mypy/typeanal.py Outdated
@@ -148,6 +148,12 @@ def visit_unbound_type(self, t: UnboundType) -> Type:
items = self.anal_array(t.args)
item = items[0]
return TypeType(item, line=t.line)
elif fullname == 'typing.ClassVar':
if len(t.args) != 1:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a bare ClassVar should be also allowed meaning ClassVar[Any]

mypy/types.py Outdated
@@ -1189,6 +1189,29 @@ def deserialize(cls, data: JsonDict) -> 'TypeType':
return TypeType(Type.deserialize(data['item']))


class ClassVarType(Type):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I already mentioned, I think this should be rather a flag in Type base class or in the Var symbol node (maybe is_classvar flag in some analogy to is_classmethod).

y = A.x
reveal_type(y)
[out]
main:5: error: Revealed type is 'builtins.int'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think more tests are needed here, some ideas:

  • More tests on accessing class vars (through instances, subclasses, and instances of subclasses)
  • Class vars with complex types: user defined types, generic collections
  • Test behaviour of class vars with type variables in user defined generic classes
  • Check correct access and inference for the above point
  • Check more overriding options: overriding by an instance variable in subclass, and by assigning to an instance of subclass (I believe both should be errors)
  • Test behaviour on attempts to define and/or override class vars inside method bodies.
  • Maybe multiple inheritance
  • Add also few tests that combine more than one of the above ideas

@ilevkivskyi
Copy link
Member

Thank you for working on this! Here are some comments. I think the two major are:

  • more tests are needed
  • class variables should be implemented as flags (access modifiers for members) rather than a new kind of types.

@miedzinski
Copy link
Contributor Author

Indeed, is_classvar in Var was my first attempt at it and I can agree it makes more sense. Unfortunately, I couldn't work out where the semantic analysis should be done - semanal.py or checker.py, or somewhere else? I know marking Vars with proper flag should be done somewhere around visit_assignment_stmt in SemanticAnalyzer (that's when mypy first sees class atributtes, right?), but I can't feel where it's best to do actual checks.

@ilevkivskyi
Copy link
Member

ilevkivskyi commented Feb 16, 2017

I couldn't work out where the semantic analysis should be done - semanal.py or checker.py, or somewhere else?

It should be done in semanal.py, checker.py is already the type checking stage. Most probably in visit_assignment_stmt after lvalue is analysed (so that corresponding Var node is already created and set in appropriate symbol table).

There are also some other places you should pay attention, in particular analyze_member_acces in checkmember.py that I mentioned in review.

EDIT: corrected file name

@miedzinski
Copy link
Contributor Author

miedzinski commented Feb 16, 2017 via email

@elazarg
Copy link
Contributor

elazarg commented Feb 16, 2017

Theoretically, ClassVar in a class A should be a field in the metaclass of A (which is never the declared metaclass but a "ghost" metaclass created for A which inherits from the declared/analyzed metaclass).
This way there is no special treatment after the semantic analysis.

Similarly this should be the case for @classmethod, except the runtime behavior of @classmethod is somewhat incompatible since it is not implemented this way in CPython.

In other words, if we'll get Type[T] for classes T refer to dedicated TypeInfo for the ghost metaclass, this feature should (hopefully) be trivial to implement: add a variable to the TypeInfo.

@miedzinski
Copy link
Contributor Author

@ilevkivskyi, removed ClassVarType, introduced Var.is_classvar and added more tests. I hope it's better now.

@miedzinski
Copy link
Contributor Author

What's not done (and I'd rather postpone to another PR):

  • assignments with multiple lvalues, like
class A:
    x, y = 1, 2  # type: ClassVar[int], ClassVar[int]
    [a, b, *c] = ['a', 'b']  # type: List[ClassVar]
  • disallowing ClassVars in function/method signatures

I think basic cases of ClassVars in signatures could be easily checked in SemanticAnalyzer.analyze_function, right before call to anal_type on function type, but with signatures like def f(x: Callable[[ClassVar], int]) -> None (with ClassVar hidden inside another type) it is less trivial.

Copy link
Member

@ilevkivskyi ilevkivskyi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This looks better, I still have some comments. In addition, there are three important things that should be done in this PR:

  • Prohibit definition of class vars inside methods, i.e. x: ClassVar[int] should fail inside methods (probably using .is_func_scope())
  • Prohibit class vars in function signatures (as you mentioned)
  • In general ClassVar should be allowed only as an "outermost" type for simple assignments, things like Union[ClassVar[T], int] are invalid (and will actually fail at runtime). This probably should be done in typeanal.py

Everything else probably could be done later in a separate PR

@@ -268,6 +268,8 @@ def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Cont
if is_lvalue and var.is_property and not var.is_settable_property:
# TODO allow setting attributes in subclass (although it is probably an error)
msg.read_only_property(name, info, node)
if is_lvalue and var.is_classvar:
msg.cant_assign_to_classvar(node)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks surprisingly simple, but maybe more corner cases will be necessary for more tests, see below

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does, but I think it works. There are tests with subclasses, callables, Type type and everything passes.

mypy/semanal.py Outdated
return False
if self.is_class_scope() or not isinstance(lvalue.node, Var):
node = cast(Var, lvalue.node)
node.is_classvar = True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks wrong to me: in this branch lvalue.node is not an instance of Var, why do you set the .is_classvar flag on something that could be not a Var?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, that was wrong. I had if statements written a bit different at first and didn't fully apply De Morgan's law there. And because mypy was complaining there (and I didn't see anything wrong in it...), I've added cast.

mypy/semanal.py Outdated
return False

def check_classvar_override(self, node: Var, is_classvar: bool) -> None:
name = node.name()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be deferred to a later stage and checked in checker.py (probably in visit_assignment_stmt)

mypy/typeanal.py Outdated
if len(t.args) == 0:
return AnyType(line=t.line)
if len(t.args) != 1:
self.fail('ClassVar[...] must have exactly one type argument', t)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message could be misleading, change it to "... at most one type argument"

from typing import ClassVar
class A:
x = 1 # type: ClassVar[int]
A().x
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use reveal_type here to show that the type is correctly read (i.e. int in this case).

pass
class B:
x = A() # type: ClassVar[A]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add tests where type inside ClassVar is more complex, like List[int], Union[int, str] etc. And check:

class C:
    x: ClassVar[List[int]]

C()[0] = 1  # This should probably succeed
C()[0] = 's'  # This fails

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have resorted to using list.append, because indexed assignment didn't work in unit tests (even without ClassVar).

x = None # type: ClassVar[A]
C.x = B()
[out]
main:8: error: Incompatible types in assignment (expression has type "B", variable has type "A")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check that this succeeds if B is a subclass of A, also check that you can override one classvar with another compatible in a subclass,:

class A:
    pass
class B(A):
    pass
class C:
    x = None  # type: ClassVar[A]
class D(C):
    x = None  # type: ClassVar[B] # This should be allowed

x = 1 # type: int
class B(A):
x = 2 # type: ClassVar[int]
[out]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test looks identical to the previous one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. :) I've meant to test the "opposite" case, where normal attribute is overridden with ClassVar.

class A:
x = None # type: ClassVar[T]
[out]
main:4: error: Invalid type "__main__.T"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests involving generic classes are still missing, for example like these:

T = TypeVar('T')
class A(Generic[T]):
    x = None  # type: ClassVar[T]

reveal_type(A[int].x) # this should be int

class B(Generic[T]):
    x = None  # type: ClassVar[List[T]]

B[int].x[0] = 'abc'  # This should fail

etc.

@miedzinski
Copy link
Contributor Author

Again, added more tests. :) I will move overriding checks to type checker and test cases for ClassVar in function signatures later.

Prohibit definition of class vars inside methods, i.e. x: ClassVar[int] should fail inside methods (probably using .is_func_scope())

This is already done; ClassVar can be defined only in class scope. There are tests for ClassVars in module scope, class scope (class attributes), function scope and in methods. But I've found it is possible to define instance variable as ClassVar when accessing self, like

class A:
    def __init__(self) -> None:
        self.x = None  # type: ClassVar

I'll fix it and add test case for this.

Prohibit class vars in function signatures (as you mentioned)

Could you guide me a bit with this then? Like I said, this could be done in SemanticAnalyzer.analyze_function, before call to anal_type (after that, information about ClassVars is lost). However, I can't see how can I detect ClassVar in more complex types like Callable[[ClassVar], None] or Union[int, List[ClassVar[str]] without reinventing the wheel - I mean there must be some helpers already defined for cases like this, right? The problem is that at this point most types are not yet analyzed (UnboundType).

In general ClassVar should be allowed only as an "outermost" type for simple assignments, things like Union[ClassVar[T], int] are invalid (and will actually fail at runtime). This probably should be done in typeanal.py

I can totally agree about Union[ClassVar[T], int], but

class A:
    x, y = 1, 2  # type: ClassVar[int], ClassVar[int]
    [a, b, *c] = ['a', 'b']  # type: List[ClassVar]

is valid Python and produces A.x: int, A.y: int, A.a: Any, A.b: Any, A.c: List. I know this code is weird, but theoretically one could write something like this and expect all these attributes to be ClassVars. Moreover, there is nothing in PEP 526 that forbids cases like this; I think it should work.

class A:
    x: Union[ClassVar[int], ClassVar[str]]

could also work, but of course we should recommend writing x: ClassVar[Union[int, str]].

x = 1 # type: ClassVar[int]
class B(A):
x = 2 # type: int
[out]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now this tests repeats testOverrideWithNormalAttribute

@ilevkivskyi
Copy link
Member

Thank you for working on this! Here is some response to your questions:

Could you guide me a bit with this then? Like I said, this could be done in SemanticAnalyzer.analyze_function, before call to anal_type (after that, information about ClassVars is lost). However, I can't see how can I detect ClassVar in more complex types like Callable[[ClassVar], None] or Union[int, List[ClassVar[str]] without reinventing the wheel

You don't need to, if you will only allow ClassVar as the outermost "type", then you could check only for the name of unbound type.


I can totally agree about Union[ClassVar[T], int], but

class A:
    x, y = 1, 2  # type: ClassVar[int], ClassVar[int]
    [a, b, *c] = ['a', 'b']  # type: List[ClassVar]

I propose not to allow the syntax in last line now. We don't even know yet how people will use the ClassVar, and whether the above feature will be needed. If someone will ask for this and when it will be added, then we could easily add an exception allowing List (and/or Tuple) one level outside ClassVar.


class A:
    x: Union[ClassVar[int], ClassVar[str]]

could also work, but of course we should recommend writing x: ClassVar[Union[int, str]].

I think this should not be allowed. First of all, "there should be only one way to do it" :-), second, we should not allow something that may make people think ClassVar is a proper type. We should emphasize that it is just an "access modifier".


I will move overriding checks to type checker and test cases for ClassVar in function signatures later.

I am looking forward for this and for fix for self.x. I will make then another (hopefully last) round of review.

[out]
main:5: error: Revealed type is 'builtins.list[builtins.int*]'
main:6: error: Argument 1 to "append" of "list" has incompatible type "str"; expected "T"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm... This error message looks suspicious, it should say "int" instead of "T".
I checked, this is the same without ClassVar, but since we are introducing this feature (class variables), it would be nice to fix this. I guess it is something to do with add_class_tvars in checkmember.py only considering Callable and Overloaded (presumably for @classmethods), but now that we are going to have arbitrary class variables, we should also consider other options (Instance, UnionType, TypeType, etc.) I think this is important because it is something to do with the "core" of new feature.

And could you please add a test with correct assignment, i.e. A[int].x.append(42).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@miedzinski Jukka proposes, and I agree with him, that generic class variables should be prohibited. So that things like x = None # type: ClassVar[List[T]] should be flagged as errors. It is quite easy to check, there is a function in typeanal.py, get_type_var_names that returns all type variables in an analyzed type (even deeply nested ones). You should make an error if this function returns a non-empty list for something wrapped in ClassVar

@ilevkivskyi
Copy link
Member

Sorry, one last thing, could you please also add tests involving import between modules (accessing class var on a class defined in another module), also in check-incremental.test to test the correct serialization of Var nodes with is_classvar == True.

@miedzinski
Copy link
Contributor Author

I've made TypeAnalyser aware of it's nesting level, which is (at least for now) only used for finding invalid ClassVar usages. I hope it resolves the issue with ClassVars inside other types or in function signatures. Additionally, I've simplified SemanticAnalyzer.anal_type a bit.

TODO:

  • issue with self
  • tests for classes defined in another module
  • tests for Var.is_classvar serialization

Regarding generics: I can do this, but in another PR. It's not directly related to ClassVar.

T = TypeVar('T')
class A(Generic[T]):
    x = None  # type: List[T]

A[int].x.append(0)

Right now this fails with error: Argument 1 to "append" of "list" has incompatible type "int"; expected "T" - there is definitely something wrong with generics. Adding A[int].x.append(0) statement to ClassVar tests will make them fail, but it's not because of ClassVar. I've opened new issue - #2878.

@miedzinski
Copy link
Contributor Author

@ilevkivskyi, done.

Copy link
Member

@ilevkivskyi ilevkivskyi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, thank you, this is almost ready! Just few more minor comments.

self.builtin_type('builtins.tuple'), t.line)
items = [self.anal_type(item, True)
for item in t.items]
return TupleType(items, self.builtin_type('builtins.tuple'), t.line)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change really necessary? I am asking for two reasons:

  • This looks unrelated to class variables
  • The transformation you made is actually not equivalent, this code returns more precise type on failure (TupleType with corresponding number of Anys as items, instead of just plain AnyType() in typeanal.py code)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad. I've restored old behaviour, although in TypeAnalyser. This is needed if we want to fail on

class A:
    x, y = None, None  # type: ClassVar, ClassVar

Since I've removed the test for this we could allow this, but these variables wouldn't get is_classvar flag. I would keep it as is.

mypy/semanal.py Outdated
@@ -2159,6 +2148,29 @@ def build_typeddict_typeinfo(self, name: str, items: List[str],

return info

def check_classvar(self, s: AssignmentStmt) -> None:
lvalue = s.lvalues[0]
if len(s.lvalues) != 1 or not isinstance(lvalue, (NameExpr, MemberExpr)):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just use RefExpr instead of (NameExpr, MemberExpr)

mypy/typeanal.py Outdated
if len(t.args) != 1:
self.fail('ClassVar[...] must have at most one type argument', t)
return AnyType()
items = self.anal_array(t.args)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be cleaner if you just return self.anal_nested(t.args[0])

x = 1 # type: ClassVar[int]
A().x = 2
[out]
main:4: error: Illegal assignment to class variable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You use too general error messages that might be not very helpful for a user trying to figure out what is wrong. You could use: 'Can not assign to class variable "x" via instance'.

class B(A):
x = 2 # type: int
[out]
main:5: error: Invalid class attribute definition (previously declared on base class "A")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error message is also too generic. I would propose two separate messages:

  • 'Cannot override class variable (previously declared on base class "A") with instance variable'
  • 'Cannot override instance variable (previously declared on base class "A") with class variable'

class A:
x, y = None, None # type: ClassVar, ClassVar
[out]
main:3: error: Invalid ClassVar definition
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although this is not allowed now, I don't think we need to explicitly test this, maybe we will allow this in the future.

from typing import ClassVar
def f(x: str, y: ClassVar) -> None: pass
[out]
main:2: error: Invalid ClassVar definition
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to see one more test with a method, like this

class C:
    def meth(x: ClassVar[int]) -> None: ... # Error

also we need to prohibit ClassVar in for and with statements inside class, like this:

class D:
    for i in range(10): # type: ClassVar[int] # Error here

class B:
x = None # type: A[ClassVar]
[out]
main:5: error: Invalid ClassVar definition
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to also have two separate error messages instead of "Invalid ClassVar definition":

  • "ClassVar could be only used for assignments in class body"
  • "Invalid type: ClassVar nested inside other type"

@miedzinski
Copy link
Contributor Author

@ilevkivskyi, done. Thanks for suggesting new error messages - I wasn't especially proud of them, but it definitely isn't my strong side. :)

from typing import ClassVar
def f() -> ClassVar: pass
[out]
main:2: error: Invalid type: ClassVar nested inside other type
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have one last comment, I would really prefer to see the other error "ClassVar can only be used for assignments in class body" in these three tests. I understand why it behaves like this, but maybe you could play a bit with visit_func_def and typeanal.py to tweak this?

@ilevkivskyi
Copy link
Member

@miedzinski Also I would recommend you to update the PR description and add there a brief summary of what you implement here, and how you do this (the current description is too much outdated).

@miedzinski
Copy link
Contributor Author

@ilevkivskyi, done. In case of ClassVars inside function signatures, but hidden in other types mypy will still report error: Invalid type: ClassVar nested inside other type. I've decided not to bother; I don't think it's confusing at all.

@ilevkivskyi
Copy link
Member

In case of ClassVars inside function signatures, but hidden in other types mypy will still report error: Invalid type: ClassVar nested inside other type

Thanks! This is exactly what I wanted. The PR now LGTM.

@miedzinski
Copy link
Contributor Author

@ilevkivskyi, done. I have added another helper - TypeAnalyser.get_type_vars - because the method you've mentioned seems to be suited for type aliases. They are pretty similar though.

I have updated the PR description. I suppose this is not enough to close #2878, right?

@ilevkivskyi
Copy link
Member

I suppose this is not enough to close #2878, right?

I think you could follow this idea #2878 (comment) and use your function get_type_vars to prohibit access via class to instance variables with types that contain type variables. This together with recent typing updates will fix #2878 I think.

@miedzinski
Copy link
Contributor Author

@ilevkivskyi, done. PR updated so it will close #2878. I hope it's OK.

@ilevkivskyi
Copy link
Member

It looks good to me.
But as I understand @JukkaL decided to make the release today, so that this PR (and many others) will go to the next cycle.

[out2]
main:4: error: Cannot assign to class variable "x" via instance

[case testIncrementalClassVarGone]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add another incremental test that caches a class with a ClassVar (this module is not changed)? Another file then introduces in the .next file an assignment to the class variable via instance, which should be flagged.

@JukkaL
Copy link
Collaborator

JukkaL commented Mar 8, 2017

I checked that this doesn't conflict with Dropbox code bases.

I only did a quick pass but this looks good. Thanks for writing such extensive tests! I had one idea for an additional test. Additionally, there a few simple merge conflicts. This should be almost ready to merge, unless @gvanrossum has some comments.

@JukkaL JukkaL merged commit fb0917d into python:master Mar 9, 2017
@JukkaL
Copy link
Collaborator

JukkaL commented Mar 9, 2017

🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants