Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/astroid/arguments.py: 82%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

133 statements  

1# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html 

2# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE 

3# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt 

4 

5from __future__ import annotations 

6 

7from astroid import nodes 

8from astroid.bases import Instance 

9from astroid.context import CallContext, InferenceContext 

10from astroid.exceptions import InferenceError, NoDefault 

11from astroid.typing import InferenceResult 

12from astroid.util import Uninferable, UninferableBase, safe_infer 

13 

14 

15class CallSite: 

16 """Class for understanding arguments passed into a call site. 

17 

18 It needs a call context, which contains the arguments and the 

19 keyword arguments that were passed into a given call site. 

20 In order to infer what an argument represents, call :meth:`infer_argument` 

21 with the corresponding function node and the argument name. 

22 

23 :param callcontext: 

24 An instance of :class:`astroid.context.CallContext`, that holds 

25 the arguments for the call site. 

26 :param argument_context_map: 

27 Additional contexts per node, passed in from :attr:`astroid.context.Context.extra_context` 

28 :param context: 

29 An instance of :class:`astroid.context.Context`. 

30 """ 

31 

32 def __init__( 

33 self, 

34 callcontext: CallContext, 

35 argument_context_map=None, 

36 context: InferenceContext | None = None, 

37 ): 

38 if argument_context_map is None: 

39 argument_context_map = {} 

40 self.argument_context_map = argument_context_map 

41 args = callcontext.args 

42 keywords = callcontext.keywords 

43 self.duplicated_keywords: set[str] = set() 

44 self._unpacked_args = self._unpack_args(args, context=context) 

45 self._unpacked_kwargs = self._unpack_keywords(keywords, context=context) 

46 

47 self.positional_arguments = [ 

48 arg for arg in self._unpacked_args if not isinstance(arg, UninferableBase) 

49 ] 

50 self.keyword_arguments = { 

51 key: value 

52 for key, value in self._unpacked_kwargs.items() 

53 if not isinstance(value, UninferableBase) 

54 } 

55 

56 @classmethod 

57 def from_call(cls, call_node, context: InferenceContext | None = None): 

58 """Get a CallSite object from the given Call node. 

59 

60 context will be used to force a single inference path. 

61 """ 

62 

63 # Determine the callcontext from the given `context` object if any. 

64 context = context or InferenceContext() 

65 callcontext = CallContext(call_node.args, call_node.keywords) 

66 return cls(callcontext, context=context) 

67 

68 def has_invalid_arguments(self): 

69 """Check if in the current CallSite were passed *invalid* arguments. 

70 

71 This can mean multiple things. For instance, if an unpacking 

72 of an invalid object was passed, then this method will return True. 

73 Other cases can be when the arguments can't be inferred by astroid, 

74 for example, by passing objects which aren't known statically. 

75 """ 

76 return len(self.positional_arguments) != len(self._unpacked_args) 

77 

78 def has_invalid_keywords(self) -> bool: 

79 """Check if in the current CallSite were passed *invalid* keyword arguments. 

80 

81 For instance, unpacking a dictionary with integer keys is invalid 

82 (**{1:2}), because the keys must be strings, which will make this 

83 method to return True. Other cases where this might return True if 

84 objects which can't be inferred were passed. 

85 """ 

86 return len(self.keyword_arguments) != len(self._unpacked_kwargs) 

87 

88 def _unpack_keywords( 

89 self, 

90 keywords: list[tuple[str | None, nodes.NodeNG]], 

91 context: InferenceContext | None = None, 

92 ): 

93 values: dict[str | None, InferenceResult] = {} 

94 context = context or InferenceContext() 

95 context.extra_context = self.argument_context_map 

96 for name, value in keywords: 

97 if name is None: 

98 # Then it's an unpacking operation (**) 

99 inferred = safe_infer(value, context=context) 

100 if not isinstance(inferred, nodes.Dict): 

101 # Not something we can work with. 

102 values[name] = Uninferable 

103 continue 

104 

105 for dict_key, dict_value in inferred.items: 

106 dict_key = safe_infer(dict_key, context=context) 

107 if not isinstance(dict_key, nodes.Const): 

108 values[name] = Uninferable 

109 continue 

110 if not isinstance(dict_key.value, str): 

111 values[name] = Uninferable 

112 continue 

113 if dict_key.value in values: 

114 # The name is already in the dictionary 

115 values[dict_key.value] = Uninferable 

116 self.duplicated_keywords.add(dict_key.value) 

117 continue 

118 values[dict_key.value] = dict_value 

119 else: 

120 values[name] = value 

121 return values 

122 

123 def _unpack_args(self, args, context: InferenceContext | None = None): 

124 values = [] 

125 context = context or InferenceContext() 

126 context.extra_context = self.argument_context_map 

127 for arg in args: 

128 if isinstance(arg, nodes.Starred): 

129 inferred = safe_infer(arg.value, context=context) 

130 if isinstance(inferred, UninferableBase): 

131 values.append(Uninferable) 

132 continue 

133 if not hasattr(inferred, "elts"): 

134 values.append(Uninferable) 

135 continue 

136 values.extend(inferred.elts) 

137 else: 

138 values.append(arg) 

139 return values 

140 

141 def infer_argument( 

142 self, funcnode: InferenceResult, name: str, context: InferenceContext 

143 ): # noqa: C901 

144 """Infer a function argument value according to the call context.""" 

145 if not isinstance(funcnode, (nodes.FunctionDef, nodes.Lambda)): 

146 raise InferenceError( 

147 f"Can not infer function argument value for non-function node {funcnode!r}.", 

148 call_site=self, 

149 func=funcnode, 

150 arg=name, 

151 context=context, 

152 ) 

153 

154 if name in self.duplicated_keywords: 

