Skip to content

Commit db55a44

Browse files
committed
transpile: Translate enums into newtype structs
1 parent 43ba401 commit db55a44

9 files changed

Lines changed: 112 additions & 70 deletions

File tree

c2rust-transpile/src/translator/enums.rs

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,18 @@ impl<'c> Translation<'c> {
2323
.borrow()
2424
.resolve_decl_name(enum_id)
2525
.expect("Enums should already be renamed");
26-
let ty = self.convert_type(integral_type.ctype)?;
27-
Ok(ConvertedDecl::Item(
28-
mk().span(span).pub_().type_item(enum_name, ty),
29-
))
26+
27+
let field = mk()
28+
.pub_()
29+
.enum_field(self.convert_type(integral_type.ctype)?);
30+
let item = mk()
31+
.span(span)
32+
.call_attr("derive", vec!["Clone", "Copy"])
33+
.call_attr("repr", vec!["transparent"])
34+
.pub_()
35+
.struct_item(enum_name, vec![field], true);
36+
37+
Ok(ConvertedDecl::Item(item))
3038
}
3139

3240
pub fn convert_enum_constant(
@@ -46,47 +54,51 @@ impl<'c> Translation<'c> {
4654
.borrow()
4755
.resolve_decl_name(enum_id)
4856
.expect("Enums should already be renamed");
49-
self.add_import(enum_id, &enum_name);
5057

51-
let ty = mk().ident_ty(enum_name);
58+
let ty = mk().ident_ty(enum_name.clone());
5259
let val = match value {
5360
ConstIntExpr::I(value) => signed_int_expr(value),
5461
ConstIntExpr::U(value) => mk().lit_expr(mk().int_unsuffixed_lit(value as u128)),
5562
};
63+
let init = self.enum_constructor_expr(enum_id, val);
5664

5765
Ok(ConvertedDecl::Item(
58-
mk().span(span).pub_().const_item(name, ty, val),
66+
mk().span(span).pub_().const_item(name, ty, init),
5967
))
6068
}
6169

62-
pub fn convert_enum_zero_initializer(&self, type_id: CTypeId) -> WithStmts<Box<Expr>> {
63-
WithStmts::new_val(self.enum_for_i64(type_id, 0))
70+
pub fn convert_enum_zero_initializer(&self, enum_id: CEnumId) -> WithStmts<Box<Expr>> {
71+
WithStmts::new_val(self.enum_for_i64(enum_id, 0))
6472
}
6573

6674
/// Translate a cast where the source type, but not the target type, is an `enum` type.
6775
pub fn convert_cast_from_enum(
6876
&self,
6977
target_cty: CTypeId,
70-
val: Box<Expr>,
78+
mut val: Box<Expr>,
7179
) -> TranslationResult<Box<Expr>> {
7280
// Convert it to the expected integral type.
81+
val = self.integer_from_enum(val);
82+
7383
let ty = self.convert_type(target_cty)?;
74-
Ok(mk().cast_expr(val, ty))
84+
val = mk().cast_expr(val, ty);
85+
86+
Ok(val)
7587
}
7688

77-
/// Translate a cast where the target type is an `enum` type.
78-
///
79-
/// When translating variable references to `EnumConstant`s, we always insert casts to the
80-
/// expected type. In C, `EnumConstant`s have some integral type, _not_ the enum type. However,
81-
/// if we then immediately have a cast to convert this variable back into an enum type, we would
82-
/// like to produce Rust with _no_ casts. This function handles this simplification.
89+
/// Gets the inner integral value of an enum value.
90+
pub fn integer_from_enum(&self, val: Box<Expr>) -> Box<Expr> {
91+
mk().anon_field_expr(val, 0)
92+
}
93+
94+
/// Translates a cast where the target type is an `enum` type.
8395
pub fn convert_cast_to_enum(
8496
&self,
8597
ctx: ExprContext,
86-
enum_type_id: CTypeId,
8798
enum_id: CEnumId,
8899
expr: Option<CExprId>,
89-
val: Box<Expr>,
100+
source_cty: CTypeId,
101+
mut val: Box<Expr>,
90102
) -> TranslationResult<Box<Expr>> {
91103
if let Some(expr) = expr {
92104
match self.ast_context[expr].kind {
@@ -110,33 +122,42 @@ impl<'c> Translation<'c> {
110122
}
111123

112124
CExprKind::Literal(_, CLiteral::Integer(i, _)) => {
113-
return Ok(self.enum_for_i64(enum_type_id, i as i64));
125+
return Ok(self.enum_for_i64(enum_id, i as i64));
114126
}
115127

116128
CExprKind::Unary(_, c_ast::UnOp::Negate, subexpr_id, _) => {
117129
if let &CExprKind::Literal(_, CLiteral::Integer(i, _)) =
118130
&self.ast_context[subexpr_id].kind
119131
{
120-
return Ok(self.enum_for_i64(enum_type_id, -(i as i64)));
132+
return Ok(self.enum_for_i64(enum_id, -(i as i64)));
121133
}
122134
}
123135

124136
_ => {}
125137
}
126138
}
127139

128-
let target_ty = self.convert_type(enum_type_id)?;
129-
Ok(mk().cast_expr(val, target_ty))
140+
let integral_type = self.enum_integral_type(enum_id);
141+
let mut needs_cast = true;
142+
143+
// C allows directly casting from enum to enum, but in Rust we need to use
144+
// the inner integer value as an intermediate.
145+
if let CTypeKind::Enum(source_enum_id) = self.ast_context.resolve_type(source_cty).kind {
146+
val = self.integer_from_enum(val);
147+
needs_cast = integral_type.ctype != self.enum_integral_type(source_enum_id).ctype;
148+
}
149+
150+
if needs_cast {
151+
let ty = self.convert_type(integral_type.ctype)?;
152+
val = mk().cast_expr(val, ty);
153+
}
154+
155+
Ok(self.enum_constructor_expr(enum_id, val))
130156
}
131157

132158
/// Given an integer value this attempts to either generate the corresponding enum
133159
/// variant directly, otherwise it converts a number to the enum type.
134-
fn enum_for_i64(&self, enum_type_id: CTypeId, value: i64) -> Box<Expr> {
135-
let enum_id = match self.ast_context.resolve_type(enum_type_id).kind {
136-
CTypeKind::Enum(enum_id) => enum_id,
137-
_ => panic!("{:?} does not point to an `enum` type", enum_type_id),
138-
};
139-
160+
fn enum_for_i64(&self, enum_id: CEnumId, value: i64) -> Box<Expr> {
140161
if let Some(enum_constant_id) = self.enum_variant_for_i64(enum_id, value) {
141162
return self.enum_constant_expr(enum_constant_id);
142163
}
@@ -148,8 +169,7 @@ impl<'c> Translation<'c> {
148169
_ => signed_int_expr(value),
149170
};
150171

151-
let target_ty = self.convert_type(enum_type_id).unwrap();
152-
mk().cast_expr(value, target_ty)
172+
self.enum_constructor_expr(enum_id, value)
153173
}
154174

155175
/// Returns the id of the variant of `enum_id` whose value matches `value`, if any.
@@ -170,6 +190,16 @@ impl<'c> Translation<'c> {
170190
})
171191
}
172192

193+
fn enum_constructor_expr(&self, enum_id: CEnumId, value: Box<Expr>) -> Box<Expr> {
194+
let enum_name = self
195+
.type_converter
196+
.borrow()
197+
.resolve_decl_name(enum_id)
198+
.unwrap();
199+
self.add_import(enum_id, &enum_name);
200+
mk().call_expr(mk().ident_expr(enum_name), vec![value])
201+
}
202+
173203
fn enum_constant_expr(&self, enum_constant_id: CEnumConstantId) -> Box<Expr> {
174204
let name = self.renamer.borrow().get(&enum_constant_id).unwrap();
175205
self.add_import(enum_constant_id, &name);

c2rust-transpile/src/translator/mod.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4486,18 +4486,12 @@ impl<'c> Translation<'c> {
44864486
Ok(val.map(|val| mk().call_expr(fn_path, vec![val])))
44874487
} else if let CTypeKind::LongDouble = self.ast_context[source_cty.ctype].kind {
44884488
self.f128_cast_to(val, target_ty_kind)
4489-
} else if let &CTypeKind::Enum(enum_decl_id) = target_ty_kind {
4489+
} else if let &CTypeKind::Enum(enum_id) = target_ty_kind {
44904490
// Casts targeting `enum` types...
44914491
let expr =
44924492
expr.ok_or_else(|| format_err!("Casts to enums require a C ExprId"))?;
44934493
val.result_map(|val| {
4494-
self.convert_cast_to_enum(
4495-
ctx,
4496-
target_cty.ctype,
4497-
enum_decl_id,
4498-
Some(expr),
4499-
val,
4500-
)
4494+
self.convert_cast_to_enum(ctx, enum_id, Some(expr), source_cty.ctype, val)
45014495
})
45024496
} else if target_ty_kind.is_floating_type() && source_ty_kind.is_bool() {
45034497
val.and_then(|x| {
@@ -4799,7 +4793,7 @@ impl<'c> Translation<'c> {
47994793
}
48004794

48014795
// Transmute the number `0` into the enum type
4802-
CDeclKind::Enum { .. } => self.convert_enum_zero_initializer(type_id),
4796+
CDeclKind::Enum { .. } => self.convert_enum_zero_initializer(decl_id),
48034797

48044798
_ => {
48054799
return Err(TranslationError::generic(
@@ -4920,7 +4914,7 @@ impl<'c> Translation<'c> {
49204914
}
49214915

49224916
let val = if ty.is_enum() {
4923-
mk().cast_expr(val, mk().path_ty(vec!["u64"]))
4917+
mk().anon_field_expr(val, 0)
49244918
} else {
49254919
val
49264920
};

c2rust-transpile/src/translator/operators.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,12 @@ impl<'c> Translation<'c> {
221221
} else {
222222
let compute_lhs_resolved_ty = &self.ast_context.resolve_type(compute_lhs_type_id.ctype);
223223
let lhs_type = self.convert_type(compute_lhs_type_id.ctype)?;
224+
let resolve_lhs_kind = &self.ast_context.resolve_type(lhs_type_id.ctype).kind;
224225

225-
// We can't simply as-cast into a non primitive like f128
226-
let lhs = if compute_lhs_resolved_ty.kind == CTypeKind::LongDouble {
226+
let lhs = if let CTypeKind::Enum(..) = resolve_lhs_kind {
227+
self.convert_cast_from_enum(compute_lhs_type_id.ctype, read)?
228+
} else if compute_lhs_resolved_ty.kind == CTypeKind::LongDouble {
229+
// We can't simply as-cast into a non primitive like f128
227230
self.use_crate(ExternCrate::F128);
228231

229232
let fn_path = mk().abs_path_expr(vec!["f128", "f128", "from"]);
@@ -245,11 +248,9 @@ impl<'c> Translation<'c> {
245248
None,
246249
)?;
247250

248-
let resolve_lhs_kind = &self.ast_context.resolve_type(lhs_type_id.ctype).kind;
249-
250251
val = if let &CTypeKind::Enum(enum_id) = resolve_lhs_kind {
251252
val.result_map(|val| {
252-
self.convert_cast_to_enum(ctx, lhs_type_id.ctype, enum_id, None, val)
253+
self.convert_cast_to_enum(ctx, enum_id, None, compute_lhs_type_id.ctype, val)
253254
})?
254255
} else if compute_lhs_resolved_ty.kind == CTypeKind::LongDouble {
255256
// We can't as-cast from a non primitive like f128 back to the result_type
@@ -487,9 +488,9 @@ impl<'c> Translation<'c> {
487488
val = if let &CTypeKind::Enum(enum_id) = expr_resolved_ty_kind {
488489
val.result_map(|val| self.convert_cast_to_enum(
489490
ctx,
490-
expr_type_id.ctype,
491491
enum_id,
492492
None,
493+
expr_type_id.ctype,
493494
val,
494495
))?
495496
} else {

c2rust-transpile/src/translator/pointers.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,10 @@ impl<'c> Translation<'c> {
265265
let lhs_node_type = lhs_node
266266
.get_type()
267267
.ok_or_else(|| format_err!("lhs node bad type"))?;
268+
let rhs_node_type = rhs_node
269+
.get_type()
270+
.ok_or_else(|| format_err!("rhs node bad type"))?;
271+
268272
if self
269273
.ast_context
270274
.resolve_type(lhs_node_type)
@@ -278,7 +282,12 @@ impl<'c> Translation<'c> {
278282
}
279283

280284
let rhs = self.convert_expr(ctx.used(), rhs, None)?;
281-
rhs.and_then(|rhs| {
285+
rhs.and_then(|mut rhs| {
286+
// C allows enums to index arrays directly without inserting a numeric cast.
287+
if let CTypeKind::Enum(..) = self.ast_context.resolve_type(rhs_node_type).kind {
288+
rhs = self.integer_from_enum(rhs);
289+
}
290+
282291
let simple_index_array = if ctx.needs_address() {
283292
// We can't necessarily index into an array if we're using
284293
// that element to compute an address.

c2rust-transpile/tests/snapshots/snapshots__transpile@exprs.c.snap

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ input_file: c2rust-transpile/tests/snapshots/exprs.c
1515
extern "C" {
1616
fn puts(str: *const ::core::ffi::c_char) -> ::core::ffi::c_int;
1717
}
18-
pub type C2Rust_Unnamed = ::core::ffi::c_uint;
19-
pub const C: C2Rust_Unnamed = 2;
20-
pub const B: C2Rust_Unnamed = 1;
21-
pub const A: C2Rust_Unnamed = 0;
18+
#[derive(Clone, Copy)]
19+
#[repr(transparent)]
20+
pub struct C2Rust_Unnamed(pub ::core::ffi::c_uint);
21+
pub const C: C2Rust_Unnamed = C2Rust_Unnamed(2);
22+
pub const B: C2Rust_Unnamed = C2Rust_Unnamed(1);
23+
pub const A: C2Rust_Unnamed = C2Rust_Unnamed(0);
2224
unsafe extern "C" fn side_effect() -> ::core::ffi::c_int {
2325
puts(b"the return of side effect\0" as *const u8 as *const ::core::ffi::c_char);
2426
return 10 as ::core::ffi::c_int;
@@ -54,7 +56,7 @@ pub unsafe extern "C" fn unary_with_side_effect() {
5456
}
5557
#[no_mangle]
5658
pub unsafe extern "C" fn compound_literal() {
57-
let mut i: ::core::ffi::c_int = B as ::core::ffi::c_int;
59+
let mut i: ::core::ffi::c_int = B.0 as ::core::ffi::c_int;
5860
}
5961
#[no_mangle]
6062
pub unsafe extern "C" fn statement_expr() {

c2rust-transpile/tests/snapshots/snapshots__transpile@macrocase.c.snap

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ input_file: c2rust-transpile/tests/snapshots/macrocase.c
1111
unused_assignments,
1212
unused_mut
1313
)]
14-
pub type ZSTD_dParameter = ::core::ffi::c_uint;
15-
pub const ZSTD_d_experimentalParam1: ZSTD_dParameter = 1000;
16-
pub const ZSTD_d_windowLogMax: ZSTD_dParameter = 100;
14+
#[derive(Clone, Copy)]
15+
#[repr(transparent)]
16+
pub struct ZSTD_dParameter(pub ::core::ffi::c_uint);
17+
pub const ZSTD_d_experimentalParam1: ZSTD_dParameter = ZSTD_dParameter(1000);
18+
pub const ZSTD_d_windowLogMax: ZSTD_dParameter = ZSTD_dParameter(100);
1719
pub const ZSTD_d_format: ::core::ffi::c_uint = 1000 as ::core::ffi::c_uint;
1820
#[no_mangle]
1921
pub unsafe extern "C" fn ZSTD_dParam_getBounds(mut dParam: ZSTD_dParameter) -> ::core::ffi::c_int {
2022
let mut bounds: ::core::ffi::c_int = 0 as ::core::ffi::c_int;
21-
match dParam as ::core::ffi::c_uint {
23+
match dParam.0 as ::core::ffi::c_uint {
2224
100 => {
2325
bounds = 1 as ::core::ffi::c_int;
2426
return bounds;

c2rust-transpile/tests/snapshots/snapshots__transpile@records.c.snap

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ input_file: c2rust-transpile/tests/snapshots/records.c
1414
#[derive(Copy, Clone)]
1515
#[repr(C)]
1616
pub struct AnonEnumInStruct {}
17-
pub type C2Rust_Unnamed = ::core::ffi::c_uint;
18-
pub const VALUE2: C2Rust_Unnamed = 1;
19-
pub const VALUE1: C2Rust_Unnamed = 0;
17+
#[derive(Clone, Copy)]
18+
#[repr(transparent)]
19+
pub struct C2Rust_Unnamed(pub ::core::ffi::c_uint);
20+
pub const VALUE2: C2Rust_Unnamed = C2Rust_Unnamed(1);
21+
pub const VALUE1: C2Rust_Unnamed = C2Rust_Unnamed(0);
2022
#[derive(Copy, Clone)]
2123
#[repr(C)]
2224
pub struct AnonStructInStruct {
@@ -40,9 +42,11 @@ pub struct InsideStruct {
4042
pub union AnonEnumInUnion {
4143
pub a: ::core::ffi::c_int,
4244
}
43-
pub type C2Rust_Unnamed_1 = ::core::ffi::c_uint;
44-
pub const VALUE4: C2Rust_Unnamed_1 = 1;
45-
pub const VALUE3: C2Rust_Unnamed_1 = 0;
45+
#[derive(Clone, Copy)]
46+
#[repr(transparent)]
47+
pub struct C2Rust_Unnamed_1(pub ::core::ffi::c_uint);
48+
pub const VALUE4: C2Rust_Unnamed_1 = C2Rust_Unnamed_1(1);
49+
pub const VALUE3: C2Rust_Unnamed_1 = C2Rust_Unnamed_1(0);
4650
#[derive(Copy, Clone)]
4751
#[repr(C)]
4852
pub union AnonStructInUnion {
@@ -66,7 +70,7 @@ pub struct InsideUnion {
6670
}
6771
#[no_mangle]
6872
pub unsafe extern "C" fn struct_declaration() {
69-
let mut value: ::core::ffi::c_int = VALUE2 as ::core::ffi::c_int;
73+
let mut value: ::core::ffi::c_int = VALUE2.0 as ::core::ffi::c_int;
7074
let mut a: AnonEnumInStruct = AnonEnumInStruct {};
7175
let mut b: AnonStructInStruct = AnonStructInStruct {
7276
c2rust_unnamed: C2Rust_Unnamed_0 { some_number: 0 },
@@ -77,7 +81,7 @@ pub unsafe extern "C" fn struct_declaration() {
7781
}
7882
#[no_mangle]
7983
pub unsafe extern "C" fn union_declaration() {
80-
let mut value: ::core::ffi::c_int = VALUE4 as ::core::ffi::c_int;
84+
let mut value: ::core::ffi::c_int = VALUE4.0 as ::core::ffi::c_int;
8185
let mut a: AnonEnumInUnion = AnonEnumInUnion { a: 0 };
8286
let mut b: AnonStructInUnion = AnonStructInUnion {
8387
c2rust_unnamed: C2Rust_Unnamed_2 { some_number: 0 },

tests/unit/enums/src/test_enums.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ const BUFFER_SIZE6: usize = 1;
3535

3636
#[test]
3737
pub fn test_variants() {
38-
assert_eq!(A as u32, 0);
39-
assert_eq!(B as u32, 1);
38+
assert_eq!(A.0 as u32, 0);
39+
assert_eq!(B.0 as u32, 1);
4040
}
4141

4242
#[test]

tests/unit/misc/src/test_uninitialized.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ pub fn test_buffer() {
2626

2727
#[test]
2828
pub fn test_types() {
29-
assert_eq!(foo as u32, 1);
30-
assert_eq!(bar as u32, 2);
31-
assert_eq!(baz as u32, 3);
29+
assert_eq!(foo.0 as u32, 1);
30+
assert_eq!(bar.0 as u32, 2);
31+
assert_eq!(baz.0 as u32, 3);
3232

3333
// FIXME: union fields are private
3434
// let my_union = u { x: 32 };

0 commit comments

Comments
 (0)