Commit 0ed4800a authored by Dag Sverre Seljebotn's avatar Dag Sverre Seljebotn

Prototype for None-checking. *Very* unstable.

parent fb59a89d
......@@ -830,10 +830,12 @@ class NameNode(AtomicExprNode):
#
# 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)
......@@ -2073,6 +2075,7 @@ class AttributeNode(ExprNode):
#
# obj ExprNode
# attribute string
# needs_none_check boolean Used if obj is an extension type.
#
# Used internally:
#
......@@ -2089,6 +2092,7 @@ class AttributeNode(ExprNode):
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
......@@ -2317,6 +2321,15 @@ class AttributeNode(ExprNode):
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)
......@@ -4551,3 +4564,13 @@ static INLINE int __Pyx_SetItemInt(PyObject *o, Py_ssize_t i, PyObject *v, int i
""",
"""
"""]
#------------------------------------------------------------------------------------
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);
}
"""]
......@@ -81,6 +81,7 @@ class Context:
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
......@@ -104,7 +105,9 @@ class Context:
_check_c_classes,
AnalyseExpressionsTransform(self),
SwitchTransform(),
FindPossibleVariableValues(self),
OptimizeRefcounting(self),
OptimizeNoneChecking(self),
# SpecialFunctions(self),
# CreateClosureClasses(context),
]
......
......@@ -149,3 +149,119 @@ class OptimizeRefcounting(Visitor.CythonTransform):
# 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
"""
Tests accessing attributes of extension type variables
set to None
>>> obj = MyClass(2, 3)
>>> func(obj)
2
>>> func(None)
Traceback (most recent call last):
...
AttributeError: 'NoneType' object has no attribute 'a'
>>> checking(obj)
2
2
>>> checking(None)
var is None
>>> check_and_assign(obj)
Traceback (most recent call last):
...
AttributeError: 'NoneType' object has no attribute 'a'
"""
cdef class MyClass:
cdef int a, b
def __init__(self, a, b):
self.a = a
self.b = b
def func(MyClass var):
print var.a
def some():
return MyClass(4, 5)
def checking(MyClass var):
state = (var is None)
if not state:
print var.a
if var is not None:
print var.a
else:
print "var is None"
def check_and_assign(MyClass var):
if var is not None:
print var.a
var = None
print var.a
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment