diff options
| author | Eevee (Alex Munroe) <amunroe@yelp.com> | 2014-03-24 18:00:17 -0700 |
|---|---|---|
| committer | Eevee (Alex Munroe) <amunroe@yelp.com> | 2014-07-01 17:33:00 -0700 |
| commit | 53013fa01f45114c1b8ee87527eab4fbeef8a113 (patch) | |
| tree | d4ea5d9e01a4e746ea48037443a62bdb40d250da | |
| parent | 2e13d989dbf9a097c679e4e1ca71e59fc9e5524f (diff) | |
| download | astroid-git-53013fa01f45114c1b8ee87527eab4fbeef8a113.tar.gz | |
Speed up rebuilder considerably by computing line numbers lazily.
Fetching the last child of each of hundreds of thousands of nodes is
relatively expensive, and most of the time this information is never
used, so don't actually figure it out until it's asked for. Saves the
overhead of quite a few function calls, too.
| -rw-r--r-- | bases.py | 28 | ||||
| -rw-r--r-- | mixins.py | 10 | ||||
| -rw-r--r-- | node_classes.py | 33 | ||||
| -rw-r--r-- | rebuilder.py | 74 | ||||
| -rw-r--r-- | scoped_nodes.py | 32 |
5 files changed, 71 insertions, 106 deletions
@@ -24,6 +24,8 @@ __docformat__ = "restructuredtext en" import sys from contextlib import contextmanager +from logilab.common.decorators import cachedproperty + from astroid.exceptions import (InferenceError, AstroidError, NotFoundError, UnresolvableName, UseInferenceDefault) @@ -441,7 +443,7 @@ class NodeNG(object): attr = getattr(self, field) if not attr: # None or empty listy / tuple continue - if isinstance(attr, (list, tuple)): + if attr.__class__ in (list, tuple): return attr[-1] else: return attr @@ -532,16 +534,28 @@ class NodeNG(object): # FIXME: raise an exception if nearest is None ? return nearest[0] - def set_line_info(self, lastchild): + # these are lazy because they're relatively expensive to compute for every + # single node, and they rarely get looked at + + @cachedproperty + def fromlineno(self): if self.lineno is None: - self.fromlineno = self._fixed_source_line() + return self._fixed_source_line() else: - self.fromlineno = self.lineno + return self.lineno + + @cachedproperty + def tolineno(self): + if not self._astroid_fields: + # can't have children + lastchild = None + else: + lastchild = self.last_child() if lastchild is None: - self.tolineno = self.fromlineno + return self.fromlineno else: - self.tolineno = lastchild.tolineno - return + return lastchild.tolineno + # TODO / FIXME: assert self.fromlineno is not None, self assert self.tolineno is not None, self @@ -18,16 +18,18 @@ """This module contains some mixins for the different nodes. """ +from logilab.common.decorators import cachedproperty + from astroid.exceptions import (AstroidBuildingException, InferenceError, NotFoundError) class BlockRangeMixIn(object): """override block range """ - def set_line_info(self, lastchild): - self.fromlineno = self.lineno - self.tolineno = lastchild.tolineno - self.blockstart_tolineno = self._blockstart_toline() + + @cachedproperty + def blockstart_tolineno(self): + return self.lineno def _elsed_block_range(self, lineno, orelse, last=None): """handle block line numbers range for try/finally, for, if and while diff --git a/node_classes.py b/node_classes.py index e247392e..45a00ba9 100644 --- a/node_classes.py +++ b/node_classes.py @@ -20,6 +20,8 @@ import sys +from logilab.common.decorators import cachedproperty + from astroid.exceptions import NoDefault from astroid.bases import (NodeNG, Statement, Instance, InferenceContext, _infer_stmts, YES, BUILTINS) @@ -267,6 +269,11 @@ class Arguments(NodeNG, AssignTypeMixin): return name return None + @cachedproperty + def fromlineno(self): + lineno = super(Arguments, self).fromlineno + return max(lineno, self.parent.fromlineno) + def format_args(self): """return arguments formatted as string""" result = [] @@ -560,7 +567,8 @@ class ExceptHandler(Statement, AssignTypeMixin): name = None body = None - def _blockstart_toline(self): + @cachedproperty + def blockstart_tolineno(self): if self.name: return self.name.tolineno elif self.type: @@ -568,11 +576,6 @@ class ExceptHandler(Statement, AssignTypeMixin): else: return self.lineno - def set_line_info(self, lastchild): - self.fromlineno = self.lineno - self.tolineno = lastchild.tolineno - self.blockstart_tolineno = self._blockstart_toline() - def catch(self, exceptions): if self.type is None or exceptions is None: return True @@ -603,7 +606,8 @@ class For(BlockRangeMixIn, AssignTypeMixin, Statement): orelse = None optional_assign = True - def _blockstart_toline(self): + @cachedproperty + def blockstart_tolineno(self): return self.iter.tolineno @@ -638,7 +642,8 @@ class If(BlockRangeMixIn, Statement): body = None orelse = None - def _blockstart_toline(self): + @cachedproperty + def blockstart_tolineno(self): return self.test.tolineno def block_range(self, lineno): @@ -789,9 +794,6 @@ class TryExcept(BlockRangeMixIn, Statement): def _infer_name(self, frame, name): return name - def _blockstart_toline(self): - return self.lineno - def block_range(self, lineno): """handle block line numbers range for try/except statements""" last = None @@ -811,9 +813,6 @@ class TryFinally(BlockRangeMixIn, Statement): body = None finalbody = None - def _blockstart_toline(self): - return self.lineno - def block_range(self, lineno): """handle block line numbers range for try/finally statements""" child = self.body[0] @@ -857,7 +856,8 @@ class While(BlockRangeMixIn, Statement): body = None orelse = None - def _blockstart_toline(self): + @cachedproperty + def blockstart_tolineno(self): return self.test.tolineno def block_range(self, lineno): @@ -871,7 +871,8 @@ class With(BlockRangeMixIn, AssignTypeMixin, Statement): items = None body = None - def _blockstart_toline(self): + @cachedproperty + def blockstart_tolineno(self): return self.items[-1][0].tolineno def get_children(self): diff --git a/rebuilder.py b/rebuilder.py index 47eff50e..cef00f8c 100644 --- a/rebuilder.py +++ b/rebuilder.py @@ -103,10 +103,8 @@ def _init_set_doc(node, newnode): def _lineno_parent(oldnode, newnode, parent): newnode.parent = parent - if hasattr(oldnode, 'lineno'): - newnode.lineno = oldnode.lineno - if hasattr(oldnode, 'col_offset'): - newnode.col_offset = oldnode.col_offset + newnode.lineno = oldnode.lineno + newnode.col_offset = oldnode.col_offset def _set_infos(oldnode, newnode, parent): newnode.parent = parent @@ -114,14 +112,12 @@ def _set_infos(oldnode, newnode, parent): newnode.lineno = oldnode.lineno if hasattr(oldnode, 'col_offset'): newnode.col_offset = oldnode.col_offset - newnode.set_line_info(newnode.last_child()) # set_line_info accepts None def _create_yield_node(node, parent, rebuilder, factory): newnode = factory() _lineno_parent(node, newnode, parent) if node.value is not None: newnode.value = rebuilder.visit(node.value, newnode) - newnode.set_line_info(newnode.last_child()) return newnode @@ -141,10 +137,9 @@ class TreeRebuilder(object): """visit a Module node by returning a fresh instance of it""" newnode = new.Module(modname, None) newnode.package = package - _lineno_parent(node, newnode, parent=None) + newnode.parent = None _init_set_doc(node, newnode) newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.set_line_info(newnode.last_child()) return self._transform(newnode) def visit(self, node, parent): @@ -169,7 +164,7 @@ class TreeRebuilder(object): def visit_arguments(self, node, parent): """visit a Arguments node by returning a fresh instance of it""" newnode = new.Arguments() - _lineno_parent(node, newnode, parent) + newnode.parent = parent self.asscontext = "Ass" newnode.args = [self.visit(child, newnode) for child in node.args] self.asscontext = None @@ -190,7 +185,6 @@ class TreeRebuilder(object): newnode.parent.set_local(vararg, newnode) if kwarg: newnode.parent.set_local(kwarg, newnode) - newnode.set_line_info(newnode.last_child()) return newnode def visit_assattr(self, node, parent): @@ -201,7 +195,6 @@ class TreeRebuilder(object): newnode.expr = self.visit(node.expr, newnode) self.asscontext = assc self._delayed_assattr.append(newnode) - newnode.set_line_info(newnode.last_child()) return newnode def visit_assert(self, node, parent): @@ -211,7 +204,6 @@ class TreeRebuilder(object): newnode.test = self.visit(node.test, newnode) if node.msg is not None: newnode.fail = self.visit(node.msg, newnode) - newnode.set_line_info(newnode.last_child()) return newnode def visit_assign(self, node, parent): @@ -239,7 +231,6 @@ class TreeRebuilder(object): meth.extra_decorators.append(newnode.value) except (AttributeError, KeyError): continue - newnode.set_line_info(newnode.last_child()) return newnode def visit_assname(self, node, parent, node_name=None): @@ -259,7 +250,6 @@ class TreeRebuilder(object): newnode.target = self.visit(node.target, newnode) self.asscontext = None newnode.value = self.visit(node.value, newnode) - newnode.set_line_info(newnode.last_child()) return newnode def visit_backquote(self, node, parent): @@ -267,7 +257,6 @@ class TreeRebuilder(object): newnode = new.Backquote() _lineno_parent(node, newnode, parent) newnode.value = self.visit(node.value, newnode) - newnode.set_line_info(newnode.last_child()) return newnode def visit_binop(self, node, parent): @@ -277,7 +266,6 @@ class TreeRebuilder(object): newnode.left = self.visit(node.left, newnode) newnode.right = self.visit(node.right, newnode) newnode.op = _BIN_OP_CLASSES[node.op.__class__] - newnode.set_line_info(newnode.last_child()) return newnode def visit_boolop(self, node, parent): @@ -286,7 +274,6 @@ class TreeRebuilder(object): _lineno_parent(node, newnode, parent) newnode.values = [self.visit(child, newnode) for child in node.values] newnode.op = _BOOL_OP_CLASSES[node.op.__class__] - newnode.set_line_info(newnode.last_child()) return newnode def visit_break(self, node, parent): @@ -306,7 +293,6 @@ class TreeRebuilder(object): if node.kwargs is not None: newnode.kwargs = self.visit(node.kwargs, newnode) newnode.args.extend(self.visit(child, newnode) for child in node.keywords) - newnode.set_line_info(newnode.last_child()) return newnode def visit_class(self, node, parent): @@ -318,7 +304,6 @@ class TreeRebuilder(object): newnode.body = [self.visit(child, newnode) for child in node.body] if 'decorator_list' in node._fields and node.decorator_list:# py >= 2.6 newnode.decorators = self.visit_decorators(node, newnode) - newnode.set_line_info(newnode.last_child()) newnode.parent.frame().set_local(newnode.name, newnode) return newnode @@ -341,19 +326,17 @@ class TreeRebuilder(object): newnode.left = self.visit(node.left, newnode) newnode.ops = [(_CMP_OP_CLASSES[op.__class__], self.visit(expr, newnode)) for (op, expr) in zip(node.ops, node.comparators)] - newnode.set_line_info(newnode.last_child()) return newnode def visit_comprehension(self, node, parent): """visit a Comprehension node by returning a fresh instance of it""" newnode = new.Comprehension() - _lineno_parent(node, newnode, parent) + newnode.parent = parent self.asscontext = "Ass" newnode.target = self.visit(node.target, newnode) self.asscontext = None newnode.iter = self.visit(node.iter, newnode) newnode.ifs = [self.visit(child, newnode) for child in node.ifs] - newnode.set_line_info(newnode.last_child()) return newnode def visit_decorators(self, node, parent): @@ -367,7 +350,6 @@ class TreeRebuilder(object): else: decorators= node.decorator_list newnode.nodes = [self.visit(child, newnode) for child in decorators] - newnode.set_line_info(newnode.last_child()) return newnode def visit_delete(self, node, parent): @@ -377,7 +359,6 @@ class TreeRebuilder(object): self.asscontext = "Del" newnode.targets = [self.visit(child, newnode) for child in node.targets] self.asscontext = None - newnode.set_line_info(newnode.last_child()) return newnode def visit_dict(self, node, parent): @@ -386,7 +367,6 @@ class TreeRebuilder(object): _lineno_parent(node, newnode, parent) newnode.items = [(self.visit(key, newnode), self.visit(value, newnode)) for key, value in zip(node.keys, node.values)] - newnode.set_line_info(newnode.last_child()) return newnode def visit_dictcomp(self, node, parent): @@ -397,7 +377,6 @@ class TreeRebuilder(object): newnode.value = self.visit(node.value, newnode) newnode.generators = [self.visit(child, newnode) for child in node.generators] - newnode.set_line_info(newnode.last_child()) return newnode def visit_discard(self, node, parent): @@ -405,7 +384,6 @@ class TreeRebuilder(object): newnode = new.Discard() _lineno_parent(node, newnode, parent) newnode.value = self.visit(node.value, newnode) - newnode.set_line_info(newnode.last_child()) return newnode def visit_ellipsis(self, node, parent): @@ -432,7 +410,6 @@ class TreeRebuilder(object): newnode.name = self.visit(node.name, newnode) self.asscontext = None newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.set_line_info(newnode.last_child()) return newnode def visit_exec(self, node, parent): @@ -444,15 +421,13 @@ class TreeRebuilder(object): newnode.globals = self.visit(node.globals, newnode) if node.locals is not None: newnode.locals = self.visit(node.locals, newnode) - newnode.set_line_info(newnode.last_child()) return newnode def visit_extslice(self, node, parent): """visit an ExtSlice node by returning a fresh instance of it""" newnode = new.ExtSlice() - _lineno_parent(node, newnode, parent) + newnode.parent = parent newnode.dims = [self.visit(dim, newnode) for dim in node.dims] - newnode.set_line_info(newnode.last_child()) return newnode def visit_for(self, node, parent): @@ -465,7 +440,6 @@ class TreeRebuilder(object): newnode.iter = self.visit(node.iter, newnode) newnode.body = [self.visit(child, newnode) for child in node.body] newnode.orelse = [self.visit(child, newnode) for child in node.orelse] - newnode.set_line_info(newnode.last_child()) return newnode def visit_from(self, node, parent): @@ -492,7 +466,6 @@ class TreeRebuilder(object): decorators = getattr(node, attr) if decorators: newnode.decorators = self.visit_decorators(node, newnode) - newnode.set_line_info(newnode.last_child()) self._global_names.pop() frame = newnode.parent.frame() if isinstance(frame, new.Class): @@ -516,7 +489,6 @@ class TreeRebuilder(object): _lineno_parent(node, newnode, parent) newnode.elt = self.visit(node.elt, newnode) newnode.generators = [self.visit(child, newnode) for child in node.generators] - newnode.set_line_info(newnode.last_child()) return newnode def visit_getattr(self, node, parent): @@ -536,7 +508,6 @@ class TreeRebuilder(object): newnode.expr = self.visit(node.value, newnode) self.asscontext = asscontext newnode.attrname = node.attr - newnode.set_line_info(newnode.last_child()) return newnode def visit_global(self, node, parent): @@ -555,7 +526,6 @@ class TreeRebuilder(object): newnode.test = self.visit(node.test, newnode) newnode.body = [self.visit(child, newnode) for child in node.body] newnode.orelse = [self.visit(child, newnode) for child in node.orelse] - newnode.set_line_info(newnode.last_child()) return newnode def visit_ifexp(self, node, parent): @@ -565,7 +535,6 @@ class TreeRebuilder(object): newnode.test = self.visit(node.test, newnode) newnode.body = self.visit(node.body, newnode) newnode.orelse = self.visit(node.orelse, newnode) - newnode.set_line_info(newnode.last_child()) return newnode def visit_import(self, node, parent): @@ -582,18 +551,16 @@ class TreeRebuilder(object): def visit_index(self, node, parent): """visit a Index node by returning a fresh instance of it""" newnode = new.Index() - _lineno_parent(node, newnode, parent) + newnode.parent = parent newnode.value = self.visit(node.value, newnode) - newnode.set_line_info(newnode.last_child()) return newnode def visit_keyword(self, node, parent): """visit a Keyword node by returning a fresh instance of it""" newnode = new.Keyword() - _lineno_parent(node, newnode, parent) + newnode.parent = parent newnode.arg = node.arg newnode.value = self.visit(node.value, newnode) - newnode.set_line_info(newnode.last_child()) return newnode def visit_lambda(self, node, parent): @@ -602,7 +569,6 @@ class TreeRebuilder(object): _lineno_parent(node, newnode, parent) newnode.args = self.visit(node.args, newnode) newnode.body = self.visit(node.body, newnode) - newnode.set_line_info(newnode.last_child()) return newnode def visit_list(self, node, parent): @@ -610,7 +576,6 @@ class TreeRebuilder(object): newnode = new.List() _lineno_parent(node, newnode, parent) newnode.elts = [self.visit(child, newnode) for child in node.elts] - newnode.set_line_info(newnode.last_child()) return newnode def visit_listcomp(self, node, parent): @@ -620,7 +585,6 @@ class TreeRebuilder(object): newnode.elt = self.visit(node.elt, newnode) newnode.generators = [self.visit(child, newnode) for child in node.generators] - newnode.set_line_info(newnode.last_child()) return newnode def visit_name(self, node, parent): @@ -643,7 +607,6 @@ class TreeRebuilder(object): # XXX REMOVE me : if self.asscontext in ('Del', 'Ass'): # 'Aug' ?? self._save_assignment(newnode) - newnode.set_line_info(newnode.last_child()) return newnode def visit_bytes(self, node, parent): @@ -678,7 +641,6 @@ class TreeRebuilder(object): if node.dest is not None: newnode.dest = self.visit(node.dest, newnode) newnode.values = [self.visit(child, newnode) for child in node.values] - newnode.set_line_info(newnode.last_child()) return newnode def visit_raise(self, node, parent): @@ -691,7 +653,6 @@ class TreeRebuilder(object): newnode.inst = self.visit(node.inst, newnode) if node.tback is not None: newnode.tback = self.visit(node.tback, newnode) - newnode.set_line_info(newnode.last_child()) return newnode def visit_return(self, node, parent): @@ -700,7 +661,6 @@ class TreeRebuilder(object): _lineno_parent(node, newnode, parent) if node.value is not None: newnode.value = self.visit(node.value, newnode) - newnode.set_line_info(newnode.last_child()) return newnode def visit_set(self, node, parent): @@ -708,7 +668,6 @@ class TreeRebuilder(object): newnode = new.Set() _lineno_parent(node, newnode, parent) newnode.elts = [self.visit(child, newnode) for child in node.elts] - newnode.set_line_info(newnode.last_child()) return newnode def visit_setcomp(self, node, parent): @@ -718,20 +677,18 @@ class TreeRebuilder(object): newnode.elt = self.visit(node.elt, newnode) newnode.generators = [self.visit(child, newnode) for child in node.generators] - newnode.set_line_info(newnode.last_child()) return newnode def visit_slice(self, node, parent): """visit a Slice node by returning a fresh instance of it""" newnode = new.Slice() - _lineno_parent(node, newnode, parent) + newnode.parent = parent if node.lower is not None: newnode.lower = self.visit(node.lower, newnode) if node.upper is not None: newnode.upper = self.visit(node.upper, newnode) if node.step is not None: newnode.step = self.visit(node.step, newnode) - newnode.set_line_info(newnode.last_child()) return newnode def visit_subscript(self, node, parent): @@ -742,7 +699,6 @@ class TreeRebuilder(object): newnode.value = self.visit(node.value, newnode) newnode.slice = self.visit(node.slice, newnode) self.asscontext = subcontext - newnode.set_line_info(newnode.last_child()) return newnode def visit_tryexcept(self, node, parent): @@ -752,7 +708,6 @@ class TreeRebuilder(object): newnode.body = [self.visit(child, newnode) for child in node.body] newnode.handlers = [self.visit(child, newnode) for child in node.handlers] newnode.orelse = [self.visit(child, newnode) for child in node.orelse] - newnode.set_line_info(newnode.last_child()) return newnode def visit_tryfinally(self, node, parent): @@ -761,7 +716,6 @@ class TreeRebuilder(object): _lineno_parent(node, newnode, parent) newnode.body = [self.visit(child, newnode) for child in node.body] newnode.finalbody = [self.visit(n, newnode) for n in node.finalbody] - newnode.set_line_info(newnode.last_child()) return newnode def visit_tuple(self, node, parent): @@ -769,7 +723,6 @@ class TreeRebuilder(object): newnode = new.Tuple() _lineno_parent(node, newnode, parent) newnode.elts = [self.visit(child, newnode) for child in node.elts] - newnode.set_line_info(newnode.last_child()) return newnode def visit_unaryop(self, node, parent): @@ -778,7 +731,6 @@ class TreeRebuilder(object): _lineno_parent(node, newnode, parent) newnode.operand = self.visit(node.operand, newnode) newnode.op = _UNARY_OP_CLASSES[node.op.__class__] - newnode.set_line_info(newnode.last_child()) return newnode def visit_while(self, node, parent): @@ -788,7 +740,6 @@ class TreeRebuilder(object): newnode.test = self.visit(node.test, newnode) newnode.body = [self.visit(child, newnode) for child in node.body] newnode.orelse = [self.visit(child, newnode) for child in node.orelse] - newnode.set_line_info(newnode.last_child()) return newnode def visit_with(self, node, parent): @@ -803,7 +754,6 @@ class TreeRebuilder(object): self.asscontext = None newnode.items = [(expr, vars)] newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.set_line_info(newnode.last_child()) return newnode def visit_yield(self, node, parent): @@ -842,7 +792,6 @@ class TreeRebuilder3k(TreeRebuilder): if node.name is not None: newnode.name = self.visit_assname(node, newnode, node.name) newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.set_line_info(newnode.last_child()) return newnode def visit_nonlocal(self, node, parent): @@ -860,7 +809,6 @@ class TreeRebuilder3k(TreeRebuilder): newnode.exc = self.visit(node.exc, newnode) if node.cause is not None: newnode.cause = self.visit(node.cause, newnode) - newnode.set_line_info(newnode.last_child()) return newnode def visit_starred(self, node, parent): @@ -868,7 +816,6 @@ class TreeRebuilder3k(TreeRebuilder): newnode = new.Starred() _lineno_parent(node, newnode, parent) newnode.value = self.visit(node.value, newnode) - newnode.set_line_info(newnode.last_child()) return newnode def visit_try(self, node, parent): @@ -883,7 +830,6 @@ class TreeRebuilder3k(TreeRebuilder): excnode.body = [self.visit(child, excnode) for child in node.body] excnode.handlers = [self.visit(child, excnode) for child in node.handlers] excnode.orelse = [self.visit(child, excnode) for child in node.orelse] - excnode.set_line_info(excnode.last_child()) newnode.body = [excnode] else: newnode.body = [self.visit(child, newnode) for child in node.body] @@ -893,7 +839,6 @@ class TreeRebuilder3k(TreeRebuilder): newnode.body = [self.visit(child, newnode) for child in node.body] newnode.handlers = [self.visit(child, newnode) for child in node.handlers] newnode.orelse = [self.visit(child, newnode) for child in node.orelse] - newnode.set_line_info(newnode.last_child()) return newnode def visit_with(self, node, parent): @@ -915,7 +860,6 @@ class TreeRebuilder3k(TreeRebuilder): newnode.items = [visit_child(child) for child in node.items] newnode.body = [self.visit(child, newnode) for child in node.body] - newnode.set_line_info(newnode.last_child()) return newnode def visit_yieldfrom(self, node, parent): diff --git a/scoped_nodes.py b/scoped_nodes.py index 389ebe71..f5d1c428 100644 --- a/scoped_nodes.py +++ b/scoped_nodes.py @@ -580,16 +580,20 @@ class Function(Statement, Lambda): self.extra_decorators = [] self.instance_attrs = {} - def set_line_info(self, lastchild): - self.fromlineno = self.lineno - # lineno is the line number of the first decorator, we want the def statement lineno + @cachedproperty + def fromlineno(self): + # lineno is the line number of the first decorator, we want the def + # statement lineno + lineno = self.lineno if self.decorators is not None: - self.fromlineno += sum(node.tolineno - node.lineno + 1 + lineno += sum(node.tolineno - node.lineno + 1 for node in self.decorators.nodes) - if self.args.fromlineno < self.fromlineno: - self.args.fromlineno = self.fromlineno - self.tolineno = lastchild.tolineno - self.blockstart_tolineno = self.args.tolineno + + return lineno + + @cachedproperty + def blockstart_tolineno(self): + return self.args.tolineno def block_range(self, lineno): """return block line numbers. @@ -819,12 +823,12 @@ class Class(Statement, LocalsDictNodeNG, FilterStmtsMixin): doc="boolean indicating if it's a new style class" "or not") - def set_line_info(self, lastchild): - self.fromlineno = self.lineno - self.blockstart_tolineno = self.bases and self.bases[-1].tolineno or self.fromlineno - if lastchild is not None: - self.tolineno = lastchild.tolineno - # else this is a class with only a docstring, then tolineno is (should be) already ok + @cachedproperty + def blockstart_tolineno(self): + if self.bases: + return self.bases[-1].tolineno + else: + return self.fromlineno def block_range(self, lineno): """return block line numbers. |
