2 PEP 0484 ( https://www.python.org/dev/peps/pep-0484/ ) describes type hints
3 through function annotations. There is a strong suggestion in this document
4 that only the type of type hinting defined in PEP0484 should be allowed
5 as annotations in future python versions.
9 from inspect
import Parameter
11 from parso
import ParserSyntaxError
, parse
13 from jedi
.inference
.cache
import inference_state_method_cache
14 from jedi
.inference
.base_value
import ValueSet
, NO_VALUES
15 from jedi
.inference
.gradual
.base
import DefineGenericBaseClass
, GenericClass
16 from jedi
.inference
.gradual
.generics
import TupleGenericManager
17 from jedi
.inference
.gradual
.type_var
import TypeVar
18 from jedi
.inference
.helpers
import is_string
19 from jedi
.inference
.compiled
import builtin_from_name
20 from jedi
.inference
.param
import get_executed_param_names
21 from jedi
import debug
22 from jedi
import parser_utils
25 def infer_annotation(context
, annotation
):
27 Inferes an annotation node. This means that it inferes the part of
32 Also checks for forward references (strings)
34 value_set
= context
.infer_node(annotation
)
35 if len(value_set
) != 1:
36 debug
.warning("Inferred typing index %s should lead to 1 object, "
37 " not %s" % (annotation
, value_set
))
40 inferred_value
= list(value_set
)[0]
41 if is_string(inferred_value
):
42 result
= _get_forward_reference_node(context
, inferred_value
.get_safe_value())
43 if result
is not None:
44 return context
.infer_node(result
)
48 def _infer_annotation_string(context
, string
, index
=None):
49 node
= _get_forward_reference_node(context
, string
)
53 value_set
= context
.infer_node(node
)
55 value_set
= value_set
.filter(
57 value
.array_type
== 'tuple'
58 and len(list(value
.py__iter__())) >= index
60 ).py__simple_getitem__(index
)
64 def _get_forward_reference_node(context
, string
):
66 new_node
= context
.inference_state
.grammar
.parse(
68 start_symbol
='eval_input',
71 except ParserSyntaxError
:
72 debug
.warning('Annotation not parsed: %s' % string
)
75 module
= context
.tree_node
.get_root_node()
76 parser_utils
.move(new_node
, module
.end_pos
[0])
77 new_node
.parent
= context
.tree_node
81 def _split_comment_param_declaration(decl_text
):
83 Split decl_text on commas, but group generic expressions
86 For example, given "foo, Bar[baz, biz]" we return
87 ['foo', 'Bar[baz, biz]'].
91 node
= parse(decl_text
, error_recovery
=False).children
[0]
92 except ParserSyntaxError
:
93 debug
.warning('Comment annotation is not valid Python: %s' % decl_text
)
96 if node
.type in ['name', 'atom_expr', 'power']:
97 return [node
.get_code().strip()]
101 children
= node
.children
102 except AttributeError:
105 for child
in children
:
106 if child
.type in ['name', 'atom_expr', 'power']:
107 params
.append(child
.get_code().strip())
112 @inference_state_method_cache()
113 def infer_param(function_value
, param
, ignore_stars
=False):
114 values
= _infer_param(function_value
, param
)
115 if ignore_stars
or not values
:
117 inference_state
= function_value
.inference_state
118 if param
.star_count
== 1:
119 tuple_
= builtin_from_name(inference_state
, 'tuple')
120 return ValueSet([GenericClass(
122 TupleGenericManager((values
,)),
124 elif param
.star_count
== 2:
125 dct
= builtin_from_name(inference_state
, 'dict')
127 ValueSet([builtin_from_name(inference_state
, 'str')]),
130 return ValueSet([GenericClass(
132 TupleGenericManager(generics
),
137 def _infer_param(function_value
, param
):
139 Infers the type of a function parameter, using type annotations.
141 annotation
= param
.annotation
142 if annotation
is None:
143 # If no Python 3-style annotation, look for a comment annotation.
144 # Identify parameters to function in the same sequence as they would
145 # appear in a type comment.
146 all_params
= [child
for child
in param
.parent
.children
147 if child
.type == 'param']
149 node
= param
.parent
.parent
150 comment
= parser_utils
.get_following_comment_same_line(node
)
154 match
= re
.match(r
"^#\s*type:\s*\(([^#]*)\)\s*->", comment
)
157 params_comments
= _split_comment_param_declaration(match
.group(1))
159 # Find the specific param being investigated
160 index
= all_params
.index(param
)
161 # If the number of parameters doesn't match length of type comment,
162 # ignore first parameter (assume it's self).
163 if len(params_comments
) != len(all_params
):
165 "Comments length != Params length %s %s",
166 params_comments
, all_params
168 if function_value
.is_bound_method():
170 # Assume it's self, which is already handled
173 if index
>= len(params_comments
):
176 param_comment
= params_comments
[index
]
177 return _infer_annotation_string(
178 function_value
.get_default_param_context(),
181 # Annotations are like default params and resolve in the same way.
182 context
= function_value
.get_default_param_context()
183 return infer_annotation(context
, annotation
)
186 def py__annotations__(funcdef
):
188 for function_param
in funcdef
.get_params():
189 param_annotation
= function_param
.annotation
190 if param_annotation
is not None:
191 dct
[function_param
.name
.value
] = param_annotation
193 return_annotation
= funcdef
.annotation
194 if return_annotation
:
195 dct
['return'] = return_annotation
199 def resolve_forward_references(context
, all_annotations
):
201 if node
is None or node
.type != 'string':
204 node
= _get_forward_reference_node(
206 context
.inference_state
.compiled_subprocess
.safe_literal_eval(
212 # There was a string, but it's not a valid annotation
215 # The forward reference tree has an additional root node ('eval_input')
216 # that we don't want. Extract the node we do want, that is equivalent to
217 # the nodes returned by `py__annotations__` for a non-quoted node.
218 node
= node
.children
[0]
222 return {name
: resolve(node
) for name
, node
in all_annotations
.items()}
225 @inference_state_method_cache()
226 def infer_return_types(function
, arguments
):
228 Infers the type of a function's return value,
229 according to type annotations.
231 context
= function
.get_default_param_context()
232 all_annotations
= resolve_forward_references(
234 py__annotations__(function
.tree_node
),
236 annotation
= all_annotations
.get("return", None)
237 if annotation
is None:
238 # If there is no Python 3-type annotation, look for an annotation
240 node
= function
.tree_node
241 comment
= parser_utils
.get_following_comment_same_line(node
)
245 match
= re
.match(r
"^#\s*type:\s*\([^#]*\)\s*->\s*([^#]*)", comment
)
249 return _infer_annotation_string(
251 match
.group(1).strip()
252 ).execute_annotation()
254 unknown_type_vars
= find_unknown_type_vars(context
, annotation
)
255 annotation_values
= infer_annotation(context
, annotation
)
256 if not unknown_type_vars
:
257 return annotation_values
.execute_annotation()
259 type_var_dict
= infer_type_vars_for_execution(function
, arguments
, all_annotations
)
261 return ValueSet
.from_sets(
262 ann
.define_generics(type_var_dict
)
263 if isinstance(ann
, (DefineGenericBaseClass
, TypeVar
)) else ValueSet({ann}
)
264 for ann
in annotation_values
265 ).execute_annotation()
268 def infer_type_vars_for_execution(function
, arguments
, annotation_dict
):
270 Some functions use type vars that are not defined by the class, but rather
271 only defined in the function. See for example `iter`. In those cases we
274 1. Search for undefined type vars.
275 2. Infer type vars with the execution state we have.
276 3. Return the union of all type vars that have been found.
278 context
= function
.get_default_param_context()
280 annotation_variable_results
= {}
281 executed_param_names
= get_executed_param_names(function
, arguments
)
282 for executed_param_name
in executed_param_names
:
284 annotation_node
= annotation_dict
[executed_param_name
.string_name
]
288 annotation_variables
= find_unknown_type_vars(context
, annotation_node
)
289 if annotation_variables
:
290 # Infer unknown type var
291 annotation_value_set
= context
.infer_node(annotation_node
)
292 kind
= executed_param_name
.get_kind()
293 actual_value_set
= executed_param_name
.infer()
294 if kind
is Parameter
.VAR_POSITIONAL
:
295 actual_value_set
= actual_value_set
.merge_types_of_iterate()
296 elif kind
is Parameter
.VAR_KEYWORD
:
297 # TODO _dict_values is not public.
298 actual_value_set
= actual_value_set
.try_merge('_dict_values')
299 merge_type_var_dicts(
300 annotation_variable_results
,
301 annotation_value_set
.infer_type_vars(actual_value_set
),
303 return annotation_variable_results
306 def infer_return_for_callable(arguments
, param_values
, result_values
):
308 for pv
in param_values
:
309 if pv
.array_type
== 'list':
310 type_var_dict
= _infer_type_vars_for_callable(arguments
, pv
.py__iter__())
311 all_type_vars
.update(type_var_dict
)
313 return ValueSet
.from_sets(
314 v
.define_generics(all_type_vars
)
315 if isinstance(v
, (DefineGenericBaseClass
, TypeVar
))
317 for v
in result_values
318 ).execute_annotation()
321 def _infer_type_vars_for_callable(arguments
, lazy_params
):
323 Infers type vars for the Calllable class:
325 def x() -> Callable[[Callable[..., _T]], _T]: ...
327 annotation_variable_results
= {}
328 for (_
, lazy_value
), lazy_callable_param
in zip(arguments
.unpack(), lazy_params
):
329 callable_param_values
= lazy_callable_param
.infer()
330 # Infer unknown type var
331 actual_value_set
= lazy_value
.infer()
332 merge_type_var_dicts(
333 annotation_variable_results
,
334 callable_param_values
.infer_type_vars(actual_value_set
),
336 return annotation_variable_results
339 def merge_type_var_dicts(base_dict
, new_dict
):
340 for type_var_name
, values
in new_dict
.items():
343 base_dict
[type_var_name
] |
= values
345 base_dict
[type_var_name
] = values
348 def merge_pairwise_generics(annotation_value
, annotated_argument_class
):
350 Match up the generic parameters from the given argument class to the
353 This walks the generic parameters immediately within the annotation and
354 argument's type, in order to determine the concrete values of the
355 annotation's parameters for the current case.
357 For example, given the following code:
359 def values(mapping: Mapping[K, V]) -> List[V]: ...
361 for val in values({1: 'a'}):
364 Then this function should be given representations of `Mapping[K, V]`
365 and `Mapping[int, str]`, so that it can determine that `K` is `int and
368 Note that it is responsibility of the caller to traverse the MRO of the
369 argument type as needed in order to find the type matching the
370 annotation (in this case finding `Mapping[int, str]` as a parent of
376 `annotation_value`: represents the annotation to infer the concrete
379 `annotated_argument_class`: represents the annotated class of the
380 argument being passed to the object annotated by `annotation_value`.
385 if not isinstance(annotated_argument_class
, DefineGenericBaseClass
):
388 annotation_generics
= annotation_value
.get_generics()
389 actual_generics
= annotated_argument_class
.get_generics()
391 for annotation_generics_set
, actual_generic_set
in zip(annotation_generics
, actual_generics
):
392 merge_type_var_dicts(
394 annotation_generics_set
.infer_type_vars(actual_generic_set
.execute_annotation()),
400 def find_type_from_comment_hint_for(context
, node
, name
):
401 return _find_type_from_comment_hint(context
, node
, node
.children
[1], name
)
404 def find_type_from_comment_hint_with(context
, node
, name
):
405 if len(node
.children
) > 4:
406 # In case there are multiple with_items, we do not want a type hint for
409 assert len(node
.children
[1].children
) == 3, \
410 "Can only be here when children[1] is 'foo() as f'"
411 varlist
= node
.children
[1].children
[2]
412 return _find_type_from_comment_hint(context
, node
, varlist
, name
)
415 def find_type_from_comment_hint_assign(context
, node
, name
):
416 return _find_type_from_comment_hint(context
, node
, node
.children
[0], name
)
419 def _find_type_from_comment_hint(context
, node
, varlist
, name
):
421 if varlist
.type in ("testlist_star_expr", "exprlist", "testlist"):
422 # something like "a, b = 1, 2"
424 for child
in varlist
.children
:
427 if child
.type == "operator":
433 comment
= parser_utils
.get_following_comment_same_line(node
)
436 match
= re
.match(r
"^#\s*type:\s*([^#]*)", comment
)
439 return _infer_annotation_string(
440 context
, match
.group(1).strip(), index
441 ).execute_annotation()
444 def find_unknown_type_vars(context
, node
):
445 def check_node(node
):
446 if node
.type in ('atom_expr', 'power'):
447 trailer
= node
.children
[-1]
448 if trailer
.type == 'trailer' and trailer
.children
[0] == '[':
449 for subscript_node
in _unpack_subscriptlist(trailer
.children
[1]):
450 check_node(subscript_node
)
452 found
[:] = _filter_type_vars(context
.infer_node(node
), found
)
454 found
= [] # We're not using a set, because the order matters.
459 def _filter_type_vars(value_set
, found
=()):
460 new_found
= list(found
)
461 for type_var
in value_set
:
462 if isinstance(type_var
, TypeVar
) and type_var
not in found
:
463 new_found
.append(type_var
)
467 def _unpack_subscriptlist(subscriptlist
):
468 if subscriptlist
.type == 'subscriptlist':
469 for subscript
in subscriptlist
.children
[::2]:
470 if subscript
.type != 'subscript':
473 if subscriptlist
.type != 'subscript':