Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jedi/inference/value/klass.py: 27%

215 statements  

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

1""" 

2Like described in the :mod:`parso.python.tree` module, 

3there's a need for an ast like module to represent the states of parsed 

4modules. 

5 

6But now there are also structures in Python that need a little bit more than 

7that. An ``Instance`` for example is only a ``Class`` before it is 

8instantiated. This class represents these cases. 

9 

10So, why is there also a ``Class`` class here? Well, there are decorators and 

11they change classes in Python 3. 

12 

13Representation modules also define "magic methods". Those methods look like 

14``py__foo__`` and are typically mappable to the Python equivalents ``__call__`` 

15and others. Here's a list: 

16 

17====================================== ======================================== 

18**Method** **Description** 

19-------------------------------------- ---------------------------------------- 

20py__call__(arguments: Array) On callable objects, returns types. 

21py__bool__() Returns True/False/None; None means that 

22 there's no certainty. 

23py__bases__() Returns a list of base classes. 

24py__iter__() Returns a generator of a set of types. 

25py__class__() Returns the class of an instance. 

26py__simple_getitem__(index: int/str) Returns a a set of types of the index. 

27 Can raise an IndexError/KeyError. 

28py__getitem__(indexes: ValueSet) Returns a a set of types of the index. 

29py__file__() Only on modules. Returns None if does 

30 not exist. 

31py__package__() -> List[str] Only on modules. For the import system. 

32py__path__() Only on modules. For the import system. 

33py__get__(call_object) Only on instances. Simulates 

34 descriptors. 

35py__doc__() Returns the docstring for a value. 

36====================================== ======================================== 

37 

38""" 

39from jedi import debug 

40from jedi.parser_utils import get_cached_parent_scope, expr_is_dotted, \ 

41 function_is_property 

42from jedi.inference.cache import inference_state_method_cache, CachedMetaClass, \ 

43 inference_state_method_generator_cache 

44from jedi.inference import compiled 

45from jedi.inference.lazy_value import LazyKnownValues, LazyTreeValue 

46from jedi.inference.filters import ParserTreeFilter 

47from jedi.inference.names import TreeNameDefinition, ValueName 

48from jedi.inference.arguments import unpack_arglist, ValuesArguments 

49from jedi.inference.base_value import ValueSet, iterator_to_value_set, \ 

50 NO_VALUES 

51from jedi.inference.context import ClassContext 

52from jedi.inference.value.function import FunctionAndClassBase 

53from jedi.inference.gradual.generics import LazyGenericManager, TupleGenericManager 

54from jedi.plugins import plugin_manager 

55 

56 

57class ClassName(TreeNameDefinition): 

58 def __init__(self, class_value, tree_name, name_context, apply_decorators): 

59 super().__init__(name_context, tree_name) 

60 self._apply_decorators = apply_decorators 

61 self._class_value = class_value 

62 

63 @iterator_to_value_set 

64 def infer(self): 

65 # We're using a different value to infer, so we cannot call super(). 

66 from jedi.inference.syntax_tree import tree_name_to_values 

67 inferred = tree_name_to_values( 

68 self.parent_context.inference_state, self.parent_context, self.tree_name) 

69 

70 for result_value in inferred: 

71 if self._apply_decorators: 

72 yield from result_value.py__get__(instance=None, class_value=self._class_value) 

73 else: 

74 yield result_value 

75 

76 @property 

77 def api_type(self): 

78 type_ = super().api_type 

79 if type_ == 'function': 

80 definition = self.tree_name.get_definition() 

81 if definition is None: 

82 return type_ 

83 if function_is_property(definition): 

84 # This essentially checks if there is an @property before 

85 # the function. @property could be something different, but 

86 # any programmer that redefines property as something that 

87 # is not really a property anymore, should be shot. (i.e. 

88 # this is a heuristic). 

89 return 'property' 

90 return type_ 

91 

92 

93class ClassFilter(ParserTreeFilter): 

94 def __init__(self, class_value, node_context=None, until_position=None, 

95 origin_scope=None, is_instance=False): 

96 super().__init__( 

97 class_value.as_context(), node_context, 

98 until_position=until_position, 

99 origin_scope=origin_scope, 

100 ) 

101 self._class_value = class_value 

102 self._is_instance = is_instance 

103 

104 def _convert_names(self, names): 

105 return [ 

106 ClassName( 

107 class_value=self._class_value, 

108 tree_name=name, 

109 name_context=self._node_context, 

110 apply_decorators=not self._is_instance, 

111 ) for name in names 

112 ] 

113 

114 def _equals_origin_scope(self): 

115 node = self._origin_scope 

116 while node is not None: 

117 if node == self._parser_scope or node == self.parent_context: 

118 return True 

119 node = get_cached_parent_scope(self._parso_cache_node, node) 

120 return False 

121 

122 def _access_possible(self, name): 

123 # Filter for name mangling of private variables like __foo 

