Unverified Commit 2c7c22f5 authored by Ashwin Srinath's avatar Ashwin Srinath Committed by GitHub

Add support for C++ scoped enums with "enum class" and "enum struct" (GH-3640)

Closes #1603.
parent e87a5559
......@@ -2146,7 +2146,7 @@ class NameNode(AtomicExprNode):
entry = self.entry
if entry.is_type and entry.type.is_extension_type:
self.type_entry = entry
if entry.is_type and entry.type.is_enum:
if entry.is_type and (entry.type.is_enum or entry.type.is_cpp_enum):
py_entry = Symtab.Entry(self.name, None, py_object_type)
py_entry.is_pyglobal = True
py_entry.scope = self.entry.scope
......@@ -6957,7 +6957,7 @@ class AttributeNode(ExprNode):
ubcm_entry.is_unbound_cmethod = 1
ubcm_entry.scope = entry.scope
return self.as_name_node(env, ubcm_entry, target=False)
elif type.is_enum:
elif type.is_enum or type.is_cpp_enum:
if self.attribute in type.values:
for entry in type.entry.enum_values:
if entry.name == self.attribute:
......
......@@ -155,7 +155,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
self.create_import_star_conversion_utility_code(env)
for name, entry in sorted(env.entries.items()):
if (entry.create_wrapper and entry.scope is env
and entry.is_type and entry.type.is_enum):
and entry.is_type and (entry.type.is_enum or entry.type.is_cpp_enum)):
entry.type.create_type_wrapper(env)
def process_implementation(self, options, result):
......@@ -880,7 +880,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
type = entry.type
if type.is_typedef: # Must test this first!
self.generate_typedef(entry, code)
elif type.is_enum:
elif type.is_enum or type.is_cpp_enum:
self.generate_enum_definition(entry, code)
elif type.is_struct_or_union:
self.generate_struct_union_definition(entry, code)
......@@ -957,8 +957,6 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
code.putln("#endif")
code.putln(header)
var_entries = scope.var_entries
if not var_entries:
error(entry.pos, "Empty struct or union definition not allowed outside a 'cdef extern from' block")
for attr in var_entries:
code.putln(
"%s;" % attr.type.declaration_code(attr.cname))
......@@ -1079,7 +1077,9 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
code.mark_pos(entry.pos)
type = entry.type
name = entry.cname or entry.name or ""
header, footer = self.sue_header_footer(type, "enum", name)
kind = "enum class" if entry.type.is_cpp_enum else "enum"
header, footer = self.sue_header_footer(type, kind, name)
code.putln(header)
enum_values = entry.enum_values
if not enum_values:
......@@ -1093,18 +1093,20 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
for value_entry in enum_values:
if value_entry.value_node is None:
value_code = value_entry.cname
value_code = value_entry.cname.split("::")[-1]
else:
value_code = ("%s = %s" % (
value_entry.cname,
value_entry.cname.split("::")[-1],
value_entry.value_node.result()))
if value_entry is not last_entry:
value_code += ","
code.putln(value_code)
code.putln(footer)
if entry.type.typedef_flag:
# Not pre-declared.
code.putln("typedef enum %s %s;" % (name, name))
if entry.type.is_enum:
if entry.type.typedef_flag:
# Not pre-declared.
code.putln("typedef enum %s %s;" % (name, name))
def generate_typeobj_predeclaration(self, entry, code):
code.putln("")
......
......@@ -23,7 +23,7 @@ from . import TypeSlots
from .PyrexTypes import py_object_type, error_type
from .Symtab import (ModuleScope, LocalScope, ClosureScope, PropertyScope,
StructOrUnionScope, PyClassScope, CppClassScope, TemplateScope,
punycodify_name)
CppScopedEnumScope, punycodify_name)
from .Code import UtilityCode
from .StringEncoding import EncodedString
from . import Future
......@@ -1535,36 +1535,56 @@ class CppClassNode(CStructOrUnionDefNode, BlockNode):
class CEnumDefNode(StatNode):
# name string or None
# cname string or None
# items [CEnumDefItemNode]
# typedef_flag boolean
# visibility "public" or "private" or "extern"
# api boolean
# in_pxd boolean
# create_wrapper boolean
# entry Entry
child_attrs = ["items"]
# name string or None
# cname string or None
# scoped boolean Is a C++ scoped enum
# underlying_type CSimpleBaseTypeNode The underlying value type (int or C++ type)
# items [CEnumDefItemNode]
# typedef_flag boolean
# visibility "public" or "private" or "extern"
# api boolean
# in_pxd boolean
# create_wrapper boolean
# entry Entry
child_attrs = ["items", "underlying_type"]
def declare(self, env):
self.entry = env.declare_enum(
self.name, self.pos,
cname=self.cname, typedef_flag=self.typedef_flag,
cname=self.cname,
scoped=self.scoped,
typedef_flag=self.typedef_flag,
visibility=self.visibility, api=self.api,
create_wrapper=self.create_wrapper)
def analyse_declarations(self, env):
scope = None
underlying_type = self.underlying_type.analyse(env)
if not underlying_type.is_int:
error(self.underlying_type.pos, "underlying type is not an integral type")
self.entry.type.underlying_type = underlying_type
if self.scoped and self.items is not None:
scope = CppScopedEnumScope(self.name, env)
scope.type = self.entry.type
else:
scope = env
if self.items is not None:
if self.in_pxd and not env.in_cinclude:
self.entry.defined_in_pxd = 1
for item in self.items:
item.analyse_declarations(env, self.entry)
item.analyse_declarations(scope, self.entry)
def analyse_expressions(self, env):
return self
def generate_execution_code(self, code):
if self.scoped:
return # nothing to do here for C++ enums
if self.visibility == 'public' or self.api:
code.mark_pos(self.pos)
temp = code.funcstate.allocate_temp(PyrexTypes.py_object_type, manage_ref=True)
......@@ -1596,9 +1616,15 @@ class CEnumDefItemNode(StatNode):
if not self.value.type.is_int:
self.value = self.value.coerce_to(PyrexTypes.c_int_type, env)
self.value = self.value.analyse_const_expression(env)
if enum_entry.type.is_cpp_enum:
cname = "%s::%s" % (enum_entry.cname, self.name)
else:
cname = self.cname
entry = env.declare_const(
self.name, enum_entry.type,
self.value, self.pos, cname=self.cname,
self.value, self.pos, cname=cname,
visibility=enum_entry.visibility, api=enum_entry.api,
create_wrapper=enum_entry.create_wrapper and enum_entry.name is None)
enum_entry.enum_values.append(entry)
......
......@@ -3133,6 +3133,12 @@ def p_cdef_extern_block(s, pos, ctx):
def p_c_enum_definition(s, pos, ctx):
# s.sy == ident 'enum'
s.next()
scoped = False
if s.context.cpp and (s.sy == 'class' or (s.sy == 'IDENT' and s.systring == 'struct')):
scoped = True
s.next()
if s.sy == 'IDENT':
name = s.systring
s.next()
......@@ -3140,24 +3146,49 @@ def p_c_enum_definition(s, pos, ctx):
if cname is None and ctx.namespace is not None:
cname = ctx.namespace + "::" + name
else:
name = None
cname = None
items = None
name = cname = None
if scoped:
s.error("Unnamed scoped enum not allowed")
if scoped and s.sy == '(':
s.next()
underlying_type = p_c_base_type(s)
s.expect(')')
else:
underlying_type = Nodes.CSimpleBaseTypeNode(
pos,
name="int",
module_path = [],
is_basic_c_type = True,
signed = 1,
complex = 0,
longness = 0
)
s.expect(':')
items = []
if s.sy != 'NEWLINE':
p_c_enum_line(s, ctx, items)
else:
s.next() # 'NEWLINE'
s.expect_indent()
while s.sy not in ('DEDENT', 'EOF'):
p_c_enum_line(s, ctx, items)
s.expect_dedent()
if not items and ctx.visibility != "extern":
error(pos, "Empty enum definition not allowed outside a 'cdef extern from' block")
return Nodes.CEnumDefNode(
pos, name = name, cname = cname, items = items,
typedef_flag = ctx.typedef_flag, visibility = ctx.visibility,
create_wrapper = ctx.overridable,
api = ctx.api, in_pxd = ctx.level == 'module_pxd')
pos, name=name, cname=cname,
scoped=scoped, items=items,
underlying_type=underlying_type,
typedef_flag=ctx.typedef_flag, visibility=ctx.visibility,
create_wrapper=ctx.overridable,
api=ctx.api, in_pxd=ctx.level == 'module_pxd')
def p_c_enum_line(s, ctx, items):
if s.sy != 'pass':
......@@ -3217,8 +3248,12 @@ def p_c_struct_or_union_definition(s, pos, ctx):
s.next()
s.expect_newline("Expected a newline")
s.expect_dedent()
if not attributes and ctx.visibility != "extern":
error(pos, "Empty struct or union definition not allowed outside a 'cdef extern from' block")
else:
s.expect_newline("Syntax error in struct or union definition")
return Nodes.CStructOrUnionDefNode(pos,
name = name, cname = cname, kind = kind, attributes = attributes,
typedef_flag = ctx.typedef_flag, visibility = ctx.visibility,
......
......@@ -182,6 +182,7 @@ class PyrexType(BaseType):
# is_struct_or_union boolean Is a C struct or union type
# is_struct boolean Is a C struct type
# is_enum boolean Is a C enum type
# is_cpp_enum boolean Is a C++ scoped enum type
# is_typedef boolean Is a typedef type
# is_string boolean Is a C char * type
# is_pyunicode_ptr boolean Is a C PyUNICODE * type
......@@ -248,6 +249,7 @@ class PyrexType(BaseType):
is_cpp_string = 0
is_struct = 0
is_enum = 0
is_cpp_enum = False
is_typedef = 0
is_string = 0
is_pyunicode_ptr = 0
......@@ -4019,6 +4021,73 @@ class CppClassType(CType):
if constructor is not None and best_match([], constructor.all_alternatives()) is None:
error(pos, "C++ class must have a nullary constructor to be %s" % msg)
class CppScopedEnumType(CType):
# name string
# cname string
is_cpp_enum = True
def __init__(self, name, cname, underlying_type, namespace=None):
self.name = name
self.cname = cname
self.values = []
self.underlying_type = underlying_type
self.namespace = namespace
def __str__(self):
return self.name
def declaration_code(self, entity_code,
for_display=0, dll_linkage=None, pyrex=0):
if pyrex or for_display:
type_name = self.name
else:
if self.namespace:
type_name = "%s::%s" % (
self.namespace.empty_declaration_code(),
self.cname
)
else:
type_name = "enum %s" % self.cname
type_name = public_decl(type_name, dll_linkage)
return self.base_declaration_code(type_name, entity_code)
def create_from_py_utility_code(self, env):
if self.from_py_function:
return True
if self.underlying_type.create_from_py_utility_code(env):
self.from_py_function = '(%s)%s' % (
self.cname, self.underlying_type.from_py_function
)
return True
def create_to_py_utility_code(self, env):
if self.to_py_function is not None:
return True
if self.underlying_type.create_to_py_utility_code(env):
# Using a C++11 lambda here, which is fine since
# scoped enums are a C++11 feature
self.to_py_function = '[](const %s& x){return %s((%s)x);}' % (
self.cname,
self.underlying_type.to_py_function,
self.underlying_type.empty_declaration_code()
)
return True
def create_type_wrapper(self, env):
from .UtilityCode import CythonUtilityCode
rst = CythonUtilityCode.load(
"CppScopedEnumType", "CpdefEnums.pyx",
context={
"name": self.name,
"cname": self.cname.split("::")[-1],
"items": tuple(self.values),
"underlying_type": self.underlying_type.empty_declaration_code(),
},
outer_module_scope=env.global_scope())
env.use_utility_code(rst)
class TemplatePlaceholderType(CType):
......
......@@ -679,8 +679,8 @@ class Scope(object):
error(pos, "'%s' previously declared as '%s'" % (
entry.name, entry.visibility))
def declare_enum(self, name, pos, cname, typedef_flag,
visibility = 'private', api = 0, create_wrapper = 0):
def declare_enum(self, name, pos, cname, scoped, typedef_flag,
visibility='private', api=0, create_wrapper=0):
if name:
if not cname:
if (self.in_cinclude or visibility == 'public'
......@@ -692,13 +692,18 @@ class Scope(object):
namespace = self.outer_scope.lookup(self.name).type
else:
namespace = None
type = PyrexTypes.CEnumType(name, cname, typedef_flag, namespace)
if scoped:
type = PyrexTypes.CppScopedEnumType(name, cname, namespace)
else:
type = PyrexTypes.CEnumType(name, cname, typedef_flag, namespace)
else:
type = PyrexTypes.c_anon_enum_type
entry = self.declare_type(name, type, pos, cname = cname,
visibility = visibility, api = api)
entry.create_wrapper = create_wrapper
entry.enum_values = []
self.sue_entries.append(entry)
return entry
......@@ -2628,6 +2633,22 @@ class CppClassScope(Scope):
return scope
class CppScopedEnumScope(Scope):
# Namespace of a ScopedEnum
def __init__(self, name, outer_scope):
Scope.__init__(self, name, outer_scope, None)
def declare_var(self, name, type, pos,
cname=None, visibility='extern'):
# Add an entry for an attribute.
if not cname:
cname = name
entry = self.declare(name, cname, type, pos, visibility)
entry.is_variable = True
return entry
class PropertyScope(Scope):
# Scope holding the __get__, __set__ and __del__ methods for
# a property of an extension type.
......
......@@ -60,3 +60,21 @@ else:
{{for item in items}}
__Pyx_globals['{{item}}'] = {{name}}({{item}}, '{{item}}')
{{endfor}}
#################### CppScopedEnumType ####################
#@requires: EnumBase
cdef dict __Pyx_globals = globals()
if PY_VERSION_HEX >= 0x03040000:
# create new IntEnum()
__Pyx_globals["{{name}}"] = __Pyx_EnumBase('{{name}}', __Pyx_OrderedDict([
{{for item in items}}
('{{item}}', <{{underlying_type}}>({{name}}.{{item}})),
{{endfor}}
]))
else:
__Pyx_globals["{{name}}"] = type('{{name}}', (__Pyx_EnumBase,), {})
{{for item in items}}
__Pyx_globals["{{name}}"](<{{underlying_type}}>({{name}}.{{item}}), '{{item}}')
{{endfor}}
......@@ -482,6 +482,33 @@ Note, however, that it is unnecessary to declare the arguments of extern
functions as references (const or otherwise) as it has no impact on the
caller's syntax.
Scoped Enumerations
-------------------
Cython supports scoped enumerations (:keyword:`enum class`) in C++ mode::
cdef enum class Cheese:
cheddar = 1
camembert = 2
As with "plain" enums, you may access the enumerators as attributes of the type.
Unlike plain enums however, the enumerators are not visible to the
enclosing scope::
cdef Cheese c1 = Cheese.cheddar # OK
cdef Cheese c2 = cheddar # ERROR!
Optionally, you may specify the underlying type of a scoped enumeration.
This is especially important when declaring an external scoped enumeration
with an underlying type::
cdef extern from "Foo.h":
cdef enum class Spam(unsigned int):
x = 10
y = 20
...
Declaring an enum class as ``cpdef`` will create a :pep:`435`-style Python wrapper.
``auto`` Keyword
----------------
......
# mode: compile
# tag: cpp,cpp11
cpdef enum class Spam:
a, b
c
d
e
f = 42
cpdef enum class Cheese(unsigned int):
x = 1
y = 2
cdef enum struct parrot_state:
alive = 1
dead = 0
cdef void eggs():
cdef Spam s1
s1 = Spam.a
s2 = Spam.b
cdef Cheese c1
c1 = Cheese.x
eggs()
# mode: error
# tag: cpp
cdef enum class Spam:
a
cdef enum class Spam:
b
_ERRORS="""
7:5: 'Spam' redeclared
4:5: Previous declaration is here
"""
# mode: error
cdef enum Spam(int):
a, b
_ERRORS = u"""
3:14: Expected ':', found '('
"""
# mode: run
# tag: cpp, cpp11
cdef extern from *:
"""
enum class Enum1 {
Item1 = 1,
Item2 = 2
};
"""
cpdef enum class Enum1:
Item1
Item2
def test_enum_to_list():
"""
>>> test_enum_to_list()
"""
assert list(Enum1) == [1, 2]
# mode: run
# tag: cpp, cpp11
"""
PYTHON setup.py build_ext --inplace
PYTHON -c "import runner"
"""
######## setup.py ########
from Cython.Build.Dependencies import cythonize
from distutils.core import setup
setup(ext_modules=cythonize("*.pyx", language='c++'))
setup(
ext_modules = cythonize([
"cheese.pyx",
"import_scoped_enum_test.pyx",
"dotted_import_scoped_enum_test.pyx"
])
)
######## cheese.pxd ########
# distutils: language = c++
# distutils: extra_compile_args = -std=c++11
cdef extern from * namespace "Namespace":
"""
namespace Namespace {
enum class Cheese {
cheddar = 1,
camembert = 2
};
}
"""
cpdef enum class Cheese:
cheddar
camembert
######## cheese.pyx ########
# distutils: language = c++
# distutils: extra_compile_args = -std=c++11
pass
######## import_scoped_enum_test.pyx ########
# distutils: language = c++
# distutils: extra_compile_args = -std=c++11
from cheese import Cheese
from cheese cimport Cheese
cdef Cheese c = Cheese.cheddar
assert list(Cheese) == [1, 2]
######## dotted_import_scoped_enum_test.pyx ########
# distutils: language = c++
# distutils: extra_compile_args = -std=c++11
cimport cheese
cdef cheese.Cheese c = cheese.Cheese.cheddar
assert [cheese.Cheese.cheddar, cheese.Cheese.camembert] == [1, 2]
cdef cheese.Cheese d = int(1)
######## runner.py ########
import import_scoped_enum_test
import dotted_import_scoped_enum_test
# mode: run
# tag: cpp, cpp11
from libcpp.limits cimport numeric_limits
cdef extern from *:
"""
enum class Enum1 {
Item1,
Item2
};
"""
cdef enum class Enum1:
Item1
Item2
cdef extern from * namespace "Namespace1":
"""
namespace Namespace1 {
enum class Enum2 {
Item1,
Item2
};
}
"""
cdef enum class Enum2:
Item1
Item2
cdef enum class Enum3(int):
a = 1
b = 2
cdef extern from *:
"""
enum class sorted
{
a = 1,
b = 0
};
"""
cdef enum class Enum4 "sorted":
a
b
cdef extern from *:
"""
#include <limits>
enum class LongIntEnum : long int {
val = std::numeric_limits<long int>::max(),
};
"""
enum class LongIntEnum(long int):
val
def test_compare_enums():
"""
>>> test_compare_enums()
(True, True, False, False)
"""
cdef Enum1 x, y
x = Enum1.Item1
y = Enum1.Item2
return (
x == Enum1.Item1,
y == Enum1.Item2,
x == Enum1.Item2,
y == Enum1.Item1
)
def test_compare_namespace_enums():
"""
>>> test_compare_enums()
(True, True, False, False)
"""
cdef Enum2 z, w
z = Enum2.Item1
w = Enum2.Item2
return (
z == Enum2.Item1,
w == Enum2.Item2,
z == Enum2.Item2,
w == Enum2.Item1
)
def test_coerce_to_from_py_value(object i):
"""
>>> test_coerce_to_from_py_value(1)
(True, False)
>>> test_coerce_to_from_py_value(2)
(False, True)
>>> test_coerce_to_from_py_value(3)
(False, False)
>>> test_coerce_to_from_py_value(11111111111111111111111111111111111111111111)
Traceback (most recent call last):
OverflowError: Python int too large to convert to C long
"""
cdef Enum3 x = i
y = Enum3.b
return (
x == Enum3.a,
y == int(i)
)
def test_reserved_cname():
"""
>>> test_reserved_cname()
True
"""
cdef Enum4 x = Enum4.a
return Enum4.a == int(1)