diff --git a/mypy/binder.py b/mypy/binder.py index 4de939e27501b..24b2c544c1153 100644 --- a/mypy/binder.py +++ b/mypy/binder.py @@ -318,7 +318,10 @@ def update_from_options(self, frames: list[Frame]) -> bool: # variable types to be widened using subsequent assignments. This is # tricky to support for instance attributes (primarily due to deferrals), # so we don't use it for them. - old_semantics = not self.bind_all or extract_var_from_literal_hash(key) is None + var = extract_var_from_literal_hash(key) + old_semantics = ( + not self.bind_all or var is None or not var.is_inferred and not var.is_argument + ) if old_semantics and any(x is None for x in resulting_values): # We didn't know anything about key before # (current_value must be None), and we still don't diff --git a/mypy/checker.py b/mypy/checker.py index 62d74dc254179..ab5b6a291151a 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3427,6 +3427,9 @@ def check_assignment( self.options.allow_redefinition_new and lvalue_type is not None and not isinstance(lvalue_type, PartialType) + and isinstance(lvalue, NameExpr) + and isinstance(lvalue.node, Var) + and lvalue.node.is_inferred ): # TODO: Can we use put() here? self.binder.assign_type(lvalue, lvalue_type, lvalue_type) @@ -4720,7 +4723,7 @@ def infer_rvalue_with_fallback_context( # and use it results in a narrower type. This helps with various practical # examples, see e.g. testOptionalTypeNarrowedByGenericCall. union_fallback = ( - inferred is None + preferred_context is not None and isinstance(get_proper_type(lvalue_type), UnionType) and binder_version == self.binder.version ) @@ -4739,7 +4742,7 @@ def infer_rvalue_with_fallback_context( not alt_local_errors.has_new_errors() and is_valid_inferred_type(alt_rvalue_type, self.options) and ( - # For redefinition fallback we are fine getting not a subtype. + # For redefinition fallbacks we are fine getting not a subtype. redefinition_fallback or argument_redefinition_fallback # Skip Any type, since it is special cased in binder. diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 747981916960d..262aa0c3a7fc2 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -509,7 +509,7 @@ def parse_section( options_key = key # Match aliasing for command line flag. if key.endswith("allow_redefinition"): - options_key += "_old" + options_key += "_new" if key in config_types: ct = config_types[key] elif key in invalid_options: @@ -590,6 +590,8 @@ def parse_section( results["disable_error_code"] = [] if "enable_error_code" not in results: results["enable_error_code"] = [] + if results.get("allow_redefinition_new"): + results["local_partial_types"] = True return results, report_dirs diff --git a/mypy/main.py b/mypy/main.py index acc27f1806b78..179f92c126971 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -97,12 +97,8 @@ def main( stdout, stderr, options.hide_error_codes, hide_success=bool(options.output) ) - if options.allow_redefinition_new and not options.local_partial_types: - fail( - "error: --local-partial-types must be enabled if using --allow-redefinition-new", - stderr, - options, - ) + if options.allow_redefinition_new: + options.local_partial_types = True if options.install_types and (stdout is not sys.stdout or stderr is not sys.stderr): # Since --install-types performs user input, we want regular stdout and stderr. @@ -868,7 +864,7 @@ def add_invertible_flag( strict_flag=False, help="Alias to --allow-redefinition-old; will point to --allow-redefinition-new in v2.0", group=strictness_group, - dest="allow_redefinition_old", + dest="allow_redefinition_new", ) add_invertible_flag( diff --git a/mypy/semanal_typeargs.py b/mypy/semanal_typeargs.py index 0f62a4aa8b1a2..1de25f8fe34b3 100644 --- a/mypy/semanal_typeargs.py +++ b/mypy/semanal_typeargs.py @@ -33,8 +33,10 @@ TypeVarTupleType, TypeVarType, UnboundType, + UnionType, UnpackType, flatten_nested_tuples, + flatten_nested_unions, get_proper_type, get_proper_types, split_with_prefix_and_suffix, @@ -118,6 +120,10 @@ def visit_tuple_type(self, t: TupleType) -> None: # and we need to return an Instance instead of TupleType. super().visit_tuple_type(t) + def visit_union_type(self, t: UnionType) -> None: + super().visit_union_type(t) + t.items = flatten_nested_unions(t.items, handle_recursive=False) + def visit_callable_type(self, t: CallableType) -> None: super().visit_callable_type(t) t.normalize_trivial_unpack() diff --git a/test-data/unit/check-redefine2.test b/test-data/unit/check-redefine2.test index de9da106716c2..8e4200b79dc2c 100644 --- a/test-data/unit/check-redefine2.test +++ b/test-data/unit/check-redefine2.test @@ -69,7 +69,7 @@ def f1() -> None: x: Union[int, str] = 0 reveal_type(x) # N: Revealed type is "builtins.int | builtins.str" x = "" - reveal_type(x) # N: Revealed type is "builtins.str" + reveal_type(x) # N: Revealed type is "builtins.int | builtins.str" [case testNewRedefineUninitializedCodePath3] # flags: --allow-redefinition-new --local-partial-types @@ -1339,3 +1339,86 @@ def process3(items: list[str]) -> None: reveal_type(items) # N: Revealed type is "builtins.list[builtins.str]" reveal_type(items) # N: Revealed type is "builtins.list[builtins.str]" [builtins fixtures/primitives.pyi] + +[case testNewRedefineHasAttr] +# flags: --allow-redefinition-new --local-partial-types + +def test(lst: list[object]) -> None: + for cls in lst: + if not hasattr(cls, "module"): + break + reveal_type(cls.module) # N: Revealed type is "Any" +[builtins fixtures/isinstancelist.pyi] + +[case testNewRedefineOldAnySpecialCasing] +# flags: --allow-redefinition-new --local-partial-types +from typing import Any + +def test() -> None: + a: Any + x = 1 + if bool(): + x = a + reveal_type(x) # N: Revealed type is "builtins.int" + + if bool(): + y = a + else: + y = 1 + reveal_type(y) # N: Revealed type is "Any" + +[case testNewRedefineUnionArgumentFallback] +# flags: --allow-redefinition-new --local-partial-types +from typing import Optional, TypeVar + +T = TypeVar("T") +async def gen(x: T) -> T: ... + +async def test(x: Optional[str]) -> None: + if x is None: + x = await gen("foo") + reveal_type(x) # N: Revealed type is "builtins.str" + +[case testNewRedefineNarrowingOnFirstAssignment] +# flags: --allow-redefinition-new --local-partial-types +from typing import Any + +li: list[int] + +def test() -> None: + x: list[Any] = li + reveal_type(x) # N: Revealed type is "builtins.list[Any]" + + if bool(): + y: list[Any] = li + else: + y = li + reveal_type(y) # N: Revealed type is "builtins.list[Any]" +[builtins fixtures/primitives.pyi] + +[case testNewRedefineNarrowingForNestedUnionWithAny] +# flags: --allow-redefinition-new --local-partial-types +from typing import Any, Union + +a: Any +Int = Union[int, Any] + +def test(x: Union[str, Int]) -> None: + x = a + reveal_type(x) # N: Revealed type is "Any" + +[case testNewRedefineWidenedArgumentDeferral] +# flags: --allow-redefinition-new --local-partial-types +def foo(x: int) -> None: + reveal_type(x) # N: Revealed type is "builtins.int" + if bool(): + x = "no" + c: C + c.x + reveal_type(x) # N: Revealed type is "builtins.int | builtins.str" + +class C: + def __init__(self) -> None: + self.x = defer() + +def defer() -> int: ... diff --git a/test-data/unit/fixtures/isinstancelist.pyi b/test-data/unit/fixtures/isinstancelist.pyi index 2a43606f361a3..54d6e9e38b499 100644 --- a/test-data/unit/fixtures/isinstancelist.pyi +++ b/test-data/unit/fixtures/isinstancelist.pyi @@ -18,6 +18,7 @@ Ellipsis = ellipsis() def isinstance(x: object, t: Union[type, Tuple]) -> bool: pass def issubclass(x: object, t: Union[type, Tuple]) -> bool: pass +def hasattr(x: object, name: str) -> bool: pass class int: def __add__(self, x: int) -> int: pass