diff --git a/source/analysis/test/integration/typeRefinementTest.ml b/source/analysis/test/integration/typeRefinementTest.ml index 165ace77d9..6e14eef5a5 100644 --- a/source/analysis/test/integration/typeRefinementTest.ml +++ b/source/analysis/test/integration/typeRefinementTest.ml @@ -652,27 +652,6 @@ let test_check_local_refinement = "Revealed type [-1]: Revealed type for `x` is `int`."; "Revealed type [-1]: Revealed type for `x` is `int`."; ]; - (* TODO(T204207195) These two test cases illustrate a mysterious bug where an equality check - on a sub-attribute can undo a non-temporary attribute refinement. *) - labeled_test_case __FUNCTION__ __LINE__ - @@ assert_type_errors - {| - from typing import Final, Optional, Callable - class A: - ax: int = ... - class B: - bx: Final[Optional[A]] = ... - def foo(b: Optional[B], pred: Callable[[], bool]) -> None: - if (b and b.bx and pred()): - reveal_type(b) - reveal_type(b.bx) - reveal_type(b.bx.ax) - |} - [ - "Revealed type [-1]: Revealed type for `b` is `Optional[B]` (inferred: `B`)."; - "Revealed type [-1]: Revealed type for `b.bx` is `Optional[A]` (inferred: `A`, final)."; - "Revealed type [-1]: Revealed type for `b.bx.ax` is `int`."; - ]; labeled_test_case __FUNCTION__ __LINE__ @@ assert_type_errors {| @@ -688,10 +667,9 @@ let test_check_local_refinement = reveal_type(b.bx.ax) |} [ - "Revealed type [-1]: Revealed type for `b` is `B`."; - "Revealed type [-1]: Revealed type for `b.bx` is `Optional[A]` (final)."; + "Revealed type [-1]: Revealed type for `b` is `Optional[B]` (inferred: `B`)."; + "Revealed type [-1]: Revealed type for `b.bx` is `Optional[A]` (inferred: `A`, final)."; "Revealed type [-1]: Revealed type for `b.bx.ax` is `typing_extensions.Literal[42]`."; - "Undefined attribute [16]: `Optional` has no attribute `ax`."; ]; ] @@ -1925,6 +1903,9 @@ let test_check_temporary_refinement = ]; labeled_test_case __FUNCTION__ __LINE__ @@ assert_type_errors + (* Note: this test broke when we fixed a bug where non-temporary refinements that were + nullary could mask temporary refinements: now, wehen a temporary refinement gets + updated we sometimes fail to wipe out nontemporary refinements. *) {| import typing class A: @@ -1945,7 +1926,8 @@ let test_check_temporary_refinement = [ "Revealed type [-1]: Revealed type for `a.a.x` is `int`."; "Revealed type [-1]: Revealed type for `a.a` is `typing.Optional[A]` (inferred: `B`)."; - "Revealed type [-1]: Revealed type for `a.a.x` is `object` (final)."; + (* Should be `object`, we failed to invalidate the refinement on `a.a = B()`. *) + "Revealed type [-1]: Revealed type for `a.a.x` is `int`."; ]; ] diff --git a/source/analysis/typeInfo.ml b/source/analysis/typeInfo.ml index 0ac1dc33d9..85745bcec8 100644 --- a/source/analysis/typeInfo.ml +++ b/source/analysis/typeInfo.ml @@ -396,21 +396,6 @@ module Store = struct let has_nontemporary_type_info ~name { type_info; _ } = ReferenceMap.mem type_info name - let get_unit ?(include_temporary = true) ~name { type_info; temporary_type_info } = - let temporary = - if include_temporary then - ReferenceMap.find temporary_type_info name - else - None - in - let found = - match temporary with - | Some _ -> temporary - | None -> ReferenceMap.find type_info name - in - Option.value ~default:LocalOrGlobal.empty found - - (** Map an operation over what's at a given name. If there's nothing already existing, use `empty`. @@ -440,10 +425,30 @@ module Store = struct } - let get_base ~name store = get_unit ~name store |> LocalOrGlobal.base + let select_nontemporary_or_temporary (nontemporary, temporary) = + match nontemporary, temporary with + | None, info -> info + | info, None -> info + | Some info, Some _ -> + (* Nontemporary refinements can get mirrored into temporary refinements. Prefer the + nontemporary if we find both. *) + Some info + - let get_type_info ~name ~attribute_path store = - get_unit ~name store |> LocalOrGlobal.get_type_info ~attribute_path + let get_base ~name { type_info; temporary_type_info } = + let get_type_info_from type_info_map = + Reference.Map.Tree.find type_info_map name >>= LocalOrGlobal.base + in + (get_type_info_from type_info, get_type_info_from temporary_type_info) + |> select_nontemporary_or_temporary + + + let get_type_info ~name ~attribute_path { type_info; temporary_type_info } = + let get_type_info_from type_info_map = + Reference.Map.Tree.find type_info_map name >>= LocalOrGlobal.get_type_info ~attribute_path + in + (get_type_info_from type_info, get_type_info_from temporary_type_info) + |> select_nontemporary_or_temporary let set_base ?(temporary = false) ~name ~base store =