124 return not name.value.startswith('__') or name.value.endswith('__') \ 

125 or self._equals_origin_scope() 

126 

127 def _filter(self, names): 

128 names = super()._filter(names) 

129 return [name for name in names if self._access_possible(name)] 

130 

131 

132class ClassMixin: 

133 def is_class(self): 

134 return True 

135 

136 def is_class_mixin(self): 

137 return True 

138 

139 def py__call__(self, arguments): 

140 from jedi.inference.value import TreeInstance 

141 

142 from jedi.inference.gradual.typing import TypedDict 

143 if self.is_typeddict(): 

144 return ValueSet([TypedDict(self)]) 

145 return ValueSet([TreeInstance(self.inference_state, self.parent_context, self, arguments)]) 

146 

147 def py__class__(self): 

148 return compiled.builtin_from_name(self.inference_state, 'type') 

149 

150 @property 

151 def name(self): 

152 return ValueName(self, self.tree_node.name) 

153 

154 def py__name__(self): 

155 return self.name.string_name 

156 

157 @inference_state_method_generator_cache() 

158 def py__mro__(self): 

159 mro = [self] 

160 yield self 

161 # TODO Do a proper mro resolution. Currently we are just listing 

162 # classes. However, it's a complicated algorithm. 

163 for lazy_cls in self.py__bases__(): 

164 # TODO there's multiple different mro paths possible if this yields 

165 # multiple possibilities. Could be changed to be more correct. 

166 for cls in lazy_cls.infer(): 

167 # TODO detect for TypeError: duplicate base class str, 

168 # e.g. `class X(str, str): pass` 

169 try: 

170 mro_method = cls.py__mro__ 

171 except AttributeError: 

172 # TODO add a TypeError like: 

173 """ 

174 >>> class Y(lambda: test): pass 

175 Traceback (most recent call last): 

176 File "<stdin>", line 1, in <module> 

177 TypeError: function() argument 1 must be code, not str 

178 >>> class Y(1): pass 

179 Traceback (most recent call last): 

180 File "<stdin>", line 1, in <module> 

181 TypeError: int() takes at most 2 arguments (3 given) 

182 """ 

183 debug.warning('Super class of %s is not a class: %s', self, cls) 

184 else: 

185 for cls_new in mro_method(): 

186 if cls_new not in mro: 

187 mro.append(cls_new) 

188 yield cls_new 

189 

190 def get_filters(self, origin_scope=None, is_instance=False, 

191 include_metaclasses=True, include_type_when_class=True): 

192 if include_metaclasses: 

193 metaclasses = self.get_metaclasses() 

194 if metaclasses: 

195 yield from self.get_metaclass_filters(metaclasses, is_instance) 

196 

197 for cls in self.py__mro__(): 

198 if cls.is_compiled(): 

199 yield from cls.get_filters(is_instance=is_instance) 

200 else: 

201 yield ClassFilter( 

202 self, node_context=cls.as_context(), 

203 origin_scope=origin_scope, 

204 is_instance=is_instance 

205 ) 

206 if not is_instance and include_type_when_class: 

207 from jedi.inference.compiled import builtin_from_name 

208 type_ = builtin_from_name(self.inference_state, 'type') 

209 assert isinstance(type_, ClassValue) 

210 if type_ != self: 

211 # We are not using execute_with_values here, because the 

212 # plugin function for type would get executed instead of an 

213 # instance creation. 

214 args = ValuesArguments([]) 

215 for instance in type_.py__call__(args): 

216 instance_filters = instance.get_filters() 

217 # Filter out self filters 

218 next(instance_filters, None) 

219 next(instance_filters, None) 

220 x = next(instance_filters, None) 

221 assert x is not None 

222 yield x 

223 

224 def get_signatures(self): 

225 # Since calling staticmethod without a function is illegal, the Jedi 

226 # plugin doesn't return anything. Therefore call directly and get what 

227 # we want: An instance of staticmethod. 

228 metaclasses = self.get_metaclasses() 

229 if metaclasses: 

230 sigs = self.get_metaclass_signatures(metaclasses) 

231 if sigs: 

232 return sigs 

233 args = ValuesArguments([]) 

234 init_funcs = self.py__call__(args).py__getattribute__('__init__') 

235 return [sig.bind(self) for sig in init_funcs.get_signatures()] 

236 

237 def _as_context(self): 

238 return ClassContext(self) 

239 

240 def get_type_hint(self, add_class_info=True): 

241 if add_class_info: 

242 return 'Type[%s]' % self.py__name__() 

243 return self.py__name__() 

244 

245 @inference_state_method_cache(default=False) 

246 def is_typeddict(self): 

247 # TODO Do a proper mro resolution. Currently we are just listing 

248 # classes. However, it's a complicated algorithm. 

249 from jedi.inference.gradual.typing import TypedDictClass 

250 for lazy_cls in self.py__bases__(): 

