Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jedi/inference/param.py: 13%

130 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

1from collections import defaultdict 

2from inspect import Parameter 

3 

4from jedi import debug 

5from jedi.inference.utils import PushBackIterator 

6from jedi.inference import analysis 

7from jedi.inference.lazy_value import LazyKnownValue, \ 

8 LazyTreeValue, LazyUnknownValue 

9from jedi.inference.value import iterable 

10from jedi.inference.names import ParamName 

11 

12 

13def _add_argument_issue(error_name, lazy_value, message): 

14 if isinstance(lazy_value, LazyTreeValue): 

15 node = lazy_value.data 

16 if node.parent.type == 'argument': 

17 node = node.parent 

18 return analysis.add(lazy_value.context, error_name, node, message) 

19 

20 

21class ExecutedParamName(ParamName): 

22 def __init__(self, function_value, arguments, param_node, lazy_value, is_default=False): 

23 super().__init__(function_value, param_node.name, arguments=arguments) 

24 self._lazy_value = lazy_value 

25 self._is_default = is_default 

26 

27 def infer(self): 

28 return self._lazy_value.infer() 

29 

30 def matches_signature(self): 

31 if self._is_default: 

32 return True 

33 argument_values = self.infer().py__class__() 

34 if self.get_kind() in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD): 

35 return True 

36 annotations = self.infer_annotation(execute_annotation=False) 

37 if not annotations: 

38 # If we cannot infer annotations - or there aren't any - pretend 

39 # that the signature matches. 

40 return True 

41 matches = any(c1.is_sub_class_of(c2) 

42 for c1 in argument_values 

43 for c2 in annotations.gather_annotation_classes()) 

44 debug.dbg("param compare %s: %s <=> %s", 

45 matches, argument_values, annotations, color='BLUE') 

46 return matches 

47 

48 def __repr__(self): 

49 return '<%s: %s>' % (self.__class__.__name__, self.string_name) 

50 

51 

52def get_executed_param_names_and_issues(function_value, arguments): 

53 """ 

54 Return a tuple of: 

55 - a list of `ExecutedParamName`s corresponding to the arguments of the 

56 function execution `function_value`, containing the inferred value of 

57 those arguments (whether explicit or default) 

58 - a list of the issues encountered while building that list 

59 

60 For example, given: 

61 ``` 

62 def foo(a, b, c=None, d='d'): ... 

63 

64 foo(42, c='c') 

65 ``` 

66 

67 Then for the execution of `foo`, this will return a tuple containing: 

68 - a list with entries for each parameter a, b, c & d; the entries for a, 

69 c, & d will have their values (42, 'c' and 'd' respectively) included. 

70 - a list with a single entry about the lack of a value for `b` 

71 """ 

72 def too_many_args(argument): 

73 m = _error_argument_count(funcdef, len(unpacked_va)) 

74 # Just report an error for the first param that is not needed (like 

75 # cPython). 

76 if arguments.get_calling_nodes(): 

77 # There might not be a valid calling node so check for that first. 

78 issues.append( 

79 _add_argument_issue( 

80 'type-error-too-many-arguments', 

81 argument, 

82 message=m 

83 ) 

84 ) 

85 else: 

86 issues.append(None) 

87 debug.warning('non-public warning: %s', m) 

88 

89 issues = [] # List[Optional[analysis issue]] 

90 result_params = [] 

91 param_dict = {} 

92 funcdef = function_value.tree_node 

93 # Default params are part of the value where the function was defined. 

94 # This means that they might have access on class variables that the 

95 # function itself doesn't have. 

96 default_param_context = function_value.get_default_param_context() 

97 

98 for param in funcdef.get_params(): 

99 param_dict[param.name.value] = param 

100 unpacked_va = list(arguments.unpack(funcdef)) 

101 var_arg_iterator = PushBackIterator(iter(unpacked_va)) 

102 

103 non_matching_keys = defaultdict(lambda: []) 

104 keys_used = {} 

105 keys_only = False 

106 had_multiple_value_error = False 

107 for param in funcdef.get_params(): 

108 # The value and key can both be null. There, the defaults apply. 

109 # args / kwargs will just be empty arrays / dicts, respectively. 

110 # Wrong value count is just ignored. If you try to test cases that are 

111 # not allowed in Python, Jedi will maybe not show any completions. 

112 is_default = False 

113 key, argument = next(var_arg_iterator, (None, None)) 

114 while key is not None: 

115 keys_only = True 

116 try: 

117 key_param = param_dict[key] 

118 except KeyError: 

119 non_matching_keys[key] = argument 

120 else: 

121 if key in keys_used: 

122 had_multiple_value_error = True 

123 m = ("TypeError: %s() got multiple values for keyword argument '%s'." 

124 % (funcdef.name, key)) 

125 for contextualized_node in arguments.get_calling_nodes(): 

