Commit 80ab2867 authored by Xavier Thompson's avatar Xavier Thompson

Introduce the 'consume' keyword

This is different from 'recover' (which was previously named 'consume'):

```
  (1)	consume <name | attr> [ with <expr> ]
```

- The operand can either be a *name* an *attribute*.

- if the operand is a cypclass or a pointer the value NULL
  is assigned to it; else the operand keeps its old value.

- The resulting value of 'consume' is the old value of the operand.

- The resulting type is the type of the operand seen as an rvalue,
  i.e. the same type except for 'iso' which is seen as 'iso~'.
parent 569b939d
......@@ -11414,7 +11414,7 @@ class RecoverNode(ExprNode):
self.is_temp = self.operand_is_named or (self.generate_runtime_check and not solid_operand.is_temp)
self.solid_operand = solid_operand
if self.operand_is_named:
solid_operand.entry.is_recovered = True
solid_operand.entry.is_consumed = True
return self
def may_be_none(self):
......@@ -11468,6 +11468,63 @@ class RecoverNode(ExprNode):
self.operand.generate_post_assignment_code(code)
class ConsumeNode(ExprNode):
# Consume expression
#
# operand ExprNode
subexprs = ['operand']
def infer_type(self, env):
operand_type = self.operand.infer_type(env)
if operand_type.is_cyp_class and operand_type.qualifier == 'iso':
return PyrexTypes.cyp_class_qualified_type(operand_type.qual_base_type, 'iso~')
else:
return operand_type
def analyse_types(self, env):
self.operand = self.operand.analyse_types(env)
if not self.operand.is_name and not self.operand.is_attribute:
error(self.pos, "Can only consume named variables or fields")
self.type = PyrexTypes.error_type
return self
operand_type = self.operand.type
if operand_type.is_cyp_class and operand_type.qualifier == 'iso':
self.type = PyrexTypes.cyp_class_qualified_type(operand_type.qual_base_type, 'iso~')
else:
self.type = operand_type
self.is_temp = operand_type.is_cyp_class or operand_type.is_ptr
self.operand.entry.is_consumed = True
return self
def may_be_none(self):
return self.operand.may_be_none()
def is_simple(self):
return self.operand.is_simple()
def make_owned_reference(self, code):
# We steal the reference of the recovered operand.
pass
def calculate_result_code(self):
return self.operand.result()
def result_in_temp(self):
return self.is_temp or self.operand.result_in_temp()
def generate_result_code(self, code):
if self.is_temp:
code.putln("%s = %s;" % (self.result(), self.operand.result()))
code.putln("%s = NULL;" % self.operand.result())
def generate_post_assignment_code(self, code):
if self.is_temp:
ExprNode.generate_post_assignment_code(self, code)
else:
self.operand.generate_post_assignment_code(code)
class TypeidNode(ExprNode):
# C++ typeid operator applied to a type or variable
#
......
......@@ -2199,7 +2199,7 @@ class FuncDefNode(StatNode, BlockNode):
if entry.type.is_cyp_class:
# CyObjects use another refcounting convention.
# Except for the 'self' argument.
if entry.is_self_arg and (entry.is_recovered or entry.cf_is_reassigned) and not entry.in_closure:
if entry.is_self_arg and (entry.is_consumed or entry.cf_is_reassigned) and not entry.in_closure:
code.put_var_incref(entry)
elif (acquire_gil or entry.cf_is_reassigned) and not entry.in_closure:
code.put_var_incref(entry)
......@@ -2419,8 +2419,8 @@ class FuncDefNode(StatNode, BlockNode):
continue
elif entry.type.is_cyp_class:
# CyObject arguments are systematically decrefed.
# Except for the 'self' argument when it has not been reassigned or recovered.
if entry.is_self_arg and not (entry.is_recovered or entry.cf_is_reassigned):
# Except for the 'self' argument when it has not been reassigned or consumed.
if entry.is_self_arg and not (entry.is_consumed or entry.cf_is_reassigned):
continue
else:
if entry.in_closure:
......
......@@ -311,6 +311,8 @@ def _p_factor(s):
return p_sizeof(s)
elif sy == 'IDENT' and s.systring == "recover":
return p_recover(s)
elif sy == 'IDENT' and s.systring == "consume":
return p_consume(s)
return p_power(s)
def p_typecast(s):
......@@ -367,6 +369,13 @@ def p_recover(s):
operand = p_factor(s)
return ExprNodes.RecoverNode(pos, operand = operand)
def p_consume(s):
# s.sy == ident "consume"
pos = s.position()
s.next()
operand = p_factor(s)
return ExprNodes.ConsumeNode(pos, operand = operand)
def p_yield_expression(s):
# s.sy == "yield"
......
......@@ -181,7 +181,7 @@ class Entry(object):
#
# active_entry Entry Entry for the active version of an asyncable cypclass method
#
# is_recovered boolean The entry is the operand of a 'recover' expression.
# is_consumed boolean The entry is the operand of a 'consume' or 'recover' expression.
#
# is_specialised boolean The entry is a template specialisation.
......@@ -262,7 +262,7 @@ class Entry(object):
static_cname = None
original_name = None
active_entry = None
is_recovered = False
is_consumed = False
is_specialised = False
def __init__(self, name, cname, type, pos = None, init = None):
......
# mode: error
# tag: cpp, cpp11, pthread
# cython: experimental_cpp_class_def=True, language_level=2
cdef cypclass A:
pass
def test_consume_iso_to_iso():
cdef iso A a = recover A()
cdef iso A b = consume a
def test_consume_mut_to_iso():
cdef A a = A()
cdef iso A b = consume a
def consume_rvalue():
a = consume A()
_ERRORS = u'''
14:19: Cannot assign type 'A' to 'iso A'
17:8: Can only consume named variables or fields
'''
# mode: run
# tag: cpp, cpp11, pthread
# cython: experimental_cpp_class_def=True, language_level=2
cdef cypclass A:
pass
def test_consume():
"""
>>> test_consume()
0
"""
a = A()
old_a = a
b = consume a
if a is not NULL:
return -1
if b is not old_a:
return -2
return 0
cdef cypclass Origin:
A field
__init__(self):
self.field = A()
def test_consume_field():
o = Origin()
old_field = o.field
a = consume o.field
if o.field is not NULL:
return -1
if a is not old_field:
return -2
return 0
# mode: run
# tag: cpp, cpp11, pthread
# cython: experimental_cpp_class_def=True, language_level=2
cdef cypclass Refcounted:
__dealloc__(self) with gil:
print("Refcounted destroyed")
def test_consume_name():
"""
>>> test_consume_name()
Refcounted destroyed
0
"""
r0 = Refcounted()
if Cy_GETREF(r0) != 2:
return -1
cdef Refcounted r1 = consume r0
if r0 is not NULL:
return -2
if Cy_GETREF(r1) != 2:
return -3
return 0
def test_consume_iso_name():
"""
>>> test_consume_iso_name()
Refcounted destroyed
0
"""
cdef iso Refcounted r0 = recover Refcounted()
cdef Refcounted r1 = consume r0
if r0 is not NULL:
return -2
if Cy_GETREF(r1) != 2:
return -3
return 0
def test_consume_and_drop_name():
"""
>>> test_consume_and_drop_name()
Refcounted destroyed
consumed
0
"""
r = Refcounted()
if Cy_GETREF(r) != 2:
return -1
consume r
print("consumed")
if r is not NULL:
return -2
return 0
cdef cypclass Origin:
Refcounted field
__init__(self):
self.field = Refcounted()
cdef cypclass OriginIso:
iso Refcounted field
__init__(self):
self.field = recover Refcounted()
def test_consume_field():
"""
>>> test_consume_field()
Refcounted destroyed
0
"""
cdef Origin o = Origin()
cdef Refcounted r = consume o.field
if Cy_GETREF(r) != 2:
return -1
if o.field is not NULL:
return -2
return 0
def test_consume_and_drop_field():
"""
>>> test_consume_and_drop_field()
Refcounted destroyed
consumed
0
"""
cdef Origin o = Origin()
consume o.field
print("consumed")
if o.field is not NULL:
return -2
return 0
def test_consume_iso_field():
"""
>>> test_consume_iso_field()
consumed
Refcounted destroyed
0
"""
cdef OriginIso o = OriginIso()
cdef iso Refcounted r = consume o.field
print("consumed")
if o.field is not NULL:
return -1
return 0
def test_consume_and_drop_iso_field():
"""
>>> test_consume_and_drop_iso_field()
Refcounted destroyed
consumed
0
"""
cdef OriginIso o = OriginIso()
consume o.field
print("consumed")
if o.field is not NULL:
return -1
return 0
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