#
# entry Entry Symbol table entry
# interned_cname string
+ # possible_var_values object See Optimize.FindPossibleVariableValues
is_name = 1
skip_assignment_decref = False
entry = None
+ possible_var_values = None
def create_analysed_rvalue(pos, env, entry):
node = NameNode(pos)
#
# obj ExprNode
# attribute string
+ # needs_none_check boolean Used if obj is an extension type.
#
# Used internally:
#
result = "<error>"
entry = None
is_called = 0
+ needs_none_check = True
def coerce_to(self, dst_type, env):
# If coercing to a generic pyobject and this is a cpdef function
self.obj.py_result(),
self.interned_attr_cname,
code.error_goto_if_null(self.result_code, self.pos)))
+ else:
+ # result_code contains what is needed, but we may need to insert
+ # a check and raise an exception
+ if self.obj.type.is_extension_type and self.needs_none_check:
+ code.globalstate.use_utility_code(raise_noneattr_error_utility_code)
+ code.putln("if (%s) {" % code.unlikely("%s == Py_None") % self.obj.result_as(PyrexTypes.py_object_type))
+ code.putln("__Pyx_RaiseNoneAttributeError(\"%s\");" % self.attribute.encode("UTF-8")) # todo: fix encoding
+ code.putln(code.error_goto(self.pos))
+ code.putln("}")
def generate_assignment_code(self, rhs, code):
self.obj.generate_evaluation_code(code)
""",
"""
"""]
+#------------------------------------------------------------------------------------
+
+raise_noneattr_error_utility_code = [
+"""
+static INLINE void __Pyx_RaiseNoneAttributeError(char* attrname);
+""", """
+static INLINE void __Pyx_RaiseNoneAttributeError(char* attrname) {
+ PyErr_Format(PyExc_AttributeError, "'NoneType' object has no attribute '%s'", attrname);
+}
+"""]
from ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform
from ParseTreeTransforms import ResolveOptions
from Optimize import FlattenInListTransform, SwitchTransform, OptimizeRefcounting
+ from Optimize import OptimizeNoneChecking, FindPossibleVariableValues
from Buffer import IntroduceBufferAuxiliaryVars
from ModuleNode import check_c_classes
_check_c_classes,
AnalyseExpressionsTransform(self),
SwitchTransform(),
+ FindPossibleVariableValues(self),
OptimizeRefcounting(self),
+ OptimizeNoneChecking(self),
# SpecialFunctions(self),
# CreateClosureClasses(context),
]
# Set a flag in NameNode to skip the decref
lhs.skip_assignment_decref = True
return node
+
+
+class ExtTypePossibleValues:
+ can_be_none = True
+ def copy_with(self, can_be_none=None):
+ result = ExtTypePossibleValues()
+ if can_be_none is not None:
+ result.can_be_none = can_be_none
+ return result
+ def new(self):
+ "Polymorphic constructor"
+ return ExtTypePossibleValues()
+
+class FindPossibleVariableValues(Visitor.CythonTransform):
+ """
+ Annotates NameNodes with information about the possible values
+ the variable referred to can take, *at that point* in the execution.
+
+ This is done on a best effort basis, so we can be as smart or dumb
+ as we want. A do-nothing-op should always be valid.
+
+ Each type of variable keeps a different type of "variable range"
+ information.
+
+ This information is invalid if the tree is reorganized (read:
+ keep this transform late in the pipeline).
+
+ Currently this is done:
+ - Extension types gets flagged
+ """
+
+ #
+ # Manage info stack
+ #
+ def create_empty_knowledge(self, scope):
+ knowledge = {}
+ for entry in scope.entries.values():
+ if entry.type.is_extension_type:
+ knowledge[entry] = ExtTypePossibleValues()
+ return knowledge
+
+ def visit_ModuleNode(self, node):
+ self.knowledge = self.create_empty_knowledge(node.scope)
+ self.visitchildren(node)
+ return node
+
+ def visit_FuncDefNode(self, node):
+ oldknow = self.knowledge
+ self.knowledge = self.create_empty_knowledge(node.local_scope)
+ self.visitchildren(node)
+ self.knowledge = oldknow
+ return node
+
+ def visit_NameNode(self, node):
+ node.possible_var_values = self.knowledge.get(node.entry, None)
+ return node
+
+ #
+ # Conditions which restrict possible variable values
+ #
+ def visit_IfClauseNode(self, clause):
+ def process():
+ self.visitchildren(clause)
+ return clause
+
+ # we're lazy and only check in one specific easy case: single comparison with None
+ # the code is a bit nasty but handling the proper cases will force through better code
+ # anyway
+ cond = clause.condition
+ if not isinstance(cond, ExprNodes.PrimaryCmpNode): return process()
+ if clause.condition.cascade is not None: return process()
+ if isinstance(cond.operand1, ExprNodes.NoneNode):
+ operand_checked = cond.operand2
+ elif isinstance(cond.operand2, ExprNodes.NoneNode):
+ operand_checked = cond.operand1
+ else:
+ return process()
+ if not isinstance(operand_checked, ExprNodes.NameNode):
+ return process()
+ entry = operand_checked.entry
+ if entry not in self.knowledge:
+ # Not tracking this variable
+ return process()
+ # Finally!
+ if cond.operator == 'is_not':
+ # Within this block we can assume the variable is not None
+ # (until it is reassigned)
+ self.visitchildren(clause, attrs=["condition"])
+ oldvalues = self.knowledge[entry]
+ self.knowledge[entry] = oldvalues.copy_with(can_be_none=False)
+ self.visitchildren(clause, attrs=["body"])
+ self.knowledge[entry] = oldvalues
+ return clause
+ else:
+ return process()
+
+
+ # Assignments which reset possible variable values
+ def visit_SingleAssignmentNode(self, node):
+ if isinstance(node.lhs, ExprNodes.NameNode):
+ entry = node.lhs.entry
+ if entry in self.knowledge:
+ self.knowledge[entry] = self.knowledge[entry].new()
+ self.visitchildren(node)
+ return node
+
+class OptimizeNoneChecking(Visitor.CythonTransform):
+ def visit_AttributeNode(self, node):
+ if isinstance(node.obj, ExprNodes.NameNode):
+ obj = node.obj
+ if obj.type.is_extension_type and not obj.possible_var_values.can_be_none:
+ node.needs_none_check = False
+ self.visitchildren(node)
+ return node
+
+