126 issues.append( 

127 analysis.add(contextualized_node.context, 

128 'type-error-multiple-values', 

129 contextualized_node.node, message=m) 

130 ) 

131 else: 

132 keys_used[key] = ExecutedParamName( 

133 function_value, arguments, key_param, argument) 

134 key, argument = next(var_arg_iterator, (None, None)) 

135 

136 try: 

137 result_params.append(keys_used[param.name.value]) 

138 continue 

139 except KeyError: 

140 pass 

141 

142 if param.star_count == 1: 

143 # *args param 

144 lazy_value_list = [] 

145 if argument is not None: 

146 lazy_value_list.append(argument) 

147 for key, argument in var_arg_iterator: 

148 # Iterate until a key argument is found. 

149 if key: 

150 var_arg_iterator.push_back((key, argument)) 

151 break 

152 lazy_value_list.append(argument) 

153 seq = iterable.FakeTuple(function_value.inference_state, lazy_value_list) 

154 result_arg = LazyKnownValue(seq) 

155 elif param.star_count == 2: 

156 if argument is not None: 

157 too_many_args(argument) 

158 # **kwargs param 

159 dct = iterable.FakeDict(function_value.inference_state, dict(non_matching_keys)) 

160 result_arg = LazyKnownValue(dct) 

161 non_matching_keys = {} 

162 else: 

163 # normal param 

164 if argument is None: 

165 # No value: Return an empty container 

166 if param.default is None: 

167 result_arg = LazyUnknownValue() 

168 if not keys_only: 

169 for contextualized_node in arguments.get_calling_nodes(): 

170 m = _error_argument_count(funcdef, len(unpacked_va)) 

171 issues.append( 

172 analysis.add( 

173 contextualized_node.context, 

174 'type-error-too-few-arguments', 

175 contextualized_node.node, 

176 message=m, 

177 ) 

178 ) 

179 else: 

180 result_arg = LazyTreeValue(default_param_context, param.default) 

181 is_default = True 

182 else: 

183 result_arg = argument 

184 

185 result_params.append(ExecutedParamName( 

186 function_value, arguments, param, result_arg, is_default=is_default 

187 )) 

188 if not isinstance(result_arg, LazyUnknownValue): 

189 keys_used[param.name.value] = result_params[-1] 

190 

191 if keys_only: 

192 # All arguments should be handed over to the next function. It's not 

193 # about the values inside, it's about the names. Jedi needs to now that 

194 # there's nothing to find for certain names. 

195 for k in set(param_dict) - set(keys_used): 

196 param = param_dict[k] 

197 

198 if not (non_matching_keys or had_multiple_value_error 

199 or param.star_count or param.default): 

200 # add a warning only if there's not another one. 

201 for contextualized_node in arguments.get_calling_nodes(): 

202 m = _error_argument_count(funcdef, len(unpacked_va)) 

203 issues.append( 

204 analysis.add(contextualized_node.context, 

205 'type-error-too-few-arguments', 

206 contextualized_node.node, message=m) 

207 ) 

208 

209 for key, lazy_value in non_matching_keys.items(): 

210 m = "TypeError: %s() got an unexpected keyword argument '%s'." \ 

211 % (funcdef.name, key) 

212 issues.append( 

213 _add_argument_issue( 

214 'type-error-keyword-argument', 

215 lazy_value, 

216 message=m 

217 ) 

218 ) 

219 

220 remaining_arguments = list(var_arg_iterator) 

221 if remaining_arguments: 

222 first_key, lazy_value = remaining_arguments[0] 

223 too_many_args(lazy_value) 

224 return result_params, issues 

225 

226 

227def get_executed_param_names(function_value, arguments): 

228 """ 

229 Return a list of `ExecutedParamName`s corresponding to the arguments of the 

230 function execution `function_value`, containing the inferred value of those 

231 arguments (whether explicit or default). Any issues building this list (for 

232 example required arguments which are missing in the invocation) are ignored. 

233 

234 For example, given: 

235 ``` 

236 def foo(a, b, c=None, d='d'): ... 

237 

238 foo(42, c='c') 

239 ``` 

240 

241 Then for the execution of `foo`, this will return a list containing entries 

242 for each parameter a, b, c & d; the entries for a, c, & d will have their 

243 values (42, 'c' and 'd' respectively) included. 

244 """ 

245 return get_executed_param_names_and_issues(function_value, arguments)[0] 

246 

247 

248def _error_argument_count(funcdef, actual_count): 

249 params = funcdef.get_params() 

250 default_arguments = sum(1 for p in params if p.default or p.star_count) 

251 

252 if default_arguments == 0: 

253 before = 'exactly ' 

254 else: 

255 before = 'from %s to ' % (len(params) - default_arguments) 

256 return ('TypeError: %s() takes %s%s arguments (%s given).' 

257 % (funcdef.name, before, len(params), actual_count))