param.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. from collections import defaultdict
  2. from inspect import Parameter
  3. from jedi import debug
  4. from jedi.inference.utils import PushBackIterator
  5. from jedi.inference import analysis
  6. from jedi.inference.lazy_value import LazyKnownValue, \
  7. LazyTreeValue, LazyUnknownValue
  8. from jedi.inference.value import iterable
  9. from jedi.inference.names import ParamName
  10. def _add_argument_issue(error_name, lazy_value, message):
  11. if isinstance(lazy_value, LazyTreeValue):
  12. node = lazy_value.data
  13. if node.parent.type == 'argument':
  14. node = node.parent
  15. return analysis.add(lazy_value.context, error_name, node, message)
  16. class ExecutedParamName(ParamName):
  17. def __init__(self, function_value, arguments, param_node, lazy_value, is_default=False):
  18. super().__init__(function_value, param_node.name, arguments=arguments)
  19. self._lazy_value = lazy_value
  20. self._is_default = is_default
  21. def infer(self):
  22. return self._lazy_value.infer()
  23. def matches_signature(self):
  24. if self._is_default:
  25. return True
  26. argument_values = self.infer().py__class__()
  27. if self.get_kind() in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD):
  28. return True
  29. annotations = self.infer_annotation(execute_annotation=False)
  30. if not annotations:
  31. # If we cannot infer annotations - or there aren't any - pretend
  32. # that the signature matches.
  33. return True
  34. matches = any(c1.is_sub_class_of(c2)
  35. for c1 in argument_values
  36. for c2 in annotations.gather_annotation_classes())
  37. debug.dbg("param compare %s: %s <=> %s",
  38. matches, argument_values, annotations, color='BLUE')
  39. return matches
  40. def __repr__(self):
  41. return '<%s: %s>' % (self.__class__.__name__, self.string_name)
  42. def get_executed_param_names_and_issues(function_value, arguments):
  43. """
  44. Return a tuple of:
  45. - a list of `ExecutedParamName`s corresponding to the arguments of the
  46. function execution `function_value`, containing the inferred value of
  47. those arguments (whether explicit or default)
  48. - a list of the issues encountered while building that list
  49. For example, given:
  50. ```
  51. def foo(a, b, c=None, d='d'): ...
  52. foo(42, c='c')
  53. ```
  54. Then for the execution of `foo`, this will return a tuple containing:
  55. - a list with entries for each parameter a, b, c & d; the entries for a,
  56. c, & d will have their values (42, 'c' and 'd' respectively) included.
  57. - a list with a single entry about the lack of a value for `b`
  58. """
  59. def too_many_args(argument):
  60. m = _error_argument_count(funcdef, len(unpacked_va))
  61. # Just report an error for the first param that is not needed (like
  62. # cPython).
  63. if arguments.get_calling_nodes():
  64. # There might not be a valid calling node so check for that first.
  65. issues.append(
  66. _add_argument_issue(
  67. 'type-error-too-many-arguments',
  68. argument,
  69. message=m
  70. )
  71. )
  72. else:
  73. issues.append(None)
  74. debug.warning('non-public warning: %s', m)
  75. issues = [] # List[Optional[analysis issue]]
  76. result_params = []
  77. param_dict = {}
  78. funcdef = function_value.tree_node
  79. # Default params are part of the value where the function was defined.
  80. # This means that they might have access on class variables that the
  81. # function itself doesn't have.
  82. default_param_context = function_value.get_default_param_context()
  83. for param in funcdef.get_params():
  84. param_dict[param.name.value] = param
  85. unpacked_va = list(arguments.unpack(funcdef))
  86. var_arg_iterator = PushBackIterator(iter(unpacked_va))
  87. non_matching_keys = defaultdict(lambda: [])
  88. keys_used = {}
  89. keys_only = False
  90. had_multiple_value_error = False
  91. for param in funcdef.get_params():
  92. # The value and key can both be null. There, the defaults apply.
  93. # args / kwargs will just be empty arrays / dicts, respectively.
  94. # Wrong value count is just ignored. If you try to test cases that are
  95. # not allowed in Python, Jedi will maybe not show any completions.
  96. is_default = False
  97. key, argument = next(var_arg_iterator, (None, None))
  98. while key is not None:
  99. keys_only = True
  100. try:
  101. key_param = param_dict[key]
  102. except KeyError:
  103. non_matching_keys[key] = argument
  104. else:
  105. if key in keys_used:
  106. had_multiple_value_error = True
  107. m = ("TypeError: %s() got multiple values for keyword argument '%s'."
  108. % (funcdef.name, key))
  109. for contextualized_node in arguments.get_calling_nodes():
  110. issues.append(
  111. analysis.add(contextualized_node.context,
  112. 'type-error-multiple-values',
  113. contextualized_node.node, message=m)
  114. )
  115. else:
  116. keys_used[key] = ExecutedParamName(
  117. function_value, arguments, key_param, argument)
  118. key, argument = next(var_arg_iterator, (None, None))
  119. try:
  120. result_params.append(keys_used[param.name.value])
  121. continue
  122. except KeyError:
  123. pass
  124. if param.star_count == 1:
  125. # *args param
  126. lazy_value_list = []
  127. if argument is not None:
  128. lazy_value_list.append(argument)
  129. for key, argument in var_arg_iterator:
  130. # Iterate until a key argument is found.
  131. if key:
  132. var_arg_iterator.push_back((key, argument))
  133. break
  134. lazy_value_list.append(argument)
  135. seq = iterable.FakeTuple(function_value.inference_state, lazy_value_list)
  136. result_arg = LazyKnownValue(seq)
  137. elif param.star_count == 2:
  138. if argument is not None:
  139. too_many_args(argument)
  140. # **kwargs param
  141. dct = iterable.FakeDict(function_value.inference_state, dict(non_matching_keys))
  142. result_arg = LazyKnownValue(dct)
  143. non_matching_keys = {}
  144. else:
  145. # normal param
  146. if argument is None:
  147. # No value: Return an empty container
  148. if param.default is None:
  149. result_arg = LazyUnknownValue()
  150. if not keys_only:
  151. for contextualized_node in arguments.get_calling_nodes():
  152. m = _error_argument_count(funcdef, len(unpacked_va))
  153. issues.append(
  154. analysis.add(
  155. contextualized_node.context,
  156. 'type-error-too-few-arguments',
  157. contextualized_node.node,
  158. message=m,
  159. )
  160. )
  161. else:
  162. result_arg = LazyTreeValue(default_param_context, param.default)
  163. is_default = True
  164. else:
  165. result_arg = argument
  166. result_params.append(ExecutedParamName(
  167. function_value, arguments, param, result_arg, is_default=is_default
  168. ))
  169. if not isinstance(result_arg, LazyUnknownValue):
  170. keys_used[param.name.value] = result_params[-1]
  171. if keys_only:
  172. # All arguments should be handed over to the next function. It's not
  173. # about the values inside, it's about the names. Jedi needs to now that
  174. # there's nothing to find for certain names.
  175. for k in set(param_dict) - set(keys_used):
  176. param = param_dict[k]
  177. if not (non_matching_keys or had_multiple_value_error
  178. or param.star_count or param.default):
  179. # add a warning only if there's not another one.
  180. for contextualized_node in arguments.get_calling_nodes():
  181. m = _error_argument_count(funcdef, len(unpacked_va))
  182. issues.append(
  183. analysis.add(contextualized_node.context,
  184. 'type-error-too-few-arguments',
  185. contextualized_node.node, message=m)
  186. )
  187. for key, lazy_value in non_matching_keys.items():
  188. m = "TypeError: %s() got an unexpected keyword argument '%s'." \
  189. % (funcdef.name, key)
  190. issues.append(
  191. _add_argument_issue(
  192. 'type-error-keyword-argument',
  193. lazy_value,
  194. message=m
  195. )
  196. )
  197. remaining_arguments = list(var_arg_iterator)
  198. if remaining_arguments:
  199. first_key, lazy_value = remaining_arguments[0]
  200. too_many_args(lazy_value)
  201. return result_params, issues
  202. def get_executed_param_names(function_value, arguments):
  203. """
  204. Return a list of `ExecutedParamName`s corresponding to the arguments of the
  205. function execution `function_value`, containing the inferred value of those
  206. arguments (whether explicit or default). Any issues building this list (for
  207. example required arguments which are missing in the invocation) are ignored.
  208. For example, given:
  209. ```
  210. def foo(a, b, c=None, d='d'): ...
  211. foo(42, c='c')
  212. ```
  213. Then for the execution of `foo`, this will return a list containing entries
  214. for each parameter a, b, c & d; the entries for a, c, & d will have their
  215. values (42, 'c' and 'd' respectively) included.
  216. """
  217. return get_executed_param_names_and_issues(function_value, arguments)[0]
  218. def _error_argument_count(funcdef, actual_count):
  219. params = funcdef.get_params()
  220. default_arguments = sum(1 for p in params if p.default or p.star_count)
  221. if default_arguments == 0:
  222. before = 'exactly '
  223. else:
  224. before = 'from %s to ' % (len(params) - default_arguments)
  225. return ('TypeError: %s() takes %s%s arguments (%s given).'
  226. % (funcdef.name, before, len(params), actual_count))