diff --git a/mypy/applytype.py b/mypy/applytype.py index 894b03a09627..db93c468f2ed 100644 --- a/mypy/applytype.py +++ b/mypy/applytype.py @@ -23,6 +23,7 @@ def apply_generic_arguments(callable: CallableType, orig_types: Sequence[Optiona # values and bounds. Also, promote subtype values to allowed values. types = list(orig_types) for i, type in enumerate(types): + assert not isinstance(type, PartialType), "Internal error: must never apply partial type" values = callable.variables[i].values if values and type: if isinstance(type, AnyType): @@ -34,14 +35,13 @@ def apply_generic_arguments(callable: CallableType, orig_types: Sequence[Optiona for v1 in type.values): continue for value in values: - if isinstance(type, PartialType) or mypy.subtypes.is_subtype(type, value): + if mypy.subtypes.is_subtype(type, value): types[i] = value break else: msg.incompatible_typevar_value(callable, type, callable.variables[i].name, context) upper_bound = callable.variables[i].upper_bound - if (type and not isinstance(type, PartialType) and - not mypy.subtypes.is_subtype(type, upper_bound)): + if type and not mypy.subtypes.is_subtype(type, upper_bound): msg.incompatible_typevar_value(callable, type, callable.variables[i].name, context) # Create a map from type variable id to target type. diff --git a/mypy/checker.py b/mypy/checker.py index 3fcc3c665e12..12baa465c835 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -110,6 +110,9 @@ class TypeChecker(NodeVisitor[None], CheckerPluginInterface): dynamic_funcs = None # type: List[bool] # Stack of collections of variables with partial types partial_types = None # type: List[Dict[Var, Context]] + # Vars for which partial type errors are already reported + # (to avoid logically duplicate errors with different error context). + partial_reported = None # type: Set[Var] globals = None # type: SymbolTable modules = None # type: Dict[str, MypyFile] # Nodes that couldn't be checked because some types weren't available. We'll run @@ -157,6 +160,7 @@ def __init__(self, errors: Errors, modules: Dict[str, MypyFile], options: Option self.return_types = [] self.dynamic_funcs = [] self.partial_types = [] + self.partial_reported = set() self.deferred_nodes = [] self.type_map = {} self.module_refs = set() @@ -1681,7 +1685,7 @@ def lvalue_type_for_inference(self, lvalues: List[Lvalue], rvalue_type: TupleTyp def append_types_for_inference(lvs: List[Expression], rv_types: List[Type]) -> None: for lv, rv_type in zip(lvs, rv_types): sub_lvalue_type, index_expr, inferred = self.check_lvalue(lv) - if sub_lvalue_type: + if sub_lvalue_type and not isinstance(sub_lvalue_type, PartialType): type_parameters.append(sub_lvalue_type) else: # index lvalue # TODO Figure out more precise type context, probably @@ -1692,7 +1696,7 @@ def append_types_for_inference(lvs: List[Expression], rv_types: List[Type]) -> N if star_lv: sub_lvalue_type, index_expr, inferred = self.check_lvalue(star_lv.expr) - if sub_lvalue_type: + if sub_lvalue_type and not isinstance(sub_lvalue_type, PartialType): type_parameters.extend([sub_lvalue_type] * len(star_rv_types)) else: # index lvalue # TODO Figure out more precise type context, probably @@ -1845,8 +1849,8 @@ def set_inferred_type(self, var: Var, lvalue: Lvalue, type: Type) -> None: var.is_inferred = True if isinstance(lvalue, MemberExpr) and self.inferred_attribute_types is not None: # Store inferred attribute type so that we can check consistency afterwards. - assert lvalue.def_var is not None - self.inferred_attribute_types[lvalue.def_var] = type + if lvalue.def_var is not None: + self.inferred_attribute_types[lvalue.def_var] = type self.store_type(lvalue, type) def set_inference_error_fallback_type(self, var: Var, lvalue: Lvalue, type: Type, @@ -2655,9 +2659,10 @@ def enter_partial_types(self) -> Iterator[None]: if isinstance(var.type, PartialType) and var.type.type is None: # None partial type: assume variable is intended to have type None var.type = NoneTyp() - else: + elif var not in self.partial_reported: self.msg.fail(messages.NEED_ANNOTATION_FOR_VAR, context) var.type = AnyType(TypeOfAny.from_error) + self.partial_reported.add(var) def find_partial_types(self, var: Var) -> Optional[Dict[Var, Context]]: for partial_types in reversed(self.partial_types): diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 9fa918782b2a..3549a27716a2 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -1887,3 +1887,43 @@ C[0] = 0 [out] main:4: error: Type expected within [...] main:4: error: Unsupported target for indexed assignment + +[case testNoCrashOnPartialMember] +class C: + x = None + def __init__(self) -> None: + self.x = [] # E: Need type annotation for variable +[builtins fixtures/list.pyi] +[out] + +[case testNoCrashOnPartialVariable] +from typing import Tuple, TypeVar +T = TypeVar('T', bound=str) + +def f(x: T) -> Tuple[T]: + ... +x = None +(x,) = f('') +reveal_type(x) # E: Revealed type is 'builtins.str' +[out] + +[case testNoCrashOnPartialVariable2] +from typing import Tuple, TypeVar +T = TypeVar('T', bound=str) + +def f() -> Tuple[T]: + ... +x = None +(x,) = f() +[out] + +[case testNoCrashOnPartialVariable3] +from typing import Tuple, TypeVar +T = TypeVar('T') + +def f(x: T) -> Tuple[T, T]: + ... +x = None +(x, x) = f('') +reveal_type(x) # E: Revealed type is 'builtins.str' +[out]