Skip to content

Commit bbd732f

Browse files
authored
Make type inference failures more consistent (#7079)
Fixes #6998 This PR makes several related changes: * Reserve `Cannot determine type` error for cases where node deferral failed etc (for example with runtime recursive definitions); don't show it after failed (ambiguous) type inference. * Always set reasonable variable type after failed inference (like `List[Any]` for an empty list). * Always give `Need type annotation for variable` error, not just for instances and tuples (that was ad-hoc IMO) This may actually give some _new_ errors as compared to status quo, but as one can see from the diff, the result is negative in number of (redundant) errors. There are couple changes in tests that look unrelated, these are because `is_same_type(..., UnboundType())` returns `True` for everything, including `UninhabitedType`. I would say we should actually avoid leaking unbound types from semantic analysis and replace the, with `Any`, but this is a separate issue.
1 parent 9438211 commit bbd732f

File tree

7 files changed

+94
-39
lines changed

7 files changed

+94
-39
lines changed

mypy/checker.py

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
Instance, NoneType, strip_type, TypeType, TypeOfAny,
3535
UnionType, TypeVarId, TypeVarType, PartialType, DeletedType, UninhabitedType, TypeVarDef,
3636
true_only, false_only, function_type, is_named_instance, union_items, TypeQuery, LiteralType,
37-
is_optional, remove_optional
37+
is_optional, remove_optional, TypeTranslator
3838
)
3939
from mypy.sametypes import is_same_type
4040
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,
25202520
# gets generated in assignment like 'x = []' where item type is not known.
25212521
if not self.infer_partial_type(name, lvalue, init_type):
25222522
self.msg.need_annotation_for_var(name, context, self.options.python_version)
2523-
self.set_inference_error_fallback_type(name, lvalue, init_type, context)
2523+
self.set_inference_error_fallback_type(name, lvalue, init_type)
25242524
elif (isinstance(lvalue, MemberExpr) and self.inferred_attribute_types is not None
25252525
and lvalue.def_var and lvalue.def_var in self.inferred_attribute_types
25262526
and not is_same_type(self.inferred_attribute_types[lvalue.def_var], init_type)):
@@ -2569,20 +2569,18 @@ def set_inferred_type(self, var: Var, lvalue: Lvalue, type: Type) -> None:
25692569
self.inferred_attribute_types[lvalue.def_var] = type
25702570
self.store_type(lvalue, type)
25712571

2572-
def set_inference_error_fallback_type(self, var: Var, lvalue: Lvalue, type: Type,
2573-
context: Context) -> None:
2574-
"""If errors on context line are ignored, store dummy type for variable.
2572+
def set_inference_error_fallback_type(self, var: Var, lvalue: Lvalue, type: Type) -> None:
2573+
"""Store best known type for variable if type inference failed.
25752574
25762575
If a program ignores error on type inference error, the variable should get some
25772576
inferred type so that if can used later on in the program. Example:
25782577
25792578
x = [] # type: ignore
25802579
x.append(1) # Should be ok!
25812580
2582-
We implement this here by giving x a valid type (Any).
2581+
We implement this here by giving x a valid type (replacing inferred <nothing> with Any).
25832582
"""
2584-
if context.get_line() in self.errors.ignored_lines[self.errors.file]:
2585-
self.set_inferred_type(var, lvalue, AnyType(TypeOfAny.from_error))
2583+
self.set_inferred_type(var, lvalue, type.accept(SetNothingToAny()))
25862584

25872585
def check_simple_assignment(self, lvalue_type: Optional[Type], rvalue: Expression,
25882586
context: Context,
@@ -4305,26 +4303,26 @@ def is_valid_inferred_type(typ: Type) -> bool:
43054303
# specific Optional type. This resolution happens in
43064304
# leave_partial_types when we pop a partial types scope.
43074305
return False
4308-
return is_valid_inferred_type_component(typ)
4306+
return not typ.accept(NothingSeeker())
43094307

43104308

4311-
def is_valid_inferred_type_component(typ: Type) -> bool:
4312-
"""Is this part of a type a valid inferred type?
4309+
class NothingSeeker(TypeQuery[bool]):
4310+
"""Find any <nothing> types resulting from failed (ambiguous) type inference."""
43134311

