Skip to content

Commit 5f2f110

Browse files
sixoletgvanrossum
authored andcommitted
Make TypeQuery more general, handling nonboolean queries. (#3084)
Instead of TypeQuery always returning a boolean and having the strategy be an enum, the strategy is now a Callable describing how to combine partial results, and the two default strategies are plain old funcitons. To preserve the short-circuiting behavior of the previous code, this PR uses an exception. This is a pure refactor that I am using in my experimentation regarding fixing #1551. It should result in exactly no change to current behavior. It's separable from the other things I'm experimenting with, so I'm filing it as a separate pull request now. It enables me to rewrite the code that pulls type variables out of types as a TypeQuery. Consider waiting to merge this PR until I have some code that uses it ready for review. Or merge it now, if you think it's a pleasant cleanup instead of an ugly complication. I'm of two minds on that particular question.
1 parent d6ed083 commit 5f2f110

File tree

4 files changed

+50
-83
lines changed

4 files changed

+50
-83
lines changed

mypy/checkexpr.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -2375,24 +2375,24 @@ def replace_callable_return_type(c: CallableType, new_ret_type: Type) -> Callabl
23752375
return c.copy_modified(ret_type=new_ret_type)
23762376

23772377

2378-
class ArgInferSecondPassQuery(types.TypeQuery):
2378+
class ArgInferSecondPassQuery(types.TypeQuery[bool]):
23792379
"""Query whether an argument type should be inferred in the second pass.
23802380
23812381
The result is True if the type has a type variable in a callable return
23822382
type anywhere. For example, the result for Callable[[], T] is True if t is
23832383
a type variable.
23842384
"""
23852385
def __init__(self) -> None:
2386-
super().__init__(False, types.ANY_TYPE_STRATEGY)
2386+
super().__init__(any)
23872387

23882388
def visit_callable_type(self, t: CallableType) -> bool:
23892389
return self.query_types(t.arg_types) or t.accept(HasTypeVarQuery())
23902390

23912391

2392-
class HasTypeVarQuery(types.TypeQuery):
2392+
class HasTypeVarQuery(types.TypeQuery[bool]):
23932393
"""Visitor for querying whether a type has a type variable component."""
23942394
def __init__(self) -> None:
2395-
super().__init__(False, types.ANY_TYPE_STRATEGY)
2395+
super().__init__(any)
23962396

23972397
def visit_type_var(self, t: TypeVarType) -> bool:
23982398
return True
@@ -2402,10 +2402,10 @@ def has_erased_component(t: Type) -> bool:
24022402
return t is not None and t.accept(HasErasedComponentsQuery())
24032403

24042404

2405-
class HasErasedComponentsQuery(types.TypeQuery):
2405+
class HasErasedComponentsQuery(types.TypeQuery[bool]):
24062406
"""Visitor for querying whether a type has an erased component."""
24072407
def __init__(self) -> None:
2408-
super().__init__(False, types.ANY_TYPE_STRATEGY)
2408+
super().__init__(any)
24092409

24102410
def visit_erased_type(self, t: ErasedType) -> bool:
24112411
return True

mypy/constraints.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
from mypy.types import (
77
CallableType, Type, TypeVisitor, UnboundType, AnyType, NoneTyp, TypeVarType,
88
Instance, TupleType, TypedDictType, UnionType, Overloaded, ErasedType, PartialType,
9-
DeletedType, UninhabitedType, TypeType, TypeVarId, TypeQuery, ALL_TYPES_STRATEGY,
10-
is_named_instance
9+
DeletedType, UninhabitedType, TypeType, TypeVarId, TypeQuery, is_named_instance
1110
)
1211
from mypy.maptype import map_instance_to_supertype
1312
from mypy import nodes
@@ -250,9 +249,9 @@ def is_complete_type(typ: Type) -> bool:
250249
return typ.accept(CompleteTypeVisitor())
251250

252251

253-
class CompleteTypeVisitor(TypeQuery):
252+
class CompleteTypeVisitor(TypeQuery[bool]):
254253
def __init__(self) -> None:
255-
super().__init__(default=True, strategy=ALL_TYPES_STRATEGY)
254+
super().__init__(all)
256255

257256
def visit_none_type(self, t: NoneTyp) -> bool:
258257
return experiments.STRICT_OPTIONAL

mypy/stats.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77

88
from mypy.traverser import TraverserVisitor
99
from mypy.types import (
10-
Type, AnyType, Instance, FunctionLike, TupleType, TypeVarType,
11-
TypeQuery, ANY_TYPE_STRATEGY, CallableType
10+
Type, AnyType, Instance, FunctionLike, TupleType, TypeVarType, TypeQuery, CallableType
1211
)
1312
from mypy import nodes
1413
from mypy.nodes import (
@@ -226,9 +225,9 @@ def is_imprecise(t: Type) -> bool:
226225
return t.accept(HasAnyQuery())
227226

228227

229-
class HasAnyQuery(TypeQuery):
228+
class HasAnyQuery(TypeQuery[bool]):
230229
def __init__(self) -> None:
231-
super().__init__(False, ANY_TYPE_STRATEGY)
230+
super().__init__(any)
232231

233232
def visit_any(self, t: AnyType) -> bool:
234233
return True

mypy/types.py

+38-69
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from collections import OrderedDict
66
from typing import (
77
Any, TypeVar, Dict, List, Tuple, cast, Generic, Set, Sequence, Optional, Union, Iterable,
8-
NamedTuple,
8+
NamedTuple, Callable,
99
)
1010

1111
import mypy.nodes
@@ -1500,109 +1500,78 @@ def keywords_str(self, a: Iterable[Tuple[str, Type]]) -> str:
15001500
])
15011501

