Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
65 changes: 63 additions & 2 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2068,9 +2068,67 @@ def infer_lambda_type_using_context(self, e: LambdaExpr) -> Tuple[Optional[Calla

def visit_super_expr(self, e: SuperExpr) -> Type:
"""Type check a super expression (non-lvalue)."""
self.check_super_arguments(e)
t = self.analyze_super(e, False)
return t

def check_super_arguments(self, e: SuperExpr) -> None:
"""Check arguments in a super(...) call."""
if ARG_STAR in e.call.arg_kinds:
self.chk.fail('Varargs not supported with "super"', e)
elif e.call.args and set(e.call.arg_kinds) != {ARG_POS}:
self.chk.fail('"super" only accepts positional arguments', e)
elif len(e.call.args) == 1:
self.chk.fail('"super" with a single argument not supported', e)
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
Copy Markdown
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
Copy Markdown
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 len(e.call.args) == 2:
type_obj_type = self.accept(e.call.args[0])
instance_type = self.accept(e.call.args[1])
if isinstance(type_obj_type, FunctionLike) and type_obj_type.is_type_obj():
type_info = type_obj_type.type_object()
elif isinstance(type_obj_type, TypeType):
item = type_obj_type.item
if isinstance(item, AnyType):
# Could be anything.
return
if isinstance(item, TupleType):
item = item.fallback # Handle named tuples and other Tuple[...] subclasses.
if not isinstance(item, Instance):
# A complicated type object type. Too tricky, give up.
# TODO: Do something more clever here.
self.chk.fail('Unsupported argument 1 for "super"', e)
return
type_info = item.type
elif isinstance(type_obj_type, AnyType):
return
else:
self.msg.first_argument_for_super_must_be_type(type_obj_type, e)
return

if isinstance(instance_type, (Instance, TupleType, TypeVarType)):
if isinstance(instance_type, TypeVarType):
# Needed for generic self.
instance_type = instance_type.upper_bound
if not isinstance(instance_type, (Instance, TupleType)):
# Too tricky, give up.
# TODO: Do something more clever here.
self.chk.fail(messages.UNSUPPORTED_ARGUMENT_2_FOR_SUPER, e)
return
if isinstance(instance_type, TupleType):
# Needed for named tuples and other Tuple[...] subclasses.
instance_type = instance_type.fallback
if type_info not in instance_type.type.mro:
self.chk.fail('Argument 2 for "super" not an instance of argument 1', e)
elif isinstance(instance_type, TypeType) or (isinstance(instance_type, FunctionLike)
and instance_type.is_type_obj()):
# TODO: Check whether this is a valid type object here.
pass
elif not isinstance(instance_type, AnyType):
self.chk.fail(messages.UNSUPPORTED_ARGUMENT_2_FOR_SUPER, e)

def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> Type:
"""Type check a super expression."""
if e.info and e.info.bases:
Expand All @@ -2091,9 +2149,12 @@ def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> Type:
self.chk.fail('super() outside of a method is not supported', e)
return AnyType(TypeOfAny.from_error)
args = self.chk.scope.top_function().arguments
# An empty args with super() is an error; we need something in declared_self
# super() in a function with empty args is an error; we
# need something in declared_self.
if not args:
self.chk.fail('super() requires at least one positional argument', e)
self.chk.fail(
'super() requires one or more positional arguments in '
'enclosing function', e)
return AnyType(TypeOfAny.from_error)
declared_self = args[0].variable.type
return analyze_member_access(name=e.name, typ=fill_typevars(e.info), node=e,
Expand Down
2 changes: 1 addition & 1 deletion mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -928,7 +928,7 @@ def visit_Attribute(self, n: ast3.Attribute) -> Union[MemberExpr, SuperExpr]:
if (isinstance(n.value, ast3.Call) and
isinstance(n.value.func, ast3.Name) and
n.value.func.id == 'super'):
return SuperExpr(n.attr)
return SuperExpr(n.attr, self.visit(n.value))

return MemberExpr(self.visit(n.value), n.attr)

Expand Down
2 changes: 1 addition & 1 deletion mypy/fastparse2.py
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,7 @@ def visit_Attribute(self, n: ast27.Attribute) -> Expression:
if (isinstance(n.value, ast27.Call) and
isinstance(n.value.func, ast27.Name) and
n.value.func.id == 'super'):
return SuperExpr(n.attr)
return SuperExpr(n.attr, self.visit(n.value))

return MemberExpr(self.visit(n.value), n.attr)

Expand Down
10 changes: 10 additions & 0 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
BARE_GENERIC = 'Missing type parameters for generic type'
IMPLICIT_GENERIC_ANY_BUILTIN = 'Implicit generic "Any". Use \'{}\' and specify generic parameters'
INCOMPATIBLE_TYPEVAR_VALUE = 'Value of type variable "{}" of {} cannot be {}'
UNSUPPORTED_ARGUMENT_2_FOR_SUPER = 'Unsupported argument 2 for "super"'

ARG_CONSTRUCTOR_NAMES = {
ARG_POS: "Arg",
Expand Down Expand Up @@ -819,6 +820,15 @@ def invalid_keyword_var_arg(self, typ: Type, is_mapping: bool, context: Context)
def undefined_in_superclass(self, member: str, context: Context) -> None:
self.fail('"{}" undefined in superclass'.format(member), context)

def first_argument_for_super_must_be_type(self, actual: Type, context: Context) -> None:
if isinstance(actual, Instance):
# Don't include type of instance, because it can look confusingly like a type
# object.
type_str = 'a non-type instance'
else:
type_str = self.format(actual)
self.fail('Argument 1 for "super" must be a type object; got {}'.format(type_str), context)

def too_few_string_formatting_arguments(self, context: Context) -> None:
self.fail('Not enough arguments for format string', context)

Expand Down
4 changes: 3 additions & 1 deletion mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1465,9 +1465,11 @@ class SuperExpr(Expression):

name = ''
info = None # type: TypeInfo # Type that contains this super expression
call = None # type: CallExpr # The expression super(...)

def __init__(self, name: str) -> None:
def __init__(self, name: str, call: CallExpr) -> None:
self.name = name
self.call = call

def accept(self, visitor: ExpressionVisitor[T]) -> T:
return visitor.visit_super_expr(self)
Expand Down
2 changes: 2 additions & 0 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3086,6 +3086,8 @@ def visit_super_expr(self, expr: SuperExpr) -> None:
self.fail('"super" used outside class', expr)
return
expr.info = self.type
for arg in expr.call.args:
arg.accept(self)

def visit_tuple_expr(self, expr: TupleExpr) -> None:
for item in expr.items:
Expand Down
4 changes: 3 additions & 1 deletion mypy/treetransform.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,9 @@ def visit_reveal_type_expr(self, node: RevealTypeExpr) -> RevealTypeExpr:
return RevealTypeExpr(self.expr(node.expr))

def visit_super_expr(self, node: SuperExpr) -> SuperExpr:
new = SuperExpr(node.name)
call = self.expr(node.call)
assert isinstance(call, CallExpr)
new = SuperExpr(node.name, call)
new.info = node.info
return new

Expand Down
9 changes: 9 additions & 0 deletions test-data/unit/check-python2.test
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,12 @@ class A:
[out]
main:2: error: Invalid type for self, or extra argument type in function annotation
main:2: note: (Hint: typically annotations omit the type for self)

[case testSuper]
class A:
def f(self): # type: () -> None
pass
class B(A):
def g(self): # type: () -> None
super(B, self).f()
super().f() # E: Too few arguments for "super"
190 changes: 186 additions & 4 deletions test-data/unit/check-super.test
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,6 @@ B(1)
B(1, 'x')
[builtins fixtures/__new__.pyi]

[case testSuperOutsideMethodNoCrash]
class C:
a = super().whatever # E: super() outside of a method is not supported

reveal_type(C.a) # E: Revealed type is 'Any'
[out]

Expand All @@ -115,3 +111,189 @@ class B(A):
def foo(self):
super(B, self).foo() # Not an error
[out]

[case testSuperWithAny]
class B:
def f(self) -> None: pass
class C(B):
def h(self, x) -> None:
reveal_type(super(x, x).f) # E: Revealed type is 'def ()'
reveal_type(super(C, x).f) # E: Revealed type is 'def ()'

[case testSuperInUnannotatedMethod]
class C:
def h(self):
super(C, self).xyz

[case testSuperWithTypeObjects]
from typing import Type

class A:
def f(self) -> object: pass

class B(A):
def f(self) -> int: pass

@classmethod
def g(cls, x) -> None:
reveal_type(super(cls, x).f) # E: Revealed type is 'def () -> builtins.object'

def h(self, t: Type[B]) -> None:
reveal_type(super(t, self).f) # E: Revealed type is 'def () -> builtins.object'
[builtins fixtures/classmethod.pyi]

[case testSuperWithTypeTypeAsSecondArgument]
class B:
def f(self) -> None: pass

class C(B):
def __new__(cls) -> 'C':
super(C, cls).f
return C()

[case testSuperWithGenericSelf]
from typing import TypeVar

T = TypeVar('T', bound='C')

class B:
def f(self) -> float: pass

class C(B):
def f(self) -> int: pass

def g(self: T) -> T:
reveal_type(super(C, self).f) # E: Revealed type is 'def () -> builtins.float'
return self

[case testSuperWithTypeVarValues1]
from typing import TypeVar

T = TypeVar('T', 'C', 'D')
S = TypeVar('S', 'B', 'C')

class B:
def f(self) -> None: pass

class C(B):
def f(self) -> None: pass

def g(self, x: T, y: S) -> None:
super(C, x).f
super(C, y).f # E: Argument 2 for "super" not an instance of argument 1

class D(C): pass

[case testSuperWithTypeVarValues2]
from typing import TypeVar, Generic

T = TypeVar('T', 'C', 'D')
S = TypeVar('S', 'B', 'C')

class B:
def f(self) -> None: pass

class C(B, Generic[T, S]):
def f(self) -> None: pass

def g(self, x: T, y: S) -> None:
super(C, x).f
super(C, y).f # E: Argument 2 for "super" not an instance of argument 1

class D(C): pass


-- Invalid uses of super()
-- -----------------------


[case testSuperOutsideMethodNoCrash]
class C:
a = super().whatever # E: super() outside of a method is not supported

[case testSuperWithSingleArgument]
class B:
def f(self) -> None: pass
class C(B):
def __init__(self) -> None:
super(C).f() # E: "super" with a single argument not supported

[case testSuperWithThreeArguments]
class B:
def f(self) -> None: pass
class C(B):
def h(self) -> None:
super(C, self, 1).f() # E: Too many arguments for "super"

[case testSuperWithNonPositionalArguments]
class B:
def f(self) -> None: pass
class C(B):
def h(self) -> None:
super(C, x=self).f() # E: "super" only accepts positional arguments
super(**{}).f() # E: "super" only accepts positional arguments

[case testSuperWithVarArgs]
class B:
def f(self) -> None: pass
class C(B):
def h(self) -> None:
super(*(C, self)).f() # E: Varargs not supported with "super"

[case testInvalidSuperArg]
class B:
def f(self) -> None: pass
class C(B):
def h(self) -> None:
super(x, y).f # E: Name 'x' is not defined # E: Name 'y' is not defined

[case testTypeErrorInSuperArg]
class B:
def f(self) -> None: pass
class C(B):
def h(self) -> None:
super(1(), self).f # E: "int" not callable
super(C, ''()).f # E: "str" not callable

[case testFlippedSuperArgs]
class B:
def f(self) -> None: pass
class C(B):
def h(self) -> None:
super(self, C).f # E: Argument 1 for "super" must be a type object; got a non-type instance

[case testInvalidFirstSuperArg]
class B:
def f(self) -> None: pass
class C(B):
def h(self) -> None:
super(None, C).f # E: Argument 1 for "super" must be a type object; got "None"

[case testInvalidSecondArgumentToSuper]
class B:
def f(self) -> None: pass
class C(B):
def h(self) -> None:
super(C, 1).f # E: Argument 2 for "super" not an instance of argument 1
super(C, None).f # E: Unsupported argument 2 for "super"

[case testSuperInMethodWithNoArguments]
class A:
def f(self) -> None: pass

class B(A):
def g() -> None: # E: Method must have at least one argument
super().f() # E: super() requires one or more positional arguments in enclosing function
def h(self) -> None:
def a() -> None:
super().f() # E: super() requires one or more positional arguments in enclosing function

[case testSuperWithUnsupportedTypeObject]
from typing import Type

class A:
def f(self) -> int: pass

class B(A):
def h(self, t: Type[None]) -> None:
super(t, self).f # E: Unsupported argument 1 for "super"