KEMBAR78
Type check arguments for "super" by JukkaL · Pull Request #3919 · python/mypy · GitHub
Skip to content

Conversation

JukkaL
Copy link
Collaborator

@JukkaL JukkaL commented Sep 5, 2017

This is not quite as strict as we could be, but this is still better
than what we had before. A full implementation will be somewhat
complicated.

Remaining issues:

  • Assume that the first argument refers to the enclosing class, even
    though that's not generally the case.

  • Assume that a type object as the second argument is always fine.

  • Don't validate some kinds of type object types as the first argument.

  • Single-argument variant is not supported.

Fixes #526 (modulo the open issues).

Also, I no longer think that defining messages as constants in
mypy.messages is generally a good idea so I'm not following
that pattern here. I'll officially update the convention in a
separate PR.

This is not quite as strict as we could be, but this is still better
than what we had before. A full implementation will be somewhat
complicated.

Remaining issues:

* Assume that the first argument refers to the enclosing class, even
  though that's not generally the case.

* Assume that a type object as the second argument is always fine.

* Don't validate some kinds of type object types as the first argument.

* Single-argument variant is not supported.

Fixes #526 (modulo the open issues).

Also, I no longer think that defining messages as constants in
`mypy.messages` is generally a good idea so I'm not following
that pattern here. I'll officially update the convention in a
separate PR.
@JukkaL
Copy link
Collaborator Author

JukkaL commented Sep 5, 2017

This is ready for review but this is not ready to be merged yet, as this requires some changes to internal Dropbox code.

I'll create follow-up issues for any remaining open issues after this has been merged.

@emmatyping
Copy link
Member

Appveyor failure is a flake (see #3895)

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 is now the oldest high priority issue, I am glad it will be fixed now.

elif len(e.call.args) > 2:
self.chk.fail('Too many arguments for "super"', e)
elif self.chk.options.python_version[0] == 2 and len(e.call.args) == 0:
self.chk.fail('Too few arguments for "super"', e)
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 move all the logic until this point to semanal.py, but it is optional.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm keeping the logic here because we want to execute the final elif block only if all the previous checks are false, and having all the checks here makes it explicit. Also, we check the argument counts of other calls during type checking (though super() is special so we could do it during semantic analysis as well).

elif isinstance(type_obj_type, AnyType):
return
else:
self.chk.fail('First argument for "super" must be a type object', e)
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 add Got: "<actual type>"

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated, but I still don't display the class name, since it looked confusing (due to type of instance C looking like a reference C to type object).

instance_type = instance_type.upper_bound
if not isinstance(instance_type, (Instance, TupleType)):
# Too tricky, give up.
self.chk.fail('Unsupported argument 2 for "super"', e)
Copy link
Member

Choose a reason for hiding this comment

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

This is a bit inconsistent with the first argument logic above, where we give up by a simple return without any error message, why it is so?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I now generate error above as well.

# TODO: Check whether this is a valid type object here.
pass
elif not isinstance(instance_type, AnyType):
self.chk.fail('Unsupported argument 2 for "super"', e)
Copy link
Member

Choose a reason for hiding this comment

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

My question above about consistency also applies here. Also in this case the error message is repeated, so maybe it makes sense to define a constant for it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated to use a constant for the error message.

elif not isinstance(instance_type, AnyType):
self.chk.fail('Unsupported argument 2 for "super"', e)

def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> 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 remember looking at the function analyze_super and something worried me. Namely, look at the error:
super() requires at least one positional argument several lines below (can't comment there), it is misleading since it can appear on Python 3 as well but we support super() without arguments. Also the logic there seems a bit wrong.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The message refers to the enclosing function not having any positional arguments. Updated the wording. If it's still incorrect, we can create a new issue about it, since this looks like a pretty marginal scenario. The logic is kind of convoluted though -- most of the for loop body is executed at most once, which is not immediately obvious.

Copy link
Member

Choose a reason for hiding this comment

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

You already have a list of things still left to fix in the PR description, if you feel this deserves a separate issue, then OK, otherwise you can just combine this with first item in your list.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's still unclear to me what the issue is (other than the code being hard to understand). Can you create a new issue (or add a comment to an existing relevant issue) about this?


def visit_super_expr(self, node: SuperExpr) -> SuperExpr:
new = SuperExpr(node.name)
new = SuperExpr(node.name, cast(CallExpr, self.expr(node.call)))
Copy link
Member

Choose a reason for hiding this comment

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

Why a cast needed here? Is it possible to replace it with an assert?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Replaced with an assert.

super(C, self).xyz

[case testSuperWithTypeType]
from typing import Type
Copy link
Member

Choose a reason for hiding this comment

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

You are not using this import, have you forgotten to test something here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, added another thing to test.

return self

[case testSuperWithTypeVarValues]
from typing import TypeVar, Generic
Copy link
Member

Choose a reason for hiding this comment

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

You imported Generic but didn't use it. Did you want to add another test also here? I would actually add some tests with Generic.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added a test that uses Generic.

def f(self) -> None: pass

class C(B):
def f(self) -> None: pass
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 make return types different here and in superclass (e.g. float and int), so that we could be sure in reveal_type that the correct one is picked up.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated.

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.

Just one more question. You can merge this when the internal changes you mentioned are ready.

if not isinstance(item, Instance):
# A complicated type object type. Too tricky, give up.
# TODO: Do something more clever here.
self.chk.fail(messages.UNSUPPORTED_ARGUMENT_2_FOR_SUPER, e)
Copy link
Member

Choose a reason for hiding this comment

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

Should it be actually argument 1 here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes! Added a test case and fixed the error message.

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.

3 participants