Skip to content
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

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

Merged
merged 7 commits into from
Mar 27, 2017

Conversation

ilevkivskyi
Copy link
Member

@ilevkivskyi ilevkivskyi commented Feb 12, 2017

Fixes #1843 (It was also necessary to fix few minor things to make this work correctly)

The rules are simple, assuming we have:

class A:
    @abstractmethod
    def m(self) -> None: pass
class C(A):
    def m(self) -> None:
        ...

then

def fun(cls: Type[A]):
    cls() # OK
fun(A) # Error
fun(C) # OK

The same applies to variables:

var: Type[A]
var() # OK
var = A # Error
var = C # OK

Also there is an option for people who want to pass abstract classes around: type aliases, they work as before. For non-abstract A, Type[A] also works as before.


@gvanrossum You could be interested in this.
My intuition why you opened #1843 is when someone writes annotation Type[A] with an abstract A, then most probably one wants a class object that implements a certain protocol, not just inherits from A.

EDIT: as discussed in python/peps#224 this behaviour is good for both protocols and usual ABCs.

@elazarg
Copy link
Contributor

elazarg commented Feb 16, 2017

#2430 depends on this PR (although the test should be removed there)

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

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

Thanks! I finally got to looking at this. I like the general idea.

@@ -157,6 +157,43 @@ class B(A): pass
B()# E: Cannot instantiate abstract class 'B' with abstract attributes 'f' and 'g'
[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.

mypy/checker.py Outdated
@@ -1149,6 +1151,16 @@ 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.

mypy/checker.py Outdated
@@ -1149,6 +1151,16 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type
else:
rvalue_type = self.check_simple_assignment(lvalue_type, rvalue, lvalue)

# Special case
if (
isinstance(rvalue_type, CallableType) and rvalue_type.is_type_obj() and
Copy link
Member

Choose a reason for hiding this comment

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

I'm not keen on this formatting.

mypy/checker.py Outdated
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).

if callee.is_concrete_type_obj() and callee.type_object().is_abstract:
if (callee.is_type_obj() and callee.type_object().is_abstract
# Exceptions for Type[...] and classmethod first argument
and not callee.from_type_type and not callee.is_classmethod_class):
Copy link
Member

Choose a reason for hiding this comment

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

Maybe also except static methods?

I found a case in our codebase that goes basically like this:

class Logger:
    @staticmethod
    def log(a: Type[C]): ...
class C:
    @classmethod
    def action(cls) -> None:
        Logger.log(cls)  #E: Only non-abstract class can be given where 'Type[C]' is expected
    @abstractmethod
    ...

(I'm far from positive that it's because of this line though.)

Copy link
Member Author

Choose a reason for hiding this comment

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

This code looks reasonable, I added a corresponding exception to check_arg.

@@ -840,6 +845,12 @@ def check_arg(self, caller_type: Type, original_caller_type: Type,
messages.does_not_return_value(caller_type, context)
elif isinstance(caller_type, DeletedType):
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 :-)

@ilevkivskyi
Copy link
Member Author

@gvanrossum
Thank you very much for review!
I applied your comments in new commits.

@gvanrossum
Copy link
Member

LGTM. We're holding off merging until we've sorted out some issues with recent mypy and typeshed commits.

@ilevkivskyi
Copy link
Member Author

@gvanrossum Could this be merged soon?

Sorry for bothering you again, this will be helpful to avoid merge conflicts with my work on protocols.

@gvanrossum gvanrossum merged commit 72967fd into python:master Mar 27, 2017
@gvanrossum
Copy link
Member

NP. We're finally at a point where we can merge PRs with confidence again.

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.

class object typed with Type[SomeAbstractClass] should not complain about instantiation
3 participants