251 if not isinstance(lazy_cls, LazyTreeValue): 

252 return False 

253 tree_node = lazy_cls.data 

254 # Only resolve simple classes, stuff like Iterable[str] are more 

255 # intensive to resolve and if generics are involved, we know it's 

256 # not a TypedDict. 

257 if not expr_is_dotted(tree_node): 

258 return False 

259 

260 for cls in lazy_cls.infer(): 

261 if isinstance(cls, TypedDictClass): 

262 return True 

263 try: 

264 method = cls.is_typeddict 

265 except AttributeError: 

266 # We're only dealing with simple classes, so just returning 

267 # here should be fine. This only happens with e.g. compiled 

268 # classes. 

269 return False 

270 else: 

271 if method(): 

272 return True 

273 return False 

274 

275 def py__getitem__(self, index_value_set, contextualized_node): 

276 from jedi.inference.gradual.base import GenericClass 

277 if not index_value_set: 

278 debug.warning('Class indexes inferred to nothing. Returning class instead') 

279 return ValueSet([self]) 

280 return ValueSet( 

281 GenericClass( 

282 self, 

283 LazyGenericManager( 

284 context_of_index=contextualized_node.context, 

285 index_value=index_value, 

286 ) 

287 ) 

288 for index_value in index_value_set 

289 ) 

290 

291 def with_generics(self, generics_tuple): 

292 from jedi.inference.gradual.base import GenericClass 

293 return GenericClass( 

294 self, 

295 TupleGenericManager(generics_tuple) 

296 ) 

297 

298 def define_generics(self, type_var_dict): 

299 from jedi.inference.gradual.base import GenericClass 

300 

301 def remap_type_vars(): 

302 """ 

303 The TypeVars in the resulting classes have sometimes different names 

304 and we need to check for that, e.g. a signature can be: 

305 

306 def iter(iterable: Iterable[_T]) -> Iterator[_T]: ... 

307 

308 However, the iterator is defined as Iterator[_T_co], which means it has 

309 a different type var name. 

310 """ 

311 for type_var in self.list_type_vars(): 

312 yield type_var_dict.get(type_var.py__name__(), NO_VALUES) 

313 

314 if type_var_dict: 

315 return ValueSet([GenericClass( 

316 self, 

317 TupleGenericManager(tuple(remap_type_vars())) 

318 )]) 

319 return ValueSet({self}) 

320 

321 

322class ClassValue(ClassMixin, FunctionAndClassBase, metaclass=CachedMetaClass): 

323 api_type = 'class' 

324 

325 @inference_state_method_cache() 

326 def list_type_vars(self): 

327 found = [] 

328 arglist = self.tree_node.get_super_arglist() 

329 if arglist is None: 

330 return [] 

331 

332 for stars, node in unpack_arglist(arglist): 

333 if stars: 

334 continue # These are not relevant for this search. 

335 

336 from jedi.inference.gradual.annotation import find_unknown_type_vars 

337 for type_var in find_unknown_type_vars(self.parent_context, node): 

338 if type_var not in found: 

339 # The order matters and it's therefore a list. 

340 found.append(type_var) 

341 return found 

342 

343 def _get_bases_arguments(self): 

344 arglist = self.tree_node.get_super_arglist() 

345 if arglist: 

346 from jedi.inference import arguments 

347 return arguments.TreeArguments(self.inference_state, self.parent_context, arglist) 

348 return None 

349 

350 @inference_state_method_cache(default=()) 

351 def py__bases__(self): 

352 args = self._get_bases_arguments() 

353 if args is not None: 

354 lst = [value for key, value in args.unpack() if key is None] 

355 if lst: 

356 return lst 

357 

358 if self.py__name__() == 'object' \ 

359 and self.parent_context.is_builtins_module(): 

360 return [] 

361 return [LazyKnownValues( 

362 self.inference_state.builtins_module.py__getattribute__('object') 

363 )] 

364 

365 @plugin_manager.decorate() 

366 def get_metaclass_filters(self, metaclasses, is_instance): 

367 debug.warning('Unprocessed metaclass %s', metaclasses) 

368 return [] 

369 

370 @inference_state_method_cache(default=NO_VALUES) 

371 def get_metaclasses(self): 

372 args = self._get_bases_arguments() 

373 if args is not None: 

374 m = [value for key, value in args.unpack() if key == 'metaclass'] 

375 metaclasses = ValueSet.from_sets(lazy_value.infer() for lazy_value in m) 

376 metaclasses = ValueSet(m for m in metaclasses if m.is_class()) 

377 if metaclasses: 

378 return metaclasses 

379 

380 for lazy_base in self.py__bases__(): 

381 for value in lazy_base.infer(): 

382 if value.is_class(): 

383 values = value.get_metaclasses() 

384 if values: 

385 return values 

386 return NO_VALUES 

387 

388 @plugin_manager.decorate() 

389 def get_metaclass_signatures(self, metaclasses): 

390 return []