-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
For class variables, lookup type in base classes (#1338, #2022, #2211) #2380
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
Changes from all commits
5547c84
dd2df98
d7373ab
450c12e
1744b9f
150361a
3871a23
01224ad
8a14697
08cd704
afab148
8ab3463
e20372e
007b303
479430e
ac971f4
8c0138a
3518c94
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from typing import Union | ||
|
||
from mypy.nodes import TypeInfo | ||
|
||
from mypy.erasetype import erase_typevars | ||
from mypy.sametypes import is_same_type | ||
from mypy.types import Instance, TypeVarType, TupleType, Type | ||
|
||
|
||
def fill_typevars(typ: TypeInfo) -> Union[Instance, TupleType]: | ||
"""For a non-generic type, return instance type representing the type. | ||
For a generic G type with parameters T1, .., Tn, return G[T1, ..., Tn]. | ||
""" | ||
tv = [] # type: List[Type] | ||
for i in range(len(typ.type_vars)): | ||
tv.append(TypeVarType(typ.defn.type_vars[i])) | ||
inst = Instance(typ, tv) | ||
if typ.tuple_type is None: | ||
return inst | ||
return typ.tuple_type.copy_modified(fallback=inst) | ||
|
||
|
||
def has_no_typevars(typ: Type) -> bool: | ||
return is_same_type(typ, erase_typevars(typ)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2123,3 +2123,153 @@ class B(object, A): # E: Cannot determine consistent method resolution order (MR | |
# flags: --fast-parser | ||
class C(metaclass=int()): # E: Dynamic metaclass not supported for 'C' | ||
pass | ||
|
||
[case testVariableSubclass] | ||
class A: | ||
a = 1 # type: int | ||
class B(A): | ||
a = 1 | ||
[out] | ||
|
||
[case testVariableSubclassAssignMismatch] | ||
class A: | ||
a = 1 # type: int | ||
class B(A): | ||
a = "a" | ||
[out] | ||
main: note: In class "B": | ||
main:4: error: Incompatible types in assignment (expression has type "str", variable has type "int") | ||
|
||
[case testVariableSubclassAssignment] | ||
class A: | ||
a = None # type: int | ||
class B(A): | ||
def __init__(self) -> None: | ||
self.a = "a" | ||
[out] | ||
main: note: In member "__init__" of class "B": | ||
main:5: error: Incompatible types in assignment (expression has type "str", variable has type "int") | ||
|
||
[case testVariableSubclassTypeOverwrite] | ||
class A: | ||
a = None # type: int | ||
class B(A): | ||
a = None # type: str | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, are you sure that this (without caring about C even) should be allowed? I think it's just as much an error as when no type is given. (However, the declared type here should be allowed to be a subclass of the type declared earlier.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Class variables are invariant, so the type should be equivalent (
|
||
class C(B): | ||
a = "a" | ||
[out] | ||
main: note: In class "B": | ||
main:4: error: Incompatible types in assignment (expression has type "str", variable has type "int") | ||
|
||
[case testVariableSubclassTypeOverwriteImplicit] | ||
class A: | ||
a = 1 | ||
class B(A): | ||
a = None # type: str | ||
[out] | ||
main: note: In class "B": | ||
main:4: error: Incompatible types in assignment (expression has type "str", variable has type "int") | ||
|
||
[case testVariableSuperUsage] | ||
class A: | ||
a = [] # type: list | ||
class B(A): | ||
a = [1, 2] | ||
class C(B): | ||
a = B.a + [3] | ||
[builtins fixtures/list.pyi] | ||
[out] | ||
|
||
[case testVariableRvalue] | ||
class A: | ||
a = None | ||
class B(A): | ||
a = 1 | ||
class C(B): | ||
a = "a" | ||
[out] | ||
main: note: In class "A": | ||
main:2: error: Need type annotation for variable | ||
main: note: In class "C": | ||
main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int") | ||
|
||
[case testVariableTypeVar] | ||
from typing import TypeVar, Generic | ||
T = TypeVar('T') | ||
class A(Generic[T]): | ||
a = None # type: T | ||
class B(A[int]): | ||
a = 1 | ||
|
||
[case testVariableTypeVarInvalid] | ||
from typing import TypeVar, Generic | ||
T = TypeVar('T') | ||
class A(Generic[T]): | ||
a = None # type: T | ||
class B(A[int]): | ||
a = "abc" | ||
[out] | ||
main: note: In class "B": | ||
main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int") | ||
|
||
[case testVariableTypeVarIndirectly] | ||
from typing import TypeVar, Generic | ||
T = TypeVar('T') | ||
class A(Generic[T]): | ||
a = None # type: T | ||
class B(A[int]): | ||
pass | ||
class C(B): | ||
a = "a" | ||
[out] | ||
main: note: In class "C": | ||
main:8: error: Incompatible types in assignment (expression has type "str", variable has type "int") | ||
|
||
[case testVariableTypeVarList] | ||
from typing import List, TypeVar, Generic | ||
T = TypeVar('T') | ||
class A(Generic[T]): | ||
a = None # type: List[T] | ||
b = None # type: List[T] | ||
class B(A[int]): | ||
a = [1] | ||
b = [''] | ||
[builtins fixtures/list.pyi] | ||
[out] | ||
main: note: In class "B": | ||
main:8: error: List item 0 has incompatible type "str" | ||
|
||
[case testVariableMethod] | ||
class A: | ||
def a(self) -> None: pass | ||
b = 1 | ||
class B(A): | ||
a = 1 | ||
def b(self) -> None: pass | ||
[out] | ||
main: note: In class "B": | ||
main:5: error: Incompatible types in assignment (expression has type "int", variable has type Callable[[A], None]) | ||
main:6: error: Signature of "b" incompatible with supertype "A" | ||
|
||
[case testVariableProperty] | ||
class A: | ||
@property | ||
def a(self) -> bool: pass | ||
class B(A): | ||
a = None # type: bool | ||
class C(A): | ||
a = True | ||
class D(A): | ||
a = 1 | ||
[builtins fixtures/property.pyi] | ||
[out] | ||
main: note: In class "D": | ||
main:9: error: Incompatible types in assignment (expression has type "int", variable has type "bool") | ||
|
||
[case testVariableOverwriteAny] | ||
from typing import Any | ||
class A: | ||
a = 1 | ||
class B(A): | ||
a = 'x' # type: Any | ||
[out] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a large class hierarchy this error could be pretty mysterious. Can you figure out a way to at least link to the superclass where the variable is declared first? (Even better the exact line, but that may be tricky.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this is an issue for only class variables; in general it would be nice if we can tell where the original declaration came from. As the same issue is already true for when using "self.a = 1" for example. I will fiddle with this a bit, see what I can come up with.