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
Support six.add_metaclass #3842
Changes from 6 commits
412fe8f
a799677
13be183
810a481
ed7171d
4ae1dea
cbfb40d
1d2b32c
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 |
---|---|---|
|
@@ -3470,64 +3470,94 @@ reveal_type(y['b']) # E: Revealed type is '__main__.B' | |
-- Special support for six | ||
-- ----------------------- | ||
|
||
[case testSixWithMetaclass] | ||
[case testSixMetaclass] | ||
import six | ||
class M(type): | ||
x = 5 | ||
class A(six.with_metaclass(M)): pass | ||
@six.add_metaclass(M) | ||
class B: pass | ||
reveal_type(type(A).x) # E: Revealed type is 'builtins.int' | ||
reveal_type(type(B).x) # E: Revealed type is 'builtins.int' | ||
|
||
[case testSixWithMetaclass_python2] | ||
[case testSixMetaclass_python2] | ||
import six | ||
class M(type): | ||
x = 5 | ||
class A(six.with_metaclass(M)): pass | ||
@six.add_metaclass(M) | ||
class B: pass | ||
reveal_type(type(A).x) # E: Revealed type is 'builtins.int' | ||
reveal_type(type(B).x) # E: Revealed type is 'builtins.int' | ||
|
||
[case testFromSixWithMetaclass] | ||
from six import with_metaclass | ||
[case testFromSixMetaclass] | ||
from six import with_metaclass, add_metaclass | ||
class M(type): | ||
x = 5 | ||
class A(with_metaclass(M)): pass | ||
@add_metaclass(M) | ||
class B: pass | ||
reveal_type(type(A).x) # E: Revealed type is 'builtins.int' | ||
reveal_type(type(B).x) # E: Revealed type is 'builtins.int' | ||
|
||
[case testSixWithMetaclassImportFrom] | ||
[case testSixMetaclassImportFrom] | ||
import six | ||
from metadefs import M | ||
class A(six.with_metaclass(M)): pass | ||
@six.add_metaclass(M) | ||
class B: pass | ||
reveal_type(type(A).x) # E: Revealed type is 'builtins.int' | ||
reveal_type(type(B).x) # E: Revealed type is 'builtins.int' | ||
[file metadefs.py] | ||
class M(type): | ||
x = 5 | ||
|
||
[case testSixWithMetaclassImport] | ||
[case testSixMetaclassImport] | ||
import six | ||
import metadefs | ||
class A(six.with_metaclass(metadefs.M)): pass | ||
@six.add_metaclass(metadefs.M) | ||
class B: pass | ||
reveal_type(type(A).x) # E: Revealed type is 'builtins.int' | ||
reveal_type(type(B).x) # E: Revealed type is 'builtins.int' | ||
[file metadefs.py] | ||
class M(type): | ||
x = 5 | ||
|
||
[case testSixWithMetaclassAndBase] | ||
[case testSixMetaclassAndBase] | ||
from typing import Iterable, Iterator | ||
import six | ||
class M(type): | ||
class M(type, Iterable[int]): | ||
x = 5 | ||
def __iter__(self) -> Iterator[int]: ... | ||
class A: | ||
def foo(self): pass | ||
class B: | ||
def bar(self): pass | ||
class C1(six.with_metaclass(M, A)): pass | ||
@six.add_metaclass(M) | ||
class D1(A): pass | ||
class C2(six.with_metaclass(M, A, B)): pass | ||
@six.add_metaclass(M) | ||
class D2(A, B): pass | ||
reveal_type(type(C1).x) # E: Revealed type is 'builtins.int' | ||
reveal_type(type(D1).x) # E: Revealed type is 'builtins.int' | ||
reveal_type(type(C2).x) # E: Revealed type is 'builtins.int' | ||
reveal_type(type(D2).x) # E: Revealed type is 'builtins.int' | ||
C1().foo() | ||
D1().foo() | ||
C1().bar() # E: "C1" has no attribute "bar" | ||
D1().bar() # E: "D1" has no attribute "bar" | ||
for x in C1: reveal_type(x) # E: Revealed type is 'builtins.int*' | ||
for x in C2: reveal_type(x) # E: Revealed type is 'builtins.int*' | ||
C2().foo() | ||
D2().foo() | ||
C2().bar() | ||
D2().bar() | ||
C2().baz() # E: "C2" has no attribute "baz" | ||
D2().baz() # E: "D2" has no attribute "baz" | ||
|
||
[case testSixWithMetaclassGenerics] | ||
[case testSixMetaclassGenerics] | ||
from typing import Generic, GenericMeta, TypeVar | ||
import six | ||
class DestroyableMeta(type): | ||
|
@@ -3539,25 +3569,50 @@ class ArcMeta(GenericMeta, DestroyableMeta): | |
pass | ||
class Arc(six.with_metaclass(ArcMeta, Generic[T_co], Destroyable)): | ||
pass | ||
@six.add_metaclass(ArcMeta) | ||
class Arc1(Generic[T_co], Destroyable): | ||
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. This actually fails at runtime because of a metaclass conflict. Is it easy to detect this? If this is non-trivial, then I think we should not include it in this PR. 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. I believe this is not caught because the base class Generic is removed earlier in the analysis. It doesn't seem obvious to me how this should be fixed. 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. OK, them it is not easy to support this. |
||
pass | ||
class MyDestr(Destroyable): | ||
pass | ||
reveal_type(Arc[MyDestr]()) # E: Revealed type is '__main__.Arc[__main__.MyDestr*]' | ||
reveal_type(Arc1[MyDestr]()) # E: Revealed type is '__main__.Arc1[__main__.MyDestr*]' | ||
[builtins fixtures/bool.pyi] | ||
|
||
[case testSixWithMetaclassErrors] | ||
[case testSixMetaclassErrors] | ||
import six | ||
class M(type): pass | ||
class A(object): pass | ||
def f() -> type: return M | ||
class C1(six.with_metaclass(M), object): pass # E: Invalid base class | ||
class C2(C1, six.with_metaclass(M)): pass # E: Invalid base class | ||
class C3(six.with_metaclass(A)): pass # E: Metaclasses not inheriting from 'type' are not supported | ||
class C4(six.with_metaclass(M), metaclass=M): pass # E: Invalid base class | ||
@six.add_metaclass(A) # E: Metaclasses not inheriting from 'type' are not supported | ||
class D3(A): pass | ||
class C4(six.with_metaclass(M), metaclass=M): pass # E: Multiple metaclass definitions | ||
@six.add_metaclass(M) # E: Multiple metaclass definitions | ||
class D4(metaclass=M): pass | ||
class C5(six.with_metaclass(f())): pass # E: Dynamic metaclass not supported for 'C5' | ||
|
||
[case testSixWithMetaclassErrors_python2-skip] | ||
# No error here yet | ||
@six.add_metaclass(f()) # E: Dynamic metaclass not supported for 'D5' | ||
class D5: pass | ||
|
||
@six.add_metaclass(M) # E: Multiple metaclass definitions | ||
class CD(six.with_metaclass(M)): pass | ||
|
||
def q(t): | ||
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. This is not a good way to test |
||
class E(metaclass=t): pass | ||
class F(six.with_metaclass(t)): pass | ||
@six.add_metaclass(t) | ||
class G: pass | ||
|
||
class M1(type): pass | ||
class Q1(metaclass=M1): pass | ||
@six.add_metaclass(M) # E: Inconsistent metaclass structure for 'CQA' | ||
class CQA(Q1): pass | ||
class CQW(six.with_metaclass(M, Q1)): pass # E: Inconsistent metaclass structure for 'CQW' | ||
|
||
[case testSixMetaclassErrors_python2] | ||
# flags: --python-version 2.7 | ||
import six | ||
class M(type): pass | ||
class C4(six.with_metaclass(M)): | ||
class C4(six.with_metaclass(M)): # E: Multiple metaclass definitions | ||
__metaclass__ = M |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
from typing import Type | ||
from typing import Type, Callable | ||
def with_metaclass(mcls: Type[type], *args: type) -> type: pass | ||
def add_metaclass(mcls: Type[type]) -> Callable[[type], type]: pass |
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 would add more tests. Some ideas:
type
)Any
(for example a metaclass form silent import)__getattr__
and/or other special attributes like__iter__
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.
But these will test other (later) mechanisms, so aren't they redundant (white-box-wise)?
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.
Look at the number of tests for
with_metaclass
, they are there for a reason, tests are not for the present (implementation), they are for the future (when everything may be refactored in unpredictable ways).