4314-
In strict Optional mode this excludes bare None types, as otherwise every
4315-
type containing None would be invalid.
4316-
"""
4317-
if is_same_type(typ, UninhabitedType()):
4318-
return False
4319-
elif isinstance(typ, Instance):
4320-
for arg in typ.args:
4321-
if not is_valid_inferred_type_component(arg):
4322-
return False
4323-
elif isinstance(typ, TupleType):
4324-
for item in typ.items:
4325-
if not is_valid_inferred_type_component(item):
4326-
return False
4327-
return True
4312+
def __init__(self) -> None:
4313+
super().__init__(any)
4314+
4315+
def visit_uninhabited_type(self, t: UninhabitedType) -> bool:
4316+
return t.ambiguous
4317+
4318+
4319+
class SetNothingToAny(TypeTranslator):
4320+
"""Replace all ambiguous <nothing> types with Any (to avoid spurious extra errors)."""
4321+
4322+
def visit_uninhabited_type(self, t: UninhabitedType) -> Type:
4323+
if t.ambiguous:
4324+
return AnyType(TypeOfAny.from_error)
4325+
return t
43284326

43294327

43304328
def is_node_static(node: Optional[Node]) -> Optional[bool]:

mypy/newsemanal/typeanal.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,8 @@ def analyze_unbound_type_without_type_info(self, t: UnboundType, sym: SymbolTabl
406406

407407
# TODO: Would it be better to always return Any instead of UnboundType
408408
# in case of an error? On one hand, UnboundType has a name so error messages
409-
# are more detailed, on the other hand, some of them may be bogus.
409+
# are more detailed, on the other hand, some of them may be bogus,
410+
# see https://github.com/python/mypy/issues/4987.
410411
return t
411412

412413
def visit_any(self, t: AnyType) -> Type:

test-data/unit/check-generics.test

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -996,7 +996,8 @@ IntNode[int](1, 1)
996996
IntNode[int](1, 'a') # E: Argument 2 to "Node" has incompatible type "str"; expected "int"
997997

998998
SameNode = Node[T, T]
999-
ff = SameNode[T](1, 1) # E: Need type annotation for 'ff'
999+
# TODO: fix https://github.com/python/mypy/issues/7084.
1000+
ff = SameNode[T](1, 1)
10001001
a = SameNode(1, 'x')
10011002
reveal_type(a) # N: Revealed type is '__main__.Node[Any, Any]'
10021003
b = SameNode[int](1, 1)

test-data/unit/check-inference.test

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -440,8 +440,7 @@ a = None # type: A
440440

441441
def ff() -> None:
442442
x = f() # E: Need type annotation for 'x'
443-
reveal_type(x) # N: Revealed type is 'Any' \
444-
# E: Cannot determine type of 'x'
443+
reveal_type(x) # N: Revealed type is 'Any'
445444

446445
g(None) # Ok
447446
f() # Ok because not used to infer local variable type
@@ -969,9 +968,8 @@ for x in [A()]:
969968
a = x
970969

971970
for y in []: # E: Need type annotation for 'y'
972-
a = y # E: Cannot determine type of 'y'
973-
reveal_type(y) # N: Revealed type is 'Any' \
974-
# E: Cannot determine type of 'y'
971+
a = y
972+
reveal_type(y) # N: Revealed type is 'Any'
975973

976974
class A: pass
977975
class B: pass
@@ -1017,10 +1015,8 @@ for x, y in [[A()]]:
10171015

10181016
for e, f in [[]]: # E: Need type annotation for 'e' \
10191017
# E: Need type annotation for 'f'
1020-
reveal_type(e) # N: Revealed type is 'Any' \
1021-
# E: Cannot determine type of 'e'
1022-
reveal_type(f) # N: Revealed type is 'Any' \
1023-
# E: Cannot determine type of 'f'
1018+
reveal_type(e) # N: Revealed type is 'Any'
1019+
reveal_type(f) # N: Revealed type is 'Any'
10241020

10251021
class A: pass
10261022
class B: pass
@@ -2724,3 +2720,23 @@ class A:
27242720
class B(A):
27252721
x = None # E: Incompatible types in assignment (expression has type "None", base class "A" defined the type as "str")
27262722
x = ''
2723+
2724+
[case testNeedAnnotationForCallable]
2725+
from typing import TypeVar, Optional, Callable
2726+
2727+
T = TypeVar('T')
2728+
2729+
def f(x: Optional[T] = None) -> Callable[..., T]: ...
2730+
2731+
x = f() # E: Need type annotation for 'x'
2732+
y = x
2733+
2734+
[case testDontNeedAnnotationForCallable]
2735+
from typing import TypeVar, Optional, Callable, NoReturn
2736+
2737+
T = TypeVar('T')
2738+
2739+
def f() -> Callable[..., NoReturn]: ...
2740+
2741+
x = f()
2742+
reveal_type(x) # N: Revealed type is 'def (*Any, **Any) -> <nothing>'

test-data/unit/check-literal.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,7 @@ def f() -> NotAType['also' + 'not' + 'a' + 'type']: ... # E: Invalid type "__mai
700700

701701
# Note: this makes us re-inspect the type (e.g. via '_patch_indirect_dependencies'
702702
# in build.py) so we can confirm the RawExpressionType did not leak out.
703-
indirect = f() # E: Need type annotation for 'indirect'
703+
indirect = f()
704704
[out]
705705

706706
--

test-data/unit/check-newsemanal.test

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2716,3 +2716,43 @@ class N(NamedTuple):
27162716
)
27172717
b
27182718
[builtins fixtures/tuple.pyi]
2719+
2720+
[case testNewAnalyzerLessErrorsNeedAnnotation]
2721+
from typing import TypeVar, Optional
2722+
2723+
T = TypeVar('T')
2724+
2725+
def f(x: Optional[T] = None) -> T: ...
2726+
2727+
x = f() # E: Need type annotation for 'x'
2728+
y = x
2729+
2730+
def g() -> None:
2731+
x = f() # E: Need type annotation for 'x'
2732+
y = x
2733+
2734+
[case testNewAnalyzerLessErrorsNeedAnnotationList]
2735+
x = [] # type: ignore
2736+
reveal_type(x) # N: Revealed type is 'builtins.list[Any]'
2737+
2738+
def g() -> None:
2739+
x = [] # type: ignore
2740+
reveal_type(x) # N: Revealed type is 'builtins.list[Any]'
2741+
[builtins fixtures/list.pyi]
2742+
2743+
[case testNewAnalyzerLessErrorsNeedAnnotationNested]
2744+
from typing import TypeVar, Optional, Generic
2745+
2746+
T = TypeVar('T')
2747+
class G(Generic[T]): ...
2748+
2749+
def f(x: Optional[T] = None) -> G[T]: ...
2750+
2751+
x = f() # E: Need type annotation for 'x'
2752+
y = x
2753+
reveal_type(y) # N: Revealed type is '__main__.G[Any]'
2754+
2755+
def g() -> None:
2756+
x = f() # E: Need type annotation for 'x'
2757+
y = x
2758+
reveal_type(y) # N: Revealed type is '__main__.G[Any]'

test-data/unit/fine-grained-cycles.test

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,7 @@ def h() -> None:
205205
[out]
206206
==
207207
a.py:3: error: Invalid type "b.C"
208-
b.py:6: error: Need type annotation for 'c'
209-
b.py:7: error: Cannot determine type of 'c'
208+
b.py:7: error: C? has no attribute "g"
210209

211210
-- TODO: More import cycle:
212211
--

0 commit comments

Comments
 (0)