From bd54406daae241cd0edc8cc70ada0e2021c942e6 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 27 Jun 2019 12:45:41 +0100 Subject: [PATCH 1/5] Add tests --- test-data/unit/check-newsemanal.test | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 6c7af2498c8d..ca6b3d2c4a67 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -2716,3 +2716,36 @@ class N(NamedTuple): ) b [builtins fixtures/tuple.pyi] + +[case testNewAnalyzerLessErrorsNeedAnnotation] +from typing import TypeVar, Optional + +T = TypeVar('T') + +def f(x: Optional[T] = None) -> T: ... + +x = f() # E: Need type annotation for 'x' +y = x +reveal_type(y) + +def g() -> None: + x = f() # E: Need type annotation for 'x' + y = x + reveal_type(y) + +[case testNewAnalyzerLessErrorsNeedAnnotationNested] +from typing import TypeVar, Optional, Generic + +T = TypeVar('T') +class G(Generic[T]): ... + +def f(x: Optional[T] = None) -> G[T]: ... + +x = f() # E: Need type annotation for 'x' +y = x +reveal_type(y) + +def g() -> None: + x = f() # E: Need type annotation for 'x' + y = x + reveal_type(y) From e2d99bbeab91c21ce7e8d636cd6186b53ef6b001 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 27 Jun 2019 13:45:14 +0100 Subject: [PATCH 2/5] Actual fix --- mypy/checker.py | 44 ++++++++++++---------------- test-data/unit/check-newsemanal.test | 6 ++-- 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 8720b4995f36..2718545bc1d8 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -34,7 +34,7 @@ Instance, NoneType, strip_type, TypeType, TypeOfAny, UnionType, TypeVarId, TypeVarType, PartialType, DeletedType, UninhabitedType, TypeVarDef, true_only, false_only, function_type, is_named_instance, union_items, TypeQuery, LiteralType, - is_optional, remove_optional + is_optional, remove_optional, TypeTranslator ) from mypy.sametypes import is_same_type from mypy.messages import MessageBuilder, make_inferred_type_note, append_invariance_notes @@ -2520,7 +2520,7 @@ def infer_variable_type(self, name: Var, lvalue: Lvalue, # gets generated in assignment like 'x = []' where item type is not known. if not self.infer_partial_type(name, lvalue, init_type): self.msg.need_annotation_for_var(name, context, self.options.python_version) - self.set_inference_error_fallback_type(name, lvalue, init_type, context) + self.set_inference_error_fallback_type(name, lvalue, init_type) elif (isinstance(lvalue, MemberExpr) and self.inferred_attribute_types is not None and lvalue.def_var and lvalue.def_var in self.inferred_attribute_types and not is_same_type(self.inferred_attribute_types[lvalue.def_var], init_type)): @@ -2569,9 +2569,8 @@ def set_inferred_type(self, var: Var, lvalue: Lvalue, type: Type) -> 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, - context: Context) -> None: - """If errors on context line are ignored, store dummy type for variable. + def set_inference_error_fallback_type(self, var: Var, lvalue: Lvalue, type: Type) -> None: + """Store best known type for variable if type inference failed. If a program ignores error on type inference error, the variable should get some inferred type so that if can used later on in the program. Example: @@ -2579,10 +2578,9 @@ def set_inference_error_fallback_type(self, var: Var, lvalue: Lvalue, type: Type x = [] # type: ignore x.append(1) # Should be ok! - We implement this here by giving x a valid type (Any). + We implement this here by giving x a valid type (replacing inferred with Any). """ - if context.get_line() in self.errors.ignored_lines[self.errors.file]: - self.set_inferred_type(var, lvalue, AnyType(TypeOfAny.from_error)) + self.set_inferred_type(var, lvalue, type.accept(SetNothingToAny())) def check_simple_assignment(self, lvalue_type: Optional[Type], rvalue: Expression, context: Context, @@ -4300,26 +4298,22 @@ def is_valid_inferred_type(typ: Type) -> bool: # specific Optional type. This resolution happens in # leave_partial_types when we pop a partial types scope. return False - return is_valid_inferred_type_component(typ) + return not typ.accept(NothingSeeker()) -def is_valid_inferred_type_component(typ: Type) -> bool: - """Is this part of a type a valid inferred type? +class NothingSeeker(TypeQuery[bool]): + def __init__(self) -> None: + super().__init__(any) - In strict Optional mode this excludes bare None types, as otherwise every - type containing None would be invalid. - """ - if is_same_type(typ, UninhabitedType()): - return False - elif isinstance(typ, Instance): - for arg in typ.args: - if not is_valid_inferred_type_component(arg): - return False - elif isinstance(typ, TupleType): - for item in typ.items: - if not is_valid_inferred_type_component(item): - return False - return True + def visit_uninhabited_type(self, t: UninhabitedType) -> bool: + return t.ambiguous + + +class SetNothingToAny(TypeTranslator): + def visit_uninhabited_type(self, t: UninhabitedType) -> Type: + if t.ambiguous: + return AnyType(TypeOfAny.from_error) + return t def is_node_static(node: Optional[Node]) -> Optional[bool]: diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index ca6b3d2c4a67..f354ab231583 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -2726,12 +2726,10 @@ def f(x: Optional[T] = None) -> T: ... x = f() # E: Need type annotation for 'x' y = x -reveal_type(y) def g() -> None: x = f() # E: Need type annotation for 'x' y = x - reveal_type(y) [case testNewAnalyzerLessErrorsNeedAnnotationNested] from typing import TypeVar, Optional, Generic @@ -2743,9 +2741,9 @@ def f(x: Optional[T] = None) -> G[T]: ... x = f() # E: Need type annotation for 'x' y = x -reveal_type(y) +reveal_type(y) # N: Revealed type is '__main__.G[Any]' def g() -> None: x = f() # E: Need type annotation for 'x' y = x - reveal_type(y) + reveal_type(y) # N: Revealed type is '__main__.G[Any]' From 35728e194e276d110e10bb0977f75a6a76bd0d7a Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 27 Jun 2019 14:08:45 +0100 Subject: [PATCH 3/5] Update tests --- test-data/unit/check-generics.test | 2 +- test-data/unit/check-inference.test | 14 +++++--------- test-data/unit/check-literal.test | 2 +- test-data/unit/fine-grained-cycles.test | 3 +-- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 6a3f15da476e..3cb4564c42b7 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -996,7 +996,7 @@ IntNode[int](1, 1) IntNode[int](1, 'a') # E: Argument 2 to "Node" has incompatible type "str"; expected "int" SameNode = Node[T, T] -ff = SameNode[T](1, 1) # E: Need type annotation for 'ff' +ff = SameNode[T](1, 1) a = SameNode(1, 'x') reveal_type(a) # N: Revealed type is '__main__.Node[Any, Any]' b = SameNode[int](1, 1) diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index cdd23f3e6cca..fc6224c92834 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -440,8 +440,7 @@ a = None # type: A def ff() -> None: x = f() # E: Need type annotation for 'x' - reveal_type(x) # N: Revealed type is 'Any' \ - # E: Cannot determine type of 'x' + reveal_type(x) # N: Revealed type is 'Any' g(None) # Ok f() # Ok because not used to infer local variable type @@ -969,9 +968,8 @@ for x in [A()]: a = x for y in []: # E: Need type annotation for 'y' - a = y # E: Cannot determine type of 'y' - reveal_type(y) # N: Revealed type is 'Any' \ - # E: Cannot determine type of 'y' + a = y + reveal_type(y) # N: Revealed type is 'Any' class A: pass class B: pass @@ -1017,10 +1015,8 @@ for x, y in [[A()]]: for e, f in [[]]: # E: Need type annotation for 'e' \ # E: Need type annotation for 'f' - reveal_type(e) # N: Revealed type is 'Any' \ - # E: Cannot determine type of 'e' - reveal_type(f) # N: Revealed type is 'Any' \ - # E: Cannot determine type of 'f' + reveal_type(e) # N: Revealed type is 'Any' + reveal_type(f) # N: Revealed type is 'Any' class A: pass class B: pass diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index 54891124582d..1dd61f2d3d86 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -700,7 +700,7 @@ def f() -> NotAType['also' + 'not' + 'a' + 'type']: ... # E: Invalid type "__mai # Note: this makes us re-inspect the type (e.g. via '_patch_indirect_dependencies' # in build.py) so we can confirm the RawExpressionType did not leak out. -indirect = f() # E: Need type annotation for 'indirect' +indirect = f() [out] -- diff --git a/test-data/unit/fine-grained-cycles.test b/test-data/unit/fine-grained-cycles.test index 9995963b1d64..e4206496b589 100644 --- a/test-data/unit/fine-grained-cycles.test +++ b/test-data/unit/fine-grained-cycles.test @@ -205,8 +205,7 @@ def h() -> None: [out] == a.py:3: error: Invalid type "b.C" -b.py:6: error: Need type annotation for 'c' -b.py:7: error: Cannot determine type of 'c' +b.py:7: error: C? has no attribute "g" -- TODO: More import cycle: -- From 009f47835b3c85d305261b1535420abe1a685960 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 27 Jun 2019 14:54:28 +0100 Subject: [PATCH 4/5] Some more docs and tests --- mypy/checker.py | 2 ++ test-data/unit/check-inference.test | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 2718545bc1d8..471da0476ed7 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4302,6 +4302,7 @@ def is_valid_inferred_type(typ: Type) -> bool: class NothingSeeker(TypeQuery[bool]): + """Find any types resulting from failed (ambiguous) type inference.""" def __init__(self) -> None: super().__init__(any) @@ -4310,6 +4311,7 @@ def visit_uninhabited_type(self, t: UninhabitedType) -> bool: class SetNothingToAny(TypeTranslator): + """Replace all ambiguous types with Any (to avoid spurious extra errors).""" def visit_uninhabited_type(self, t: UninhabitedType) -> Type: if t.ambiguous: return AnyType(TypeOfAny.from_error) diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index fc6224c92834..f80134b74930 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -2720,3 +2720,23 @@ class A: class B(A): x = None # E: Incompatible types in assignment (expression has type "None", base class "A" defined the type as "str") x = '' + +[case testNeedAnnotationForCallable] +from typing import TypeVar, Optional, Callable + +T = TypeVar('T') + +def f(x: Optional[T] = None) -> Callable[..., T]: ... + +x = f() # E: Need type annotation for 'x' +y = x + +[case testDontNeedAnnotationForCallable] +from typing import TypeVar, Optional, Callable, NoReturn + +T = TypeVar('T') + +def f() -> Callable[..., NoReturn]: ... + +x = f() +reveal_type(x) # N: Revealed type is 'def (*Any, **Any) -> ' From 2848f78b49dec021dd31ff5742309dd388b57ea8 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 27 Jun 2019 16:37:47 +0100 Subject: [PATCH 5/5] Address CR --- mypy/checker.py | 2 ++ mypy/newsemanal/typeanal.py | 3 ++- test-data/unit/check-generics.test | 1 + test-data/unit/check-newsemanal.test | 9 +++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 471da0476ed7..72881b267c94 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -4303,6 +4303,7 @@ def is_valid_inferred_type(typ: Type) -> bool: class NothingSeeker(TypeQuery[bool]): """Find any types resulting from failed (ambiguous) type inference.""" + def __init__(self) -> None: super().__init__(any) @@ -4312,6 +4313,7 @@ def visit_uninhabited_type(self, t: UninhabitedType) -> bool: class SetNothingToAny(TypeTranslator): """Replace all ambiguous types with Any (to avoid spurious extra errors).""" + def visit_uninhabited_type(self, t: UninhabitedType) -> Type: if t.ambiguous: return AnyType(TypeOfAny.from_error) diff --git a/mypy/newsemanal/typeanal.py b/mypy/newsemanal/typeanal.py index b16cce09f2e8..9af58c4331d6 100644 --- a/mypy/newsemanal/typeanal.py +++ b/mypy/newsemanal/typeanal.py @@ -406,7 +406,8 @@ def analyze_unbound_type_without_type_info(self, t: UnboundType, sym: SymbolTabl # TODO: Would it be better to always return Any instead of UnboundType # in case of an error? On one hand, UnboundType has a name so error messages - # are more detailed, on the other hand, some of them may be bogus. + # are more detailed, on the other hand, some of them may be bogus, + # see https://github.com/python/mypy/issues/4987. return t def visit_any(self, t: AnyType) -> Type: diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 3cb4564c42b7..bce3bed30c9d 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -996,6 +996,7 @@ IntNode[int](1, 1) IntNode[int](1, 'a') # E: Argument 2 to "Node" has incompatible type "str"; expected "int" SameNode = Node[T, T] +# TODO: fix https://github.com/python/mypy/issues/7084. ff = SameNode[T](1, 1) a = SameNode(1, 'x') reveal_type(a) # N: Revealed type is '__main__.Node[Any, Any]' diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index f354ab231583..99d3beea4fa3 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -2731,6 +2731,15 @@ def g() -> None: x = f() # E: Need type annotation for 'x' y = x +[case testNewAnalyzerLessErrorsNeedAnnotationList] +x = [] # type: ignore +reveal_type(x) # N: Revealed type is 'builtins.list[Any]' + +def g() -> None: + x = [] # type: ignore + reveal_type(x) # N: Revealed type is 'builtins.list[Any]' +[builtins fixtures/list.pyi] + [case testNewAnalyzerLessErrorsNeedAnnotationNested] from typing import TypeVar, Optional, Generic