diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 2862aff08071..32cbbcbc34e1 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -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): @@ -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: @@ -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).""" @@ -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) @@ -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. diff --git a/mypy/checkmember.py b/mypy/checkmember.py index f7dafc968aad..9a55103f9215 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -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, override_info: TypeInfo = None, - original_type: Type = None, chk: 'mypy.checker.TypeChecker' = None) -> Type: """Return the type of attribute `name` of typ. @@ -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 @@ -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')) @@ -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? @@ -190,7 +190,7 @@ 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. @@ -198,7 +198,6 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, 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) @@ -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__']: @@ -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 @@ -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) @@ -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: @@ -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: @@ -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 @@ -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? @@ -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) diff --git a/mypy/semanal.py b/mypy/semanal.py index e22caf41c859..09f69b0caa00 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -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 @@ -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)]) diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 26d58c83cbf0..967747be5196 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -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 x + 1 # E: Unsupported operand types for + (likely involving Union) [builtins fixtures/isinstancelist.pyi] [out] diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index c41cc5a916fd..650c2101e33e 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -283,7 +283,6 @@ 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 @@ -291,7 +290,7 @@ 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] @@ -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] diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 496224af0990..bdd3805f6c24 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -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 @@ -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__()