155 raise InferenceError( 

156 "The arguments passed to {func!r} have duplicate keywords.", 

157 call_site=self, 

158 func=funcnode, 

159 arg=name, 

160 context=context, 

161 ) 

162 

163 # Look into the keywords first, maybe it's already there. 

164 try: 

165 return self.keyword_arguments[name].infer(context) 

166 except KeyError: 

167 pass 

168 

169 # Too many arguments given and no variable arguments. 

170 if len(self.positional_arguments) > len(funcnode.args.args): 

171 if not funcnode.args.vararg and not funcnode.args.posonlyargs: 

172 raise InferenceError( 

173 "Too many positional arguments " 

174 "passed to {func!r} that does " 

175 "not have *args.", 

176 call_site=self, 

177 func=funcnode, 

178 arg=name, 

179 context=context, 

180 ) 

181 

182 positional = self.positional_arguments[: len(funcnode.args.args)] 

183 vararg = self.positional_arguments[len(funcnode.args.args) :] 

184 

185 # preserving previous behavior, when vararg and kwarg were not included in find_argname results 

186 if name in [funcnode.args.vararg, funcnode.args.kwarg]: 

187 argindex = None 

188 else: 

189 argindex = funcnode.args.find_argname(name)[0] 

190 

191 kwonlyargs = {arg.name for arg in funcnode.args.kwonlyargs} 

192 kwargs = { 

193 key: value 

194 for key, value in self.keyword_arguments.items() 

195 if key not in kwonlyargs 

196 } 

197 # If there are too few positionals compared to 

198 # what the function expects to receive, check to see 

199 # if the missing positional arguments were passed 

200 # as keyword arguments and if so, place them into the 

201 # positional args list. 

202 if len(positional) < len(funcnode.args.args): 

203 for func_arg in funcnode.args.args: 

204 if func_arg.name in kwargs: 

205 arg = kwargs.pop(func_arg.name) 

206 positional.append(arg) 

207 

208 if argindex is not None: 

209 boundnode = context.boundnode 

210 # 2. first argument of instance/class method 

211 if argindex == 0 and funcnode.type in {"method", "classmethod"}: 

212 # context.boundnode is None when an instance method is called with 

213 # the class, e.g. MyClass.method(obj, ...). In this case, self 

214 # is the first argument. 

215 if boundnode is None and funcnode.type == "method" and positional: 

216 return positional[0].infer(context=context) 

217 if boundnode is None: 

218 # XXX can do better ? 

219 boundnode = funcnode.parent.frame() 

220 

221 if isinstance(boundnode, nodes.ClassDef): 

222 # Verify that we're accessing a method 

223 # of the metaclass through a class, as in 

224 # `cls.metaclass_method`. In this case, the 

225 # first argument is always the class. 

226 method_scope = funcnode.parent.scope() 

227 if method_scope is boundnode.metaclass(context=context): 

228 return iter((boundnode,)) 

229 

230 if funcnode.type == "method": 

231 if not isinstance(boundnode, Instance): 

232 boundnode = boundnode.instantiate_class() 

233 return iter((boundnode,)) 

234 if funcnode.type == "classmethod": 

235 return iter((boundnode,)) 

236 # if we have a method, extract one position 

237 # from the index, so we'll take in account 

238 # the extra parameter represented by `self` or `cls` 

239 if funcnode.type in {"method", "classmethod"} and boundnode: 

240 argindex -= 1 

241 # 2. search arg index 

242 try: 

243 return self.positional_arguments[argindex].infer(context) 

244 except IndexError: 

245 pass 

246 

247 if funcnode.args.kwarg == name: 

248 # It wants all the keywords that were passed into 

249 # the call site. 

250 if self.has_invalid_keywords(): 

251 raise InferenceError( 

252 "Inference failed to find values for all keyword arguments " 

253 "to {func!r}: {unpacked_kwargs!r} doesn't correspond to " 

254 "{keyword_arguments!r}.", 

255 keyword_arguments=self.keyword_arguments, 

256 unpacked_kwargs=self._unpacked_kwargs, 

257 call_site=self, 

258 func=funcnode, 

259 arg=name, 

260 context=context, 

261 ) 

262 kwarg = nodes.Dict( 

263 lineno=funcnode.args.lineno, 

264 col_offset=funcnode.args.col_offset, 

265 parent=funcnode.args, 

266 end_lineno=funcnode.args.end_lineno, 

267 end_col_offset=funcnode.args.end_col_offset, 

268 ) 

269 kwarg.postinit( 

270 [(nodes.const_factory(key), value) for key, value in kwargs.items()] 

271 ) 

272 return iter((kwarg,)) 

273 if funcnode.args.vararg == name: 

274 # It wants all the args that were passed into 

275 # the call site. 

276 if self.has_invalid_arguments(): 

277 raise InferenceError( 

278 "Inference failed to find values for all positional " 

279 "arguments to {func!r}: {unpacked_args!r} doesn't " 

280 "correspond to {positional_arguments!r}.", 

281 positional_arguments=self.positional_arguments, 

282 unpacked_args=self._unpacked_args, 

283 call_site=self, 

284 func=funcnode, 

285 arg=name, 

286 context=context, 

287 ) 

288 args = nodes.Tuple( 

289 lineno=funcnode.args.lineno, 

290 col_offset=funcnode.args.col_offset, 

291 parent=funcnode.args, 

292 ) 

293 args.postinit(vararg) 

294 return iter((args,)) 

295 

296 # Check if it's a default parameter. 

297 try: 

298 return funcnode.args.default_value(name).infer(context) 

299 except NoDefault: 

300 pass 

301 raise InferenceError( 

302 "No value found for argument {arg} to {func!r}", 

303 call_site=self, 

304 func=funcnode, 

305 arg=name, 

306 context=context, 

307 )