15021502

1503-
# These constants define the method used by TypeQuery to combine multiple
1504-
# query results, e.g. for tuple types. The strategy is not used for empty
1505-
# result lists; in that case the default value takes precedence.
1506-
ANY_TYPE_STRATEGY = 0 # Return True if any of the results are True.
1507-
ALL_TYPES_STRATEGY = 1 # Return True if all of the results are True.
1503+
class TypeQuery(Generic[T], TypeVisitor[T]):
1504+
"""Visitor for performing queries of types.
15081505
1506+
strategy is used to combine results for a series of types
15091507
1510-
class TypeQuery(TypeVisitor[bool]):
1511-
"""Visitor for performing simple boolean queries of types.
1512-
1513-
This class allows defining the default value for leafs to simplify the
1514-
implementation of many queries.
1508+
Common use cases involve a boolean query using `any` or `all`
15151509
"""
15161510

1517-
default = False # Default result
1518-
strategy = 0 # Strategy for combining multiple values (ANY_TYPE_STRATEGY or ALL_TYPES_...).
1519-
1520-
def __init__(self, default: bool, strategy: int) -> None:
1521-
"""Construct a query visitor.
1522-
1523-
Use the given default result and strategy for combining
1524-
multiple results. The strategy must be either
1525-
ANY_TYPE_STRATEGY or ALL_TYPES_STRATEGY.
1526-
"""
1527-
self.default = default
1511+
def __init__(self, strategy: Callable[[Iterable[T]], T]) -> None:
15281512
self.strategy = strategy
15291513

1530-
def visit_unbound_type(self, t: UnboundType) -> bool:
1531-
return self.default
1514+
def visit_unbound_type(self, t: UnboundType) -> T:
1515+
return self.query_types(t.args)
15321516

1533-
def visit_type_list(self, t: TypeList) -> bool:
1534-
return self.default
1517+
def visit_type_list(self, t: TypeList) -> T:
1518+
return self.query_types(t.items)
15351519

1536-
def visit_any(self, t: AnyType) -> bool:
1537-
return self.default
1520+
def visit_any(self, t: AnyType) -> T:
1521+
return self.strategy([])
15381522

1539-
def visit_uninhabited_type(self, t: UninhabitedType) -> bool:
1540-
return self.default
1523+
def visit_uninhabited_type(self, t: UninhabitedType) -> T:
1524+
return self.strategy([])
15411525

1542-
def visit_none_type(self, t: NoneTyp) -> bool:
1543-
return self.default
1526+
def visit_none_type(self, t: NoneTyp) -> T:
1527+
return self.strategy([])
15441528

1545-
def visit_erased_type(self, t: ErasedType) -> bool:
1546-
return self.default
1529+
def visit_erased_type(self, t: ErasedType) -> T:
1530+
return self.strategy([])
15471531

1548-
def visit_deleted_type(self, t: DeletedType) -> bool:
1549-
return self.default
1532+
def visit_deleted_type(self, t: DeletedType) -> T:
1533+
return self.strategy([])
15501534

1551-
def visit_type_var(self, t: TypeVarType) -> bool:
1552-
return self.default
1535+
def visit_type_var(self, t: TypeVarType) -> T:
1536+
return self.strategy([])
15531537

1554-
def visit_partial_type(self, t: PartialType) -> bool:
1555-
return self.default
1538+
def visit_partial_type(self, t: PartialType) -> T:
1539+
return self.query_types(t.inner_types)
15561540

1557-
def visit_instance(self, t: Instance) -> bool:
1541+
def visit_instance(self, t: Instance) -> T:
15581542
return self.query_types(t.args)
15591543

1560-
def visit_callable_type(self, t: CallableType) -> bool:
1544+
def visit_callable_type(self, t: CallableType) -> T:
15611545
# FIX generics
15621546
return self.query_types(t.arg_types + [t.ret_type])
15631547

1564-
def visit_tuple_type(self, t: TupleType) -> bool:
1548+
def visit_tuple_type(self, t: TupleType) -> T:
15651549
return self.query_types(t.items)
15661550

1567-
def visit_typeddict_type(self, t: TypedDictType) -> bool:
1551+
def visit_typeddict_type(self, t: TypedDictType) -> T:
15681552
return self.query_types(t.items.values())
15691553

1570-
def visit_star_type(self, t: StarType) -> bool:
1554+
def visit_star_type(self, t: StarType) -> T:
15711555
return t.type.accept(self)
15721556

1573-
def visit_union_type(self, t: UnionType) -> bool:
1557+
def visit_union_type(self, t: UnionType) -> T:
15741558
return self.query_types(t.items)
15751559

1576-
def visit_overloaded(self, t: Overloaded) -> bool:
1560+
def visit_overloaded(self, t: Overloaded) -> T:
15771561
return self.query_types(t.items())
15781562

1579-
def visit_type_type(self, t: TypeType) -> bool:
1563+
def visit_type_type(self, t: TypeType) -> T:
15801564
return t.item.accept(self)
15811565

1582-
def query_types(self, types: Iterable[Type]) -> bool:
1566+
def visit_ellipsis_type(self, t: EllipsisType) -> T:
1567+
return self.strategy([])
1568+
1569+
def query_types(self, types: Iterable[Type]) -> T:
15831570
"""Perform a query for a list of types.
15841571
1585-
Use the strategy constant to combine the results.
1572+
Use the strategy to combine the results.
15861573
"""
1587-
if not types:
1588-
# Use default result for empty list.
1589-
return self.default
1590-
if self.strategy == ANY_TYPE_STRATEGY:
1591-
# Return True if at least one component is true.
1592-
res = False
1593-
for t in types:
1594-
res = res or t.accept(self)
1595-
if res:
1596-
break
1597-
return res
1598-
else:
1599-
# Return True if all components are true.
1600-
res = True
1601-
for t in types:
1602-
res = res and t.accept(self)
1603-
if not res:
1604-
break
1605-
return res
1574+
return self.strategy(t.accept(self) for t in types)
16061575

16071576

16081577
def strip_type(typ: Type) -> Type:

0 commit comments

Comments
 (0)