From ff358dcce615154a517976f061c9f7ab62086014 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Fri, 1 Nov 2019 11:31:00 +0000 Subject: [PATCH] Fix crashes in astdiff when sorting union items Previously the snapshots could be unsortable, since the snapshot tuples could have inconsistent types. This attemps to make the types in tuples predictable based on the tuple prefix. More formally, consider these two snapshot tuples: (x1, ..., xn, xn+1, ...) (y1, ..., yn, yn+1, ...) If (x1, ..., xn) == (y1, ..., yn), xn+1 and yn+1 must be comparable. Attempt to fix #7834. --- mypy/server/astdiff.py | 18 ++++++++++++++---- test-data/unit/diff.test | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index d07c7aca5e40..f7e9cd1b7471 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -246,6 +246,13 @@ def snapshot_simple_type(typ: Type) -> SnapshotItem: return (type(typ).__name__,) +def encode_optional_str(s: Optional[str]) -> str: + if s is None: + return '' + else: + return s + + class SnapshotTypeVisitor(TypeVisitor[SnapshotItem]): """Creates a read-only, self-contained snapshot of a type object. @@ -256,6 +263,9 @@ class SnapshotTypeVisitor(TypeVisitor[SnapshotItem]): - Has no references to mutable or non-primitive objects. - Two snapshots represent the same object if and only if they are equal. + - Results must be sortable. It's important that tuples have + consistent types and can't arbitrarily mix str and None values, + for example, since they can't be compared. """ def visit_unbound_type(self, typ: UnboundType) -> SnapshotItem: @@ -282,9 +292,9 @@ def visit_deleted_type(self, typ: DeletedType) -> SnapshotItem: def visit_instance(self, typ: Instance) -> SnapshotItem: return ('Instance', - typ.type.fullname(), + encode_optional_str(typ.type.fullname()), snapshot_types(typ.args), - None if typ.last_known_value is None else snapshot_type(typ.last_known_value)) + ('None',) if typ.last_known_value is None else snapshot_type(typ.last_known_value)) def visit_type_var(self, typ: TypeVarType) -> SnapshotItem: return ('TypeVar', @@ -301,7 +311,7 @@ def visit_callable_type(self, typ: CallableType) -> SnapshotItem: return ('CallableType', snapshot_types(typ.arg_types), snapshot_type(typ.ret_type), - tuple(typ.arg_names), + tuple([encode_optional_str(name) for name in typ.arg_names]), tuple(typ.arg_kinds), typ.is_type_obj(), typ.is_ellipsis_args) @@ -316,7 +326,7 @@ def visit_typeddict_type(self, typ: TypedDictType) -> SnapshotItem: return ('TypedDictType', items, required) def visit_literal_type(self, typ: LiteralType) -> SnapshotItem: - return ('LiteralType', typ.value, snapshot_type(typ.fallback)) + return ('LiteralType', snapshot_type(typ.fallback), typ.value) def visit_union_type(self, typ: UnionType) -> SnapshotItem: # Sort and remove duplicates so that we can use equality to test for diff --git a/test-data/unit/diff.test b/test-data/unit/diff.test index 6e9742dda2b7..34a5ef263cdd 100644 --- a/test-data/unit/diff.test +++ b/test-data/unit/diff.test @@ -1443,3 +1443,25 @@ class C: [out] __main__.C.method __main__.func + +[case testUnionOfLiterals] +from typing_extensions import Literal +x: Literal[1, '2'] +[file next.py] +from typing_extensions import Literal +x: Literal[1, 2] +[out] +__main__.x + +[case testUnionOfCallables] +from typing import Callable, Union +from mypy_extensions import Arg +x: Union[Callable[[Arg(int, 'x')], None], + Callable[[int], None]] +[file next.py] +from typing import Callable, Union +from mypy_extensions import Arg +x: Union[Callable[[Arg(int, 'y')], None], + Callable[[int], None]] +[out] +__main__.x