protocols.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
  2. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
  3. # copyright 2003-2010 Sylvain Thenault, all rights reserved.
  4. # contact mailto:thenault@gmail.com
  5. #
  6. # This file is part of logilab-astng.
  7. #
  8. # logilab-astng is free software: you can redistribute it and/or modify it
  9. # under the terms of the GNU Lesser General Public License as published by the
  10. # Free Software Foundation, either version 2.1 of the License, or (at your
  11. # option) any later version.
  12. #
  13. # logilab-astng is distributed in the hope that it will be useful, but
  14. # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15. # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
  16. # for more details.
  17. #
  18. # You should have received a copy of the GNU Lesser General Public License along
  19. # with logilab-astng. If not, see <http://www.gnu.org/licenses/>.
  20. """this module contains a set of functions to handle python protocols for nodes
  21. where it makes sense.
  22. """
  23. __doctype__ = "restructuredtext en"
  24. from logilab.astng.exceptions import InferenceError, NoDefault
  25. from logilab.astng.node_classes import unpack_infer
  26. from logilab.astng.bases import copy_context, \
  27. raise_if_nothing_infered, yes_if_nothing_infered, Instance, Generator, YES
  28. from logilab.astng.nodes import const_factory
  29. from logilab.astng import nodes
  30. # unary operations ############################################################
  31. def tl_infer_unary_op(self, operator):
  32. if operator == 'not':
  33. return const_factory(not bool(self.elts))
  34. raise TypeError() # XXX log unsupported operation
  35. nodes.Tuple.infer_unary_op = tl_infer_unary_op
  36. nodes.List.infer_unary_op = tl_infer_unary_op
  37. def dict_infer_unary_op(self, operator):
  38. if operator == 'not':
  39. return const_factory(not bool(self.items))
  40. raise TypeError() # XXX log unsupported operation
  41. nodes.Dict.infer_unary_op = dict_infer_unary_op
  42. def const_infer_unary_op(self, operator):
  43. if operator == 'not':
  44. return const_factory(not self.value)
  45. # XXX log potentially raised TypeError
  46. elif operator == '+':
  47. return const_factory(+self.value)
  48. else: # operator == '-':
  49. return const_factory(-self.value)
  50. nodes.Const.infer_unary_op = const_infer_unary_op
  51. # binary operations ###########################################################
  52. BIN_OP_IMPL = {'+': lambda a, b: a + b,
  53. '-': lambda a, b: a - b,
  54. '/': lambda a, b: a / b,
  55. '//': lambda a, b: a // b,
  56. '*': lambda a, b: a * b,
  57. '**': lambda a, b: a ** b,
  58. '%': lambda a, b: a % b,
  59. '&': lambda a, b: a & b,
  60. '|': lambda a, b: a | b,
  61. '^': lambda a, b: a ^ b,
  62. '<<': lambda a, b: a << b,
  63. '>>': lambda a, b: a >> b,
  64. }
  65. for key, impl in BIN_OP_IMPL.items():
  66. BIN_OP_IMPL[key+'='] = impl
  67. def const_infer_binary_op(self, operator, other, context):
  68. for other in other.infer(context):
  69. if isinstance(other, nodes.Const):
  70. try:
  71. impl = BIN_OP_IMPL[operator]
  72. try:
  73. yield const_factory(impl(self.value, other.value))
  74. except Exception:
  75. # ArithmeticError is not enough: float >> float is a TypeError
  76. # TODO : let pylint know about the problem
  77. pass
  78. except TypeError:
  79. # XXX log TypeError
  80. continue
  81. elif other is YES:
  82. yield other
  83. else:
  84. try:
  85. for val in other.infer_binary_op(operator, self, context):
  86. yield val
  87. except AttributeError:
  88. yield YES
  89. nodes.Const.infer_binary_op = yes_if_nothing_infered(const_infer_binary_op)
  90. def tl_infer_binary_op(self, operator, other, context):
  91. for other in other.infer(context):
  92. if isinstance(other, self.__class__) and operator == '+':
  93. node = self.__class__()
  94. elts = [n for elt in self.elts for n in elt.infer(context)
  95. if not n is YES]
  96. elts += [n for elt in other.elts for n in elt.infer(context)
  97. if not n is YES]
  98. node.elts = elts
  99. yield node
  100. elif isinstance(other, nodes.Const) and operator == '*':
  101. if not isinstance(other.value, int):
  102. yield YES
  103. continue
  104. node = self.__class__()
  105. elts = [n for elt in self.elts for n in elt.infer(context)
  106. if not n is YES] * other.value
  107. node.elts = elts
  108. yield node
  109. elif isinstance(other, Instance) and not isinstance(other, nodes.Const):
  110. yield YES
  111. # XXX else log TypeError
  112. nodes.Tuple.infer_binary_op = yes_if_nothing_infered(tl_infer_binary_op)
  113. nodes.List.infer_binary_op = yes_if_nothing_infered(tl_infer_binary_op)
  114. def dict_infer_binary_op(self, operator, other, context):
  115. for other in other.infer(context):
  116. if isinstance(other, Instance) and isinstance(other._proxied, nodes.Class):
  117. yield YES
  118. # XXX else log TypeError
  119. nodes.Dict.infer_binary_op = yes_if_nothing_infered(dict_infer_binary_op)
  120. # assignment ##################################################################
  121. """the assigned_stmts method is responsible to return the assigned statement
  122. (e.g. not inferred) according to the assignment type.
  123. The `asspath` argument is used to record the lhs path of the original node.
  124. For instance if we want assigned statements for 'c' in 'a, (b,c)', asspath
  125. will be [1, 1] once arrived to the Assign node.
  126. The `context` argument is the current inference context which should be given
  127. to any intermediary inference necessary.
  128. """
  129. def _resolve_looppart(parts, asspath, context):
  130. """recursive function to resolve multiple assignments on loops"""
  131. asspath = asspath[:]
  132. index = asspath.pop(0)
  133. for part in parts:
  134. if part is YES:
  135. continue
  136. # XXX handle __iter__ and log potentially detected errors
  137. if not hasattr(part, 'itered'):
  138. continue
  139. try:
  140. itered = part.itered()
  141. except TypeError:
  142. continue # XXX log error
  143. for stmt in itered:
  144. try:
  145. assigned = stmt.getitem(index, context)
  146. except (AttributeError, IndexError):
  147. continue
  148. except TypeError, exc: # stmt is unsubscriptable Const
  149. continue
  150. if not asspath:
  151. # we achieved to resolved the assignment path,
  152. # don't infer the last part
  153. yield assigned
  154. elif assigned is YES:
  155. break
  156. else:
  157. # we are not yet on the last part of the path
  158. # search on each possibly inferred value
  159. try:
  160. for infered in _resolve_looppart(assigned.infer(context),
  161. asspath, context):
  162. yield infered
  163. except InferenceError:
  164. break
  165. def for_assigned_stmts(self, node, context=None, asspath=None):
  166. if asspath is None:
  167. for lst in self.iter.infer(context):
  168. if isinstance(lst, (nodes.Tuple, nodes.List)):
  169. for item in lst.elts:
  170. yield item
  171. else:
  172. for infered in _resolve_looppart(self.iter.infer(context),
  173. asspath, context):
  174. yield infered
  175. nodes.For.assigned_stmts = raise_if_nothing_infered(for_assigned_stmts)
  176. nodes.Comprehension.assigned_stmts = raise_if_nothing_infered(for_assigned_stmts)
  177. def mulass_assigned_stmts(self, node, context=None, asspath=None):
  178. if asspath is None:
  179. asspath = []
  180. asspath.insert(0, self.elts.index(node))
  181. return self.parent.assigned_stmts(self, context, asspath)
  182. nodes.Tuple.assigned_stmts = mulass_assigned_stmts
  183. nodes.List.assigned_stmts = mulass_assigned_stmts
  184. def assend_assigned_stmts(self, context=None):
  185. return self.parent.assigned_stmts(self, context=context)
  186. nodes.AssName.assigned_stmts = assend_assigned_stmts
  187. nodes.AssAttr.assigned_stmts = assend_assigned_stmts
  188. def _arguments_infer_argname(self, name, context):
  189. # arguments information may be missing, in which case we can't do anything
  190. # more
  191. if not (self.args or self.vararg or self.kwarg):
  192. yield YES
  193. return
  194. # first argument of instance/class method
  195. if self.args and getattr(self.args[0], 'name', None) == name:
  196. functype = self.parent.type
  197. if functype == 'method':
  198. yield Instance(self.parent.parent.frame())
  199. return
  200. if functype == 'classmethod':
  201. yield self.parent.parent.frame()
  202. return
  203. if name == self.vararg:
  204. yield const_factory(())
  205. return
  206. if name == self.kwarg:
  207. yield const_factory({})
  208. return
  209. # if there is a default value, yield it. And then yield YES to reflect
  210. # we can't guess given argument value
  211. try:
  212. context = copy_context(context)
  213. for infered in self.default_value(name).infer(context):
  214. yield infered
  215. yield YES
  216. except NoDefault:
  217. yield YES
  218. def arguments_assigned_stmts(self, node, context, asspath=None):
  219. if context.callcontext:
  220. # reset call context/name
  221. callcontext = context.callcontext
  222. context = copy_context(context)
  223. context.callcontext = None
  224. for infered in callcontext.infer_argument(self.parent, node.name, context):
  225. yield infered
  226. return
  227. for infered in _arguments_infer_argname(self, node.name, context):
  228. yield infered
  229. nodes.Arguments.assigned_stmts = arguments_assigned_stmts
  230. def assign_assigned_stmts(self, node, context=None, asspath=None):
  231. if not asspath:
  232. yield self.value
  233. return
  234. for infered in _resolve_asspart(self.value.infer(context), asspath, context):
  235. yield infered
  236. nodes.Assign.assigned_stmts = raise_if_nothing_infered(assign_assigned_stmts)
  237. nodes.AugAssign.assigned_stmts = raise_if_nothing_infered(assign_assigned_stmts)
  238. def _resolve_asspart(parts, asspath, context):
  239. """recursive function to resolve multiple assignments"""
  240. asspath = asspath[:]
  241. index = asspath.pop(0)
  242. for part in parts:
  243. if hasattr(part, 'getitem'):
  244. try:
  245. assigned = part.getitem(index, context)
  246. # XXX raise a specific exception to avoid potential hiding of
  247. # unexpected exception ?
  248. except (TypeError, IndexError):
  249. return
  250. if not asspath:
  251. # we achieved to resolved the assignment path, don't infer the
  252. # last part
  253. yield assigned
  254. elif assigned is YES:
  255. return
  256. else:
  257. # we are not yet on the last part of the path search on each
  258. # possibly inferred value
  259. try:
  260. for infered in _resolve_asspart(assigned.infer(context),
  261. asspath, context):
  262. yield infered
  263. except InferenceError:
  264. return
  265. def excepthandler_assigned_stmts(self, node, context=None, asspath=None):
  266. for assigned in unpack_infer(self.type):
  267. if isinstance(assigned, nodes.Class):
  268. assigned = Instance(assigned)
  269. yield assigned
  270. nodes.ExceptHandler.assigned_stmts = raise_if_nothing_infered(excepthandler_assigned_stmts)
  271. def with_assigned_stmts(self, node, context=None, asspath=None):
  272. if asspath is None:
  273. for lst in self.vars.infer(context):
  274. if isinstance(lst, (nodes.Tuple, nodes.List)):
  275. for item in lst.nodes:
  276. yield item
  277. nodes.With.assigned_stmts = raise_if_nothing_infered(with_assigned_stmts)