Skip to content

Allow instantiation of Type[A], if A is abstract #2853

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 7 commits into from
Mar 27, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Work on corner cases, more tests
  • Loading branch information
ilevkivskyi committed Feb 12, 2017
commit 71ea62f24d084714ebf616267c705e3cebf01550
9 changes: 8 additions & 1 deletion mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1080,7 +1080,6 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type
infer_lvalue_type)
else:
lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalue)

if isinstance(lvalue, NameExpr):
if self.check_compatibility_all_supers(lvalue, lvalue_type, rvalue):
# We hit an error on this line; don't check for any others
Expand Down Expand Up @@ -1128,6 +1127,14 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type
else:
rvalue_type = self.check_simple_assignment(lvalue_type, rvalue, lvalue)

# Special case
Copy link
Member

Choose a reason for hiding this comment

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

For what, exactly?

Copy link
Member Author

Choose a reason for hiding this comment

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

I added a detailed comment here.

if (isinstance(rvalue_type, CallableType) and rvalue_type.is_type_obj() and
rvalue_type.type_object().is_abstract and
isinstance(lvalue_type, TypeType) and
isinstance(lvalue_type.item, Instance) and lvalue_type.item.type.is_abstract):
self.fail("Cannot only assign non-abstract classes"
Copy link
Member

Choose a reason for hiding this comment

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

"Cannot only" -> "Cannot" (I presume).

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, sorry, it should be "Can only" (this is probably a worst kind of mistakes).

" to a variable of type '{}'".format(lvalue_type), rvalue)
return
if rvalue_type and infer_lvalue_type:
self.binder.assign_type(lvalue,
rvalue_type,
Expand Down
12 changes: 6 additions & 6 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ def check_call(self, callee: Type, args: List[Expression],
"""
arg_messages = arg_messages or self.msg
if isinstance(callee, CallableType):
if (callee.is_concrete_type_obj() and callee.type_object().is_abstract
if (callee.is_type_obj() and callee.type_object().is_abstract
and not callee.from_type_type):
type = callee.type_object()
self.msg.cannot_instantiate_abstract_class(
Expand Down Expand Up @@ -440,7 +440,7 @@ def analyze_type_type_callee(self, item: Type, context: Context) -> Type:
if isinstance(item, Instance):
res = type_object_type(item.type, self.named_type)
if isinstance(res, CallableType):
res.from_type_type = True
res = res.copy_modified(from_type_type=True)
return res
if isinstance(item, UnionType):
return UnionType([self.analyze_type_type_callee(item, context)
Expand Down Expand Up @@ -846,10 +846,10 @@ def check_arg(self, caller_type: Type, original_caller_type: Type,
messages.deleted_as_rvalue(caller_type, context)
# Only non-abstract class could be given where Type[...] is expected
Copy link
Member

Choose a reason for hiding this comment

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

could -> can (again :-)

elif isinstance(caller_type, CallableType) and isinstance(callee_type, TypeType):
tp = caller_type.type_object()
if caller_type.is_concrete_type_obj() and tp.is_abstract:
messages.cannot_instantiate_abstract_class(tp.name(),
tp.abstract_attributes, context)
if (caller_type.is_type_obj() and caller_type.type_object().is_abstract and
isinstance(callee_type.item, Instance) and callee_type.item.type.is_abstract):
messages.fail("Only non-abstract class can be given where '{}' is expected"
.format(callee_type), context)
elif not is_subtype(caller_type, callee_type):
if self.chk.should_suppress_optional_error([caller_type, callee_type]):
return
Expand Down
3 changes: 2 additions & 1 deletion mypy/meet.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,12 @@ class C(A, B): ...
elif isinstance(t, TypeType) or isinstance(s, TypeType):
# If exactly only one of t or s is a TypeType, check if one of them
# is an `object` or a `type` and otherwise assume no overlap.
one = t if isinstance(t, TypeType) else s
other = s if isinstance(t, TypeType) else t
if isinstance(other, Instance):
return other.type.fullname() in {'builtins.object', 'builtins.type'}
else:
return False
return isinstance(other, CallableType) and is_subtype(other, one)
if experiments.STRICT_OPTIONAL:
if isinstance(t, NoneTyp) != isinstance(s, NoneTyp):
# NoneTyp does not overlap with other non-Union types under strict Optional checking
Expand Down
13 changes: 10 additions & 3 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,7 @@ def __init__(self,
implicit: bool = False,
is_classmethod_class: bool = False,
special_sig: Optional[str] = None,
from_type_type: bool = False,
) -> None:
if variables is None:
variables = []
Expand All @@ -613,8 +614,9 @@ def __init__(self,
self.variables = variables
self.is_ellipsis_args = is_ellipsis_args
self.implicit = implicit
self.is_classmethod_class = is_classmethod_class
self.special_sig = special_sig
self.from_type_type = False
self.from_type_type = from_type_type
super().__init__(line, column)

def copy_modified(self,
Expand All @@ -629,7 +631,8 @@ def copy_modified(self,
line: int = _dummy,
column: int = _dummy,
is_ellipsis_args: bool = _dummy,
special_sig: Optional[str] = _dummy) -> 'CallableType':
special_sig: Optional[str] = _dummy,
from_type_type: bool = _dummy) -> 'CallableType':
return CallableType(
arg_types=arg_types if arg_types is not _dummy else self.arg_types,
arg_kinds=arg_kinds if arg_kinds is not _dummy else self.arg_kinds,
Expand All @@ -646,6 +649,7 @@ def copy_modified(self,
implicit=self.implicit,
is_classmethod_class=self.is_classmethod_class,
special_sig=special_sig if special_sig is not _dummy else self.special_sig,
from_type_type=from_type_type if from_type_type is not _dummy else self.from_type_type,
)

def is_type_obj(self) -> bool:
Expand All @@ -660,7 +664,10 @@ def type_object(self) -> mypy.nodes.TypeInfo:
ret = self.ret_type
if isinstance(ret, TupleType):
ret = ret.fallback
return cast(Instance, ret).type
if isinstance(ret, TypeVarType):
ret = ret.upper_bound
assert isinstance(ret, Instance)
return ret.type

def accept(self, visitor: 'TypeVisitor[T]') -> T:
return visitor.visit_callable_type(self)
Expand Down
21 changes: 15 additions & 6 deletions test-data/unit/check-abstract.test
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ B()# E: Cannot instantiate abstract class 'B' with abstract attributes 'f' and '
[out]

[case testInstantiationAbstractsInType]
# flags: --python-version 3.6
Copy link
Member

Choose a reason for hiding this comment

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

I take it this is only because of the var: Type[A] below? If this behavior is supposed to work for all Python versions maybe you can rewrite that part of the test using a function? (Also it's a rather long test; maybe it would be better split up.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Since recently --python-version flag is not needed. Also I split the test into three smaller and added a test case for @classmethod.

from typing import Type
from abc import abstractmethod

Expand All @@ -173,15 +174,23 @@ def f(cls: Type[A]) -> A:
return cls() # OK

def g() -> A:
return A() # Error, see out
return A() # E: Cannot instantiate abstract class 'A' with abstract attribute 'm'

f(A) # E: Cannot instantiate abstract class 'A' with abstract attribute 'm'
f(B) # E: Cannot instantiate abstract class 'B' with abstract attribute 'm'
f(A) # E: Only non-abstract class can be given where 'Type[__main__.A]' is expected
f(B) # E: Only non-abstract class can be given where 'Type[__main__.A]' is expected
f(C) # OK

Alias = A
GoodAlias = C
Alias() # E: Cannot instantiate abstract class 'A' with abstract attribute 'm'
GoodAlias()

var: Type[A]
var()
var = A # E: Cannot only assign non-abstract classes to a variable of type 'Type[__main__.A]'
var = B # E: Cannot only assign non-abstract classes to a variable of type 'Type[__main__.A]'
var = C # OK
[out]
main: note: In function "g":
main:16: error: Cannot instantiate abstract class 'A' with abstract attribute 'm'
main: note: At top level:

[case testInstantiatingClassWithInheritedAbstractMethodAndSuppression]
from abc import abstractmethod, ABCMeta
Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy