Skip to content

Selftype with TupleType #2436

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 8 commits into from
Nov 13, 2016
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
18 changes: 10 additions & 8 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,9 @@ def check_call(self, callee: Type, args: List[Expression],
callee)
elif isinstance(callee, Instance):
call_function = analyze_member_access('__call__', callee, context,
False, False, False, self.named_type,
self.not_ready_callback, self.msg, chk=self.chk)
False, False, False, self.named_type,
self.not_ready_callback, self.msg,
original_type=callee, chk=self.chk)
return self.check_call(call_function, args, arg_kinds, context, arg_names,
callable_node, arg_messages)
elif isinstance(callee, TypeVarType):
Expand Down Expand Up @@ -896,10 +897,11 @@ def analyze_ordinary_member_access(self, e: MemberExpr,
return self.analyze_ref_expr(e)
else:
# This is a reference to a non-module attribute.
return analyze_member_access(e.name, self.accept(e.expr), e,
original_type = self.accept(e.expr)
return analyze_member_access(e.name, original_type, e,
is_lvalue, False, False,
self.named_type, self.not_ready_callback, self.msg,
chk=self.chk)
original_type=original_type, chk=self.chk)

def analyze_external_member_access(self, member: str, base_type: Type,
context: Context) -> Type:
Expand All @@ -909,7 +911,7 @@ def analyze_external_member_access(self, member: str, base_type: Type,
# TODO remove; no private definitions in mypy
return analyze_member_access(member, base_type, context, False, False, False,
self.named_type, self.not_ready_callback, self.msg,
chk=self.chk)
original_type=base_type, chk=self.chk)

def visit_int_expr(self, e: IntExpr) -> Type:
"""Type check an integer literal (trivial)."""
Expand Down Expand Up @@ -1070,7 +1072,7 @@ def check_op_local(self, method: str, base_type: Type, arg: Expression,
"""
method_type = analyze_member_access(method, base_type, context, False, False, True,
self.named_type, self.not_ready_callback, local_errors,
chk=self.chk)
original_type=base_type, chk=self.chk)
return self.check_call(method_type, [arg], [nodes.ARG_POS],
context, arg_messages=local_errors)

Expand Down Expand Up @@ -1670,8 +1672,8 @@ def analyze_super(self, e: SuperExpr, is_lvalue: bool) -> Type:
is_lvalue=False, is_super=True, is_operator=False,
builtin_type=self.named_type,
not_ready_callback=self.not_ready_callback,
msg=self.msg, override_info=base, chk=self.chk,
original_type=declared_self)
msg=self.msg, override_info=base,
original_type=declared_self, chk=self.chk)
assert False, 'unreachable'
else:
# Invalid super. This has been reported by the semantic analyzer.
Expand Down
58 changes: 27 additions & 31 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ def analyze_member_access(name: str,
is_operator: bool,
builtin_type: Callable[[str], Instance],
not_ready_callback: Callable[[str, Context], None],
msg: MessageBuilder,
msg: MessageBuilder, *,
original_type: Type,
Copy link
Member

Choose a reason for hiding this comment

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

Now that you've made this mandatory and passed it explicitly everywhere, do you still need line 53 below? (original_type = original_type or typ -- I can't put a comment there.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, I was about to remove it

override_info: TypeInfo = None,
original_type: Type = None,
chk: 'mypy.checker.TypeChecker' = None) -> Type:
"""Return the type of attribute `name` of typ.

Expand All @@ -50,7 +50,6 @@ def analyze_member_access(name: str,
the fallback type, for example.
original_type is always the type used in the initial call.
"""
original_type = original_type or typ
if isinstance(typ, Instance):
if name == '__init__' and not is_super:
# Accessing __init__ in statically typed code would compromise
Expand All @@ -75,7 +74,7 @@ def analyze_member_access(name: str,
if method.is_property:
assert isinstance(method, OverloadedFuncDef)
return analyze_var(name, method.items[0].var, typ, info, node, is_lvalue, msg,
not_ready_callback)
original_type, not_ready_callback)
if is_lvalue:
msg.cant_assign_to_method(node)
signature = function_type(method, builtin_type('builtins.function'))
Expand Down Expand Up @@ -108,14 +107,15 @@ def analyze_member_access(name: str,
msg.disable_type_names += 1
results = [analyze_member_access(name, subtype, node, is_lvalue, is_super,
is_operator, builtin_type, not_ready_callback, msg,
chk=chk)
original_type=original_type, chk=chk)
for subtype in typ.items]
msg.disable_type_names -= 1
return UnionType.make_simplified_union(results)
elif isinstance(typ, TupleType):
# Actually look up from the fallback instance type.
return analyze_member_access(name, typ.fallback, node, is_lvalue, is_super,
is_operator, builtin_type, not_ready_callback, msg, chk=chk)
is_operator, builtin_type, not_ready_callback, msg,
original_type=original_type, chk=chk)
elif isinstance(typ, FunctionLike) and typ.is_type_obj():
# Class attribute.
# TODO super?
Expand Down Expand Up @@ -190,15 +190,14 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo,
builtin_type: Callable[[str], Instance],
not_ready_callback: Callable[[str, Context], None],
msg: MessageBuilder,
original_type: Type = None,
original_type: Type,
chk: 'mypy.checker.TypeChecker' = None) -> Type:
"""Analyse attribute access that does not target a method.

This is logically part of analyze_member_access and the arguments are similar.

original_type is the type of E in the expression E.var
"""
original_type = original_type or itype
# It was not a method. Try looking up a variable.
v = lookup_member_var_or_accessor(info, name, is_lvalue)

Expand All @@ -207,7 +206,8 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo,
# The associated Var node of a decorator contains the type.
v = vv.var
if isinstance(v, Var):
return analyze_var(name, v, itype, info, node, is_lvalue, msg, not_ready_callback)
return analyze_var(name, v, itype, info, node, is_lvalue, msg,
original_type, not_ready_callback)
elif isinstance(v, FuncDef):
assert False, "Did not expect a function"
elif not v and name not in ['__getattr__', '__setattr__']:
Expand Down Expand Up @@ -235,16 +235,14 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo,


def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Context,
is_lvalue: bool, msg: MessageBuilder,
not_ready_callback: Callable[[str, Context], None],
original_type: Type = None) -> Type:
is_lvalue: bool, msg: MessageBuilder, original_type: Type,
not_ready_callback: Callable[[str, Context], None]) -> Type:
"""Analyze access to an attribute via a Var node.

This is conceptually part of analyze_member_access and the arguments are similar.

original_type is the type of E in the expression E.var
"""
original_type = original_type or itype
# Found a member variable.
itype = map_instance_to_supertype(itype, var.info)
typ = var.type
Expand Down Expand Up @@ -300,7 +298,7 @@ def handle_partial_attribute_type(typ: PartialType, is_lvalue: bool, msg: Messag


def lookup_member_var_or_accessor(info: TypeInfo, name: str,
is_lvalue: bool) -> SymbolNode:
is_lvalue: bool) -> Optional[SymbolNode]:
"""Find the attribute/accessor node that refers to a member of a type."""
# TODO handle lvalues
node = info.get(name)
Expand Down Expand Up @@ -346,7 +344,7 @@ def analyze_class_attribute_access(itype: Instance,
builtin_type: Callable[[str], Instance],
not_ready_callback: Callable[[str, Context], None],
msg: MessageBuilder,
original_type: Type = None) -> Type:
original_type: Type) -> Type:
"""original_type is the type of E in the expression E.var"""
node = itype.type.get(name)
if not node:
Expand Down Expand Up @@ -391,7 +389,7 @@ def analyze_class_attribute_access(itype: Instance,

def add_class_tvars(t: Type, itype: Instance, is_classmethod: bool,
builtin_type: Callable[[str], Instance],
original_type: Type = None) -> Type:
original_type: Type) -> Type:
"""Instantiate type variables during analyze_class_attribute_access,
e.g T and Q in the following:

Expand All @@ -405,21 +403,19 @@ class B(A): pass

original_type is the value of the type B in the expression B.foo()
"""
# TODO: verify consistency betweem Q and T
# TODO: verify consistency between Q and T
info = itype.type # type: TypeInfo
if isinstance(t, CallableType):
# TODO: Should we propagate type variable values?
vars = [TypeVarDef(n, i + 1, None, builtin_type('builtins.object'), tv.variance)
for (i, n), tv in zip(enumerate(info.type_vars), info.defn.type_vars)]
tvars = [TypeVarDef(n, i + 1, None, builtin_type('builtins.object'), tv.variance)
for (i, n), tv in zip(enumerate(info.type_vars), info.defn.type_vars)]
if is_classmethod:
if not isinstance(original_type, TypeType):
original_type = TypeType(itype)
t = bind_self(t, original_type)
return t.copy_modified(variables=vars + t.variables)
return t.copy_modified(variables=tvars + t.variables)
elif isinstance(t, Overloaded):
return Overloaded([cast(CallableType, add_class_tvars(i, itype, is_classmethod,
return Overloaded([cast(CallableType, add_class_tvars(item, itype, is_classmethod,
builtin_type, original_type))
for i in t.items()])
for item in t.items()])
return t


Expand Down Expand Up @@ -573,15 +569,15 @@ class B(A): pass
return cast(F, func)
if func.arg_kinds[0] == ARG_STAR:
# The signature is of the form 'def foo(*args, ...)'.
# In this case we shouldn'func drop the first arg,
# In this case we shouldn't drop the first arg,
# since func will be absorbed by the *args.

# TODO: infer bounds on the type of *args?
return cast(F, func)
self_param_type = func.arg_types[0]
if func.variables and (isinstance(self_param_type, TypeVarType) or
(isinstance(self_param_type, TypeType) and
isinstance(self_param_type.item, TypeVarType))):
(isinstance(self_param_type, TypeType) and
isinstance(self_param_type.item, TypeVarType))):
if original_type is None:
# Type check method override
# XXX value restriction as union?
Expand All @@ -601,10 +597,10 @@ def expand(target: Type) -> Type:
ret_type = func.ret_type
variables = func.variables
res = func.copy_modified(arg_types=arg_types,
arg_kinds=func.arg_kinds[1:],
arg_names=func.arg_names[1:],
variables=variables,
ret_type=ret_type)
arg_kinds=func.arg_kinds[1:],
arg_names=func.arg_names[1:],
variables=variables,
ret_type=ret_type)
return cast(F, res)


Expand Down
30 changes: 20 additions & 10 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
from mypy.errors import Errors, report_internal_error
from mypy.types import (
NoneTyp, CallableType, Overloaded, Instance, Type, TypeVarType, AnyType,
FunctionLike, UnboundType, TypeList, TypeVarDef,
FunctionLike, UnboundType, TypeList, TypeVarDef, TypeType,
TupleType, UnionType, StarType, EllipsisType, function_type)
from mypy.nodes import implicit_module_attrs
from mypy.typeanal import TypeAnalyser, TypeAnalyserPass3, analyze_type_alias
Expand Down Expand Up @@ -1799,31 +1799,41 @@ def add_field(var: Var, is_initialized_in_class: bool = False,
add_field(Var('_field_types', dictype), is_initialized_in_class=True)
add_field(Var('_source', strtype), is_initialized_in_class=True)

# TODO: SelfType should be bind to actual 'self'
this_type = fill_typevars(info)
tvd = TypeVarDef('NT', 1, [], info.tuple_type)
selftype = TypeVarType(tvd)

def add_method(funcname: str, ret: Type, args: List[Argument], name=None,
is_classmethod=False) -> None:
if not is_classmethod:
args = [Argument(Var('self'), this_type, None, ARG_POS)] + args
if is_classmethod:
first = [Argument(Var('cls'), TypeType(selftype), None, ARG_POS)]
else:
first = [Argument(Var('self'), selftype, None, ARG_POS)]
args = first + args

types = [arg.type_annotation for arg in args]
items = [arg.variable.name() for arg in args]
arg_kinds = [arg.kind for arg in args]
signature = CallableType(types, arg_kinds, items, ret, function_type,
name=name or info.name() + '.' + funcname)
signature.is_classmethod_class = is_classmethod
signature.variables = [tvd]
func = FuncDef(funcname, args, Block([]), typ=signature)
func.info = info
func.is_class = is_classmethod
info.names[funcname] = SymbolTableNode(MDEF, func)
if is_classmethod:
v = Var(funcname, signature)
v.is_classmethod = True
v.info = info
dec = Decorator(func, [NameExpr('classmethod')], v)
info.names[funcname] = SymbolTableNode(MDEF, dec)
else:
info.names[funcname] = SymbolTableNode(MDEF, func)

add_method('_replace', ret=this_type,
add_method('_replace', ret=selftype,
args=[Argument(var, var.type, EllipsisExpr(), ARG_NAMED) for var in vars])
add_method('__init__', ret=NoneTyp(), name=info.name(),
args=[Argument(var, var.type, None, ARG_POS) for var in vars])
add_method('_asdict', args=[], ret=ordereddictype)
# FIX: make it actual class method
add_method('_make', ret=this_type, is_classmethod=True,
add_method('_make', ret=selftype, is_classmethod=True,
args=[Argument(Var('iterable', iterable_type), iterable_type, None, ARG_POS),
Argument(Var('new'), AnyType(), EllipsisExpr(), ARG_NAMED),
Argument(Var('len'), AnyType(), EllipsisExpr(), ARG_NAMED)])
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-isinstance.test
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ def f(x: Union[List[int], List[str], int]) -> None:
else:
x[0] # E: Value of type "int" is not indexable
x + 1
x[0] # E: Value of type "int" is not indexable
x[0] # E: Value of type "Union[List[int], List[str], int]" is not indexable
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 guessing this is because of the better determination of original_type right? I approve of the new error message.

x + 1 # E: Unsupported operand types for + (likely involving Union)
[builtins fixtures/isinstancelist.pyi]
[out]
Expand Down
50 changes: 48 additions & 2 deletions test-data/unit/check-namedtuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -283,15 +283,14 @@ reveal_type(x._replace()) # E: Revealed type is 'Tuple[builtins.int, builtins.s
x._replace(x=5)
x._replace(y=5) # E: Argument 1 to X._replace has incompatible type "int"; expected "str"


[case testNamedTupleMake]
from typing import NamedTuple

X = NamedTuple('X', [('x', int), ('y', str)])
reveal_type(X._make([5, 'a'])) # E: Revealed type is 'Tuple[builtins.int, builtins.str, fallback=__main__.X]'
X._make('a b') # E: Argument 1 to X._make has incompatible type "str"; expected Iterable[Any]

-- # FIX: not a proper class method
-- # FIX: not a proper class method
-- x = None # type: X
-- reveal_type(x._make([5, 'a'])) # E: Revealed type is 'Tuple[builtins.int, builtins.str, fallback=__main__.X]'
-- x._make('a b') # E: Argument 1 to X._make has incompatible type "str"; expected Iterable[Any]
Expand Down Expand Up @@ -367,3 +366,50 @@ class D(NamedTuple('D', []), A): pass
g(D()) # E: Argument 1 to "g" has incompatible type "D"; expected "C"
y = None # type: C
y = D() # E: Incompatible types in assignment (expression has type "D", variable has type "C")

[case testNamedTupleSelfTypeMethod]
from typing import TypeVar, NamedTuple

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

class A(NamedTuple('A', [('x', str)])):
def member(self: T) -> T:
return self

class B(A):
pass

a = None # type: A
a = A('').member()
b = None # type: B
b = B('').member()
a = B('')
a = B('').member()

[case testNamedTupleSelfTypeReplace]
from typing import NamedTuple, TypeVar
A = NamedTuple('A', [('x', str)])
reveal_type(A('hello')._replace(x='')) # E: Revealed type is 'Tuple[builtins.str, fallback=__main__.A]'
a = None # type: A
a = A('hello')._replace(x='')

class B(A):
pass

reveal_type(B('hello')._replace(x='')) # E: Revealed type is 'Tuple[builtins.str, fallback=__main__.B]'
b = None # type: B
b = B('hello')._replace(x='')

[case testNamedTupleSelfTypeMake]
from typing import NamedTuple, TypeVar
A = NamedTuple('A', [('x', str)])
reveal_type(A._make([''])) # E: Revealed type is 'Tuple[builtins.str, fallback=__main__.A]'
a = A._make(['']) # type: A

class B(A):
pass

reveal_type(B._make([''])) # E: Revealed type is 'Tuple[builtins.str, fallback=__main__.B]'
b = B._make(['']) # type: B

[builtins fixtures/list.pyi]
6 changes: 3 additions & 3 deletions test-data/unit/check-tuples.test
Original file line number Diff line number Diff line change
Expand Up @@ -559,10 +559,10 @@ main:15: error: Incompatible types in assignment (expression has type "Tuple[A,

a = None # type: A

(a, a) + a # E: Unsupported left operand type for + (Tuple[A, ...])
(a, a) + a # E: Unsupported left operand type for + ("Tuple[A, A]")
a + (a, a) # E: Unsupported operand types for + ("A" and "Tuple[A, A]")
f((a, a)) # E: Argument 1 to "f" has incompatible type "Tuple[A, A]"; expected "A"
(a, a).foo # E: Tuple[A, ...] has no attribute "foo"
(a, a).foo # E: "Tuple[A, A]" has no attribute "foo"

def f(x: 'A') -> None: pass

Expand Down Expand Up @@ -596,7 +596,7 @@ b = bool()
s = t.__len__() # E: Incompatible types in assignment (expression has type "int", variable has type "str")
i = t.__str__() # E: Incompatible types in assignment (expression has type "str", variable has type "int")
i = s in t # E: Incompatible types in assignment (expression has type "bool", variable has type "int")
t.foo # E: Tuple[Any, ...] has no attribute "foo"
t.foo # E: "Tuple[int, str]" has no attribute "foo"

i = t.__len__()
s = t.__str__()
Expand Down