1
1
"""Utilities for mapping between actual and formal arguments (and their types)."""
2
2
3
- from typing import List , Optional , Sequence , Callable
3
+ from typing import List , Optional , Sequence , Callable , Set
4
4
5
- from mypy .types import Type , Instance , TupleType , AnyType , TypeOfAny
5
+ from mypy .types import Type , Instance , TupleType , AnyType , TypeOfAny , TypedDictType
6
6
from mypy import nodes
7
7
8
8
@@ -65,13 +65,24 @@ def map_actuals_to_formals(caller_kinds: List[int],
65
65
map [callee_kinds .index (nodes .ARG_STAR2 )].append (i )
66
66
else :
67
67
assert kind == nodes .ARG_STAR2
68
- for j in range (ncallee ):
69
- # TODO tuple varargs complicate this
70
- no_certain_match = (
71
- not map [j ] or caller_kinds [map [j ][0 ]] == nodes .ARG_STAR )
72
- if ((callee_names [j ] and no_certain_match )
73
- or callee_kinds [j ] == nodes .ARG_STAR2 ):
74
- map [j ].append (i )
68
+ argt = caller_arg_type (i )
69
+ if isinstance (argt , TypedDictType ):
70
+ for name , value in argt .items .items ():
71
+ if name in callee_names :
72
+ map [callee_names .index (name )].append (i )
73
+ elif nodes .ARG_STAR2 in callee_kinds :
74
+ map [callee_kinds .index (nodes .ARG_STAR2 )].append (i )
75
+ else :
76
+ # We don't exactly know which **kwargs are provided by the
77
+ # caller. Assume that they will fill the remaining arguments.
78
+ for j in range (ncallee ):
79
+ # TODO: If there are also tuple varargs, we might be missing some potential
80
+ # matches if the tuple was short enough to not match everything.
81
+ no_certain_match = (
82
+ not map [j ] or caller_kinds [map [j ][0 ]] == nodes .ARG_STAR )
83
+ if ((callee_names [j ] and no_certain_match )
84
+ or callee_kinds [j ] == nodes .ARG_STAR2 ):
85
+ map [j ].append (i )
75
86
return map
76
87
77
88
@@ -95,35 +106,87 @@ def map_formals_to_actuals(caller_kinds: List[int],
95
106
return actual_to_formal
96
107
97
108
98
- def get_actual_type (arg_type : Type , kind : int ,
99
- tuple_counter : List [int ]) -> Type :
100
- """Return the type of an actual argument with the given kind.
109
+ class ArgTypeExpander :
110
+ """Utility class for mapping actual argument types to formal arguments.
111
+
112
+ One of the main responsibilities is to expand caller tuple *args and TypedDict
113
+ **kwargs, and to keep track of which tuple/TypedDict items have already been
114
+ consumed.
115
+
116
+ Example:
117
+
118
+ def f(x: int, *args: str) -> None: ...
119
+ f(*(1, 'x', 1.1))
120
+
121
+ We'd call expand_actual_type three times:
101
122
102
- If the argument is a *arg, return the individual argument item.
123
+ 1. The first call would provide 'int' as the actual type of 'x' (from '1').
124
+ 2. The second call would provide 'str' as one of the actual types for '*args'.
125
+ 2. The third call would provide 'float' as one of the actual types for '*args'.
126
+
127
+ A single instance can process all the arguments for a single call. Each call
128
+ needs a separate instance since instances have per-call state.
103
129
"""
104
130
105
- if kind == nodes .ARG_STAR :
106
- if isinstance (arg_type , Instance ):
107
- if arg_type .type .fullname () == 'builtins.list' :
108
- # List *arg.
109
- return arg_type .args [0 ]
110
- elif arg_type .args :
111
- # TODO try to map type arguments to Iterable
112
- return arg_type .args [0 ]
131
+ def __init__ (self ) -> None :
132
+ # Next tuple *args index to use.
133
+ self .tuple_index = 0
134
+ # Keyword arguments in TypedDict **kwargs used.
135
+ self .kwargs_used = set () # type: Set[str]
136
+
137
+ def expand_actual_type (self ,
138
+ actual_type : Type ,
139
+ actual_kind : int ,
140
+ formal_name : Optional [str ],
141
+ formal_kind : int ) -> Type :
142
+ """Return the actual (caller) type(s) of a formal argument with the given kinds.
143
+
144
+ If the actual argument is a tuple *args, return the next individual tuple item that
145
+ maps to the formal arg.
146
+
147
+ If the actual argument is a TypedDict **kwargs, return the next matching typed dict
148
+ value type based on formal argument name and kind.
149
+
150
+ This is supposed to be called for each formal, in order. Call multiple times per
151
+ formal if multiple actuals map to a formal.
152
+ """
153
+ if actual_kind == nodes .ARG_STAR :
154
+ if isinstance (actual_type , Instance ):
155
+ if actual_type .type .fullname () == 'builtins.list' :
156
+ # List *arg.
157
+ return actual_type .args [0 ]
158
+ elif actual_type .args :
159
+ # TODO: Try to map type arguments to Iterable
160
+ return actual_type .args [0 ]
161
+ else :
162
+ return AnyType (TypeOfAny .from_error )
163
+ elif isinstance (actual_type , TupleType ):
164
+ # Get the next tuple item of a tuple *arg.
165
+ if self .tuple_index >= len (actual_type .items ):
166
+ # Exhausted a tuple -- continue to the next *args.
167
+ self .tuple_index = 1
168
+ else :
169
+ self .tuple_index += 1
170
+ return actual_type .items [self .tuple_index - 1 ]
171
+ else :
172
+ return AnyType (TypeOfAny .from_error )
173
+ elif actual_kind == nodes .ARG_STAR2 :
174
+ if isinstance (actual_type , TypedDictType ):
175
+ if formal_kind != nodes .ARG_STAR2 and formal_name in actual_type .items :
176
+ # Lookup type based on keyword argument name.
177
+ assert formal_name is not None
178
+ else :
179
+ # Pick an arbitrary item if no specified keyword is expected.
180
+ formal_name = (set (actual_type .items .keys ()) - self .kwargs_used ).pop ()
181
+ self .kwargs_used .add (formal_name )
182
+ return actual_type .items [formal_name ]
183
+ elif (isinstance (actual_type , Instance )
184
+ and (actual_type .type .fullname () == 'builtins.dict' )):
185
+ # Dict **arg.
186
+ # TODO: Handle arbitrary Mapping
187
+ return actual_type .args [1 ]
113
188
else :
114
189
return AnyType (TypeOfAny .from_error )
115
- elif isinstance (arg_type , TupleType ):
116
- # Get the next tuple item of a tuple *arg.
117
- tuple_counter [0 ] += 1
118
- return arg_type .items [tuple_counter [0 ] - 1 ]
119
- else :
120
- return AnyType (TypeOfAny .from_error )
121
- elif kind == nodes .ARG_STAR2 :
122
- if isinstance (arg_type , Instance ) and (arg_type .type .fullname () == 'builtins.dict' ):
123
- # Dict **arg. TODO more general (Mapping)
124
- return arg_type .args [1 ]
125
190
else :
126
- return AnyType (TypeOfAny .from_error )
127
- else :
128
- # No translation for other kinds.
129
- return arg_type
191
+ # No translation for other kinds -- 1:1 mapping.
192
+ return actual_type
0 commit comments