summaryrefslogtreecommitdiff
path: root/checkers/typecheck.py
diff options
context:
space:
mode:
Diffstat (limited to 'checkers/typecheck.py')
-rw-r--r--checkers/typecheck.py181
1 files changed, 181 insertions, 0 deletions
diff --git a/checkers/typecheck.py b/checkers/typecheck.py
new file mode 100644
index 000000000..e8c435510
--- /dev/null
+++ b/checkers/typecheck.py
@@ -0,0 +1,181 @@
+# Copyright (c) 2006 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+"""try to find more bugs in the code using astng inference capabilities
+"""
+
+__revision__ = "$Id: typecheck.py,v 1.12 2006-04-19 16:16:20 syt Exp $"
+
+from logilab import astng
+
+from pylint.interfaces import IASTNGChecker
+from pylint.checkers import BaseChecker
+
+MSGS = {
+ 'E1101': ('%s %r has no %r member',
+ 'Used when a class is accessed for an unexistant member.'),
+ 'E1102': ('%s is not callable',
+ 'Used when an object being called has been infered to a non \
+ callable object'),
+ 'E1111': ('Assigning to function call which doesn\'t return',
+ 'Used when an assigment is done on a function call but the \
+ infered function doesn\'t return anything.'),
+ 'W1111': ('Assigning to function call which only returns None',
+ 'Used when an assigment is done on a function call but the \
+ infered function returns nothing but None.'),
+ }
+
+
+class TypeChecker(BaseChecker):
+ """try to find bugs in the code using type inference
+ """
+
+ __implements__ = (IASTNGChecker,)
+
+ # configuration section name
+ name = 'typecheck'
+ # messages
+ msgs = MSGS
+ priority = -1
+ # configuration options
+ options = (
+ ('ignore-mixin-members',
+ {'default' : True, 'type' : 'yn', 'metavar': '<y_or_n>',
+ 'help' : 'Tells wether missing members accessed in mixin \
+class should be ignored. A mixin class is detected if its name ends with \
+"mixin" (case insensitive).'}
+ ),
+
+ ('zope',
+ {'default' : False, 'type' : 'yn', 'metavar': '<y_or_n>',
+ 'help' : 'When zope mode is activated, consider the \
+acquired-members option to ignore access to some undefined attributes.'}
+ ),
+ ('acquired-members',
+ {'default' : (
+ 'REQUEST', 'acl_users', 'aq_parent'),
+ 'type' : 'csv',
+ 'metavar' : '<members names>',
+ 'help' : 'List of members which are usually get through \
+zope\'s acquisition mecanism and so shouldn\'t trigger E0201 when accessed \
+(need zope=yes to be considered.'}
+ ),
+ )
+
+ def visit_getattr(self, node):
+ """check that the accessed attribute exists"""
+ # if we are running in zope mode, is it an acquired attribute ?
+ if self.config.zope and node.attrname in self.config.acquired_members:
+ return
+ try:
+ infered = list(node.expr.infer())
+ for owner in infered:
+ # skip yes object
+ if owner is astng.YES:
+ continue
+ # if there is ambiguity, skip None
+ if len(infered) > 1 and isinstance(owner, astng.Const) \
+ and owner.value is None:
+ continue
+ # XXX "super" call
+ owner_name = getattr(owner, 'name', 'None')
+ if owner_name == 'super' and \
+ owner.root().name == '__builtin__':
+ continue
+ if getattr(owner, 'type', None) == 'metaclass':
+ continue
+ if self.config.ignore_mixin_members \
+ and owner_name[-5:].lower() == 'mixin':
+ continue
+ #print owner.name, owner.root().name
+ try:
+ owner.getattr(node.attrname)
+ except AttributeError:
+ # XXX method / function
+ continue
+ except astng.NotFoundError:
+ if isinstance(owner, astng.Instance):
+ if hasattr(owner, 'has_dynamic_getattr') and owner.has_dynamic_getattr():
+ continue
+ # XXX
+ if getattr(owner, 'name', None) == 'Values' and \
+ owner.root().name == 'optparse':
+ continue
+ _type = 'Instance of'
+ elif isinstance(owner, astng.Module):
+ _type = 'Module'
+ else:
+ _type = 'Class'
+ self.add_message('E1101', node=node,
+ args=(_type, owner_name, node.attrname))
+ # XXX: stop on the first found
+ # this is a bad solution to fix func_noerror_socket_member.py
+ break
+ except astng.InferenceError:
+ pass
+
+ def visit_assign(self, node):
+ """check that if assigning to a function call, the function is
+ possibly returning something valuable
+ """
+ if not isinstance(node.expr, astng.CallFunc):
+ return
+ function_node = self._safe_infer(node.expr.node)
+ # skip class, generator and uncomplete function definition
+ if not (isinstance(function_node, astng.Function) and
+ function_node.root().fully_defined()):
+ return
+ if function_node.is_generator() \
+ or function_node.is_abstract(pass_is_abstract=False):
+ return
+ returns = list(function_node.nodes_of_class(astng.Return,
+ skip_klass=astng.Function))
+ if len(returns) == 0:
+ self.add_message('E1111', node=node)
+ else:
+ for rnode in returns:
+ if not (isinstance(rnode.value, astng.Name)
+ and rnode.value.name == 'None'):
+ break
+ else:
+ self.add_message('W1111', node=node)
+
+ def visit_callfunc(self, node):
+ """check that called method are infered to callable objects
+ """
+ called = self._safe_infer(node.node)
+ # only function, generator and object defining __call__ are allowed
+ if called is not None and not called.callable():
+ self.add_message('E1102', node=node, args=node.node.as_string())
+
+ def _safe_infer(self, node):
+ """return the infered value for the given node.
+ Return None if inference failed or if there is some ambiguity (more than
+ one node has been infered)
+ """
+ try:
+ inferit = node.infer()
+ value = inferit.next()
+ except astng.InferenceError:
+ return
+ try:
+ inferit.next()
+ return # None if there is ambiguity on the infered node
+ except StopIteration:
+ return value
+
+def register(linter):
+ """required method to auto register this checker """
+ linter.register_checker(TypeChecker(linter))