Skip to content

Commit

Permalink
Try some compromise variant
Browse files Browse the repository at this point in the history
  • Loading branch information
ilevkivskyi committed Jan 28, 2025
1 parent 46b682c commit 1b5cdeb
Show file tree
Hide file tree
Showing 10 changed files with 57 additions and 52 deletions.
48 changes: 29 additions & 19 deletions mypy/binder.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from mypy.types import (
AnyType,
Instance,
NoneType,
PartialType,
ProperType,
TupleType,
Expand Down Expand Up @@ -341,12 +342,34 @@ def assign_type(self, expr: Expression, type: Type, declared_type: Type | None)
# times?
return

enclosing_type = get_proper_type(self.most_recent_enclosing_type(expr, type))
if isinstance(enclosing_type, AnyType):
# If x is Any and y is int, after x = y we do not infer that x is int,
# instead we keep it Any. This behavior is unsafe, but it exists since
# long time, so we will keep it until someone complains.
self.put(expr, enclosing_type)
p_declared = get_proper_type(declared_type)
p_type = get_proper_type(type)
if isinstance(p_type, AnyType):
# Any type requires some special casing, for both historical reasons,
# and to optimise user experience without sacrificing correctness too much.
if isinstance(expr, RefExpr) and isinstance(expr.node, Var) and expr.node.is_inferred:
# First case: a local/global variable without explicit annotation,
# in this case we just assign Any (essentially following the SSA logic).
self.put(expr, type)
elif isinstance(p_declared, UnionType) and any(
isinstance(get_proper_type(item), NoneType) for item in p_declared.items
):
# Second case: explicit optional type, in this case we optimize for a common
# pattern when an untyped value used as a fallback replacing None.
new_items = [
type if isinstance(get_proper_type(item), NoneType) else item
for item in p_declared.items
]
self.put(expr, UnionType(new_items))
elif isinstance(p_declared, UnionType) and any(
isinstance(get_proper_type(item), AnyType) for item in p_declared.items
):
# Third case: a union already containing Any (most likely from an un-imported
# name), in this case we allow assigning Any as well.
self.put(expr, type)
else:
# In all other cases we don't narrow to Any to minimize false negatives.
self.put(expr, declared_type)
else:
self.put(expr, type)

Expand All @@ -368,19 +391,6 @@ def invalidate_dependencies(self, expr: BindableExpression) -> None:
for dep in self.dependencies.get(key, set()):
self._cleanse_key(dep)

def most_recent_enclosing_type(self, expr: BindableExpression, type: Type) -> Type | None:
type = get_proper_type(type)
if isinstance(type, AnyType):
return get_declaration(expr)
key = literal_hash(expr)
assert key is not None
enclosers = [get_declaration(expr)] + [
f.types[key].type
for f in self.frames
if key in f.types and is_subtype(type, f.types[key][0])
]
return enclosers[-1]

def allow_jump(self, index: int) -> None:
# self.frames and self.options_on_return have different lengths
# so make sure the index is positive
Expand Down
4 changes: 3 additions & 1 deletion mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -1106,7 +1106,9 @@ def make_argument(
if argument_elide_name(arg.arg):
pos_only = True

argument = Argument(Var(arg.arg, arg_type), arg_type, self.visit(default), kind, pos_only)
var = Var(arg.arg, arg_type)
var.is_inferred = False
argument = Argument(var, arg_type, self.visit(default), kind, pos_only)
argument.set_line(
arg.lineno,
arg.col_offset,
Expand Down
9 changes: 0 additions & 9 deletions mypyc/test-data/irbuild-any.test
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ def f(a: Any, n: int, c: C) -> None:
c.n = a
a = n
n = a
a.a = n
[out]
def f(a, n, c):
a :: object
Expand All @@ -49,10 +48,6 @@ def f(a, n, c):
r3 :: bool
r4 :: object
r5 :: int
r6 :: object
r7 :: str
r8 :: i32
r9 :: bit
L0:
r0 = box(int, n)
c.a = r0; r1 = is_error
Expand All @@ -62,10 +57,6 @@ L0:
a = r4
r5 = unbox(int, a)
n = r5
r6 = box(int, n)
r7 = 'a'
r8 = PyObject_SetAttr(a, r7, r6)
r9 = r8 >= 0 :: signed
return 1

[case testCoerceAnyInOps]
Expand Down
6 changes: 4 additions & 2 deletions test-data/unit/check-dynamic-typing.test
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,10 @@ d = None # All ok
d = t
d = g
d = A
t = d
f = d

d1: Any
t = d1
f = d1
[builtins fixtures/tuple.pyi]


Expand Down
5 changes: 2 additions & 3 deletions test-data/unit/check-isinstance.test
Original file line number Diff line number Diff line change
Expand Up @@ -1919,13 +1919,12 @@ if isinstance(x, str, 1): # E: Too many arguments for "isinstance"
from typing import Any

def narrow_any_to_str_then_reassign_to_int() -> None:
v = 1 # type: Any
v: Any = 1

if isinstance(v, str):
reveal_type(v) # N: Revealed type is "builtins.str"
v = 2
reveal_type(v) # N: Revealed type is "Any"

reveal_type(v) # N: Revealed type is "builtins.int"
[builtins fixtures/isinstance.pyi]

[case testNarrowTypeAfterInList]
Expand Down
18 changes: 9 additions & 9 deletions test-data/unit/check-optional.test
Original file line number Diff line number Diff line change
Expand Up @@ -723,9 +723,10 @@ def f():
def g(x: Optional[int]) -> int:
if x is None:
reveal_type(x) # N: Revealed type is "None"
# As a special case for Unions containing None, during
x = f()
reveal_type(x) # N: Revealed type is "Any"
reveal_type(x) # N: Revealed type is "Union[Any, builtins.int]"
reveal_type(x) # N: Revealed type is "Union[builtins.int, Any]"
reveal_type(x) # N: Revealed type is "Union[builtins.int, Any]"
return x
[builtins fixtures/bool.pyi]

Expand All @@ -739,9 +740,10 @@ def g(x: Optional[int]) -> int:
reveal_type(x) # N: Revealed type is "None"
x = 1
reveal_type(x) # N: Revealed type is "builtins.int"
# Same as above, even after we've assigned to x
x = f()
reveal_type(x) # N: Revealed type is "Any"
reveal_type(x) # N: Revealed type is "Union[Any, builtins.int]"
reveal_type(x) # N: Revealed type is "Union[builtins.int, Any]"
reveal_type(x) # N: Revealed type is "Union[builtins.int, Any]"
return x
[builtins fixtures/bool.pyi]

Expand All @@ -754,11 +756,9 @@ def g(x: Optional[int]) -> int:
if x is not None:
return x
reveal_type(x) # N: Revealed type is "None"
if bool():
x = f()
reveal_type(x) # N: Revealed type is "Any"
return x
return x # E: Incompatible return value type (got "None", expected "int")
x = f()
reveal_type(x) # N: Revealed type is "Union[builtins.int, Any]"
return x
[builtins fixtures/bool.pyi]

[case testStrictOptionalCovarianceCrossModule]
Expand Down
5 changes: 3 additions & 2 deletions test-data/unit/check-parameter-specification.test
Original file line number Diff line number Diff line change
Expand Up @@ -343,8 +343,9 @@ class C(Generic[P]):
a = kwargs
args = kwargs # E: Incompatible types in assignment (expression has type "P.kwargs", variable has type "P.args")
kwargs = args # E: Incompatible types in assignment (expression has type "P.args", variable has type "P.kwargs")
args = a
kwargs = a
a1: Any
args = a1
kwargs = a1
[builtins fixtures/dict.pyi]

[case testParamSpecSubtypeChecking2]
Expand Down
3 changes: 2 additions & 1 deletion test-data/unit/check-redefine.test
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,8 @@ def f() -> None:
_, _ = 1, ''
if 1:
_, _ = '', 1
reveal_type(_) # N: Revealed type is "Any"
# This is unintentional but probably fine. No one is going to read _ value.
reveal_type(_) # N: Revealed type is "builtins.int"

[case testRedefineWithBreakAndContinue]
# flags: --allow-redefinition
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-unions.test
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ x: Union[int, str]
a: Any
if bool():
x = a
reveal_type(x) # N: Revealed type is "Any"
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str]"
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str]"
[builtins fixtures/bool.pyi]

Expand Down
9 changes: 4 additions & 5 deletions test-data/unit/check-unreachable-code.test
Original file line number Diff line number Diff line change
Expand Up @@ -631,12 +631,11 @@ class Child(Parent):
def bar(self) -> int:
if 1:
self = super(Child, self).something()
reveal_type(self) # N: Revealed type is "Any"
# TODO: we should probably make this unreachable similar to above.
reveal_type(self) # N: Revealed type is "__main__.Child"
if self is None:
reveal_type(self) # N: Revealed type is "Never"
return None # E: Incompatible return value type (got "None", expected "int")
reveal_type(self) # N: Revealed type is "Any"
reveal_type(self)
return None
reveal_type(self) # N: Revealed type is "__main__.Child"
return 3
[builtins fixtures/isinstance.pyi]

Expand Down

0 comments on commit 1b5cdeb

Please sign in to comment.