Skip to content
2 changes: 1 addition & 1 deletion c2rust-transpile/src/translator/literals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ impl<'c> Translation<'c> {
) -> TranslationResult<WithStmts<Box<Expr>>> {
// C compound literals are lvalues, but equivalent Rust expressions generally are not.
// So if an address is needed, store it in an intermediate variable first.
if !ctx.needs_address() || ctx.expanding_macro.is_some() {
if !ctx.needs_address() || ctx.converting_macro.is_some() {
return self.convert_expr(ctx, val, override_ty);
}

Expand Down
176 changes: 114 additions & 62 deletions c2rust-transpile/src/translator/macros.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use c2rust_ast_builder::mk;
use failure::format_err;
use indexmap::IndexMap;
use log::{info, trace};
use proc_macro2::{Span, TokenStream};
use syn::{Expr, MacroDelimiter};

use crate::c_ast::{CDeclId, CExprId, CQualTypeId, CTypeId, CTypeKind};
use crate::c_ast::{CDeclId, CExprId, CQualTypeId, CTypeKind};
use crate::diagnostics::{TranslationError, TranslationResult};
use crate::translator::{ConvertedDecl, ExprContext, MacroExpansion, Translation};
use crate::translator::{ConvertedDecl, ConvertedMacro, ExprContext, Translation};
use crate::with_stmts::WithStmts;
use crate::TranslateMacros;

Expand All @@ -25,29 +26,37 @@ impl<'c> Translation<'c> {
);

let maybe_replacement = self.recreate_const_macro_from_expansions(
ctx.const_().set_expanding_macro(decl_id),
ctx.const_().set_converting_macro(decl_id),
&self.ast_context.macro_expansions[&decl_id],
);

match maybe_replacement {
Ok((replacement, ty)) => {
Ok((replacement, converted)) => {
trace!(" to {:?}", replacement);

let expansion = MacroExpansion { ty };
self.macro_expansions
for (expr_id, result) in &converted.expr_results {
if let Err(err) = result {
info!(
"Could not convert macro {} for {:?}: {}",
name, expr_id, err
);
}
}

let result_type_rs = self.convert_type(converted.result_type_id)?;
self.converted_macros
.borrow_mut()
.insert(decl_id, Some(expansion));
let ty = self.convert_type(ty)?;
.insert(decl_id, Some(converted));

Ok(ConvertedDecl::Item(mk().span(span).pub_().const_item(
name,
ty,
result_type_rs,
replacement,
)))
}
Err(e) => {
self.macro_expansions.borrow_mut().insert(decl_id, None);
info!("Could not expand macro {}: {}", name, e);
self.converted_macros.borrow_mut().insert(decl_id, None);
info!("Could not convert macro {}: {}", name, e);
Ok(ConvertedDecl::NoItem)
}
}
Expand All @@ -68,43 +77,77 @@ impl<'c> Translation<'c> {
&self,
ctx: ExprContext,
expansions: &[CExprId],
) -> TranslationResult<(Box<Expr>, CTypeId)> {
let (val, ty) = expansions
) -> TranslationResult<(Box<Expr>, ConvertedMacro)> {
let mut canonical = None;
let mut expr_results: IndexMap<_, _> = expansions
.iter()
.try_fold::<Option<(WithStmts<Box<Expr>>, CTypeId)>, _, _>(None, |canonical, &id| {
self.can_convert_const_macro_expansion(id)?;

let ty = self.ast_context[id]
.kind
.get_type()
.ok_or_else(|| format_err!("Invalid expression type"))?;
let expr = self.convert_expr(ctx, id, None)?;

// Join ty and cur_ty to the smaller of the two types. If the
// types are not cast-compatible, abort the fold.
let ty_kind = self.ast_context.resolve_type(ty).kind.clone();
if let Some((canon_val, canon_ty)) = canonical {
let canon_ty_kind = self.ast_context.resolve_type(canon_ty).kind.clone();
if let Some(smaller_ty) =
CTypeKind::smaller_compatible_type(canon_ty_kind.clone(), ty_kind)
{
if smaller_ty == canon_ty_kind {
Ok(Some((canon_val, canon_ty)))
} else {
Ok(Some((expr, ty)))
.map(|&expr_id| {
let result = self
.can_convert_const_macro_expansion(expr_id)
.and_then(|_| {
let type_id = self.ast_context[expr_id]
.kind
.get_type()
.ok_or_else(|| format_err!("Invalid expression type"))?;
let val = self.convert_expr(ctx, expr_id, None)?;
Ok((val, type_id))
})
.and_then(|new| {
// Join ty and cur_ty to the smaller of the two types. If the
// types are not cast-compatible, skip this expansion.
match &mut canonical {
Some(canonical) => {
let &mut (_, canon_type_id) = canonical;
let (_, new_type_id) = new;

let canon_type_kind =
self.ast_context.resolve_type(canon_type_id).kind.clone();
let new_type_kind =
self.ast_context.resolve_type(new_type_id).kind.clone();
let smaller_type_kind = CTypeKind::smaller_compatible_type(
canon_type_kind.clone(),
new_type_kind,
);
let Some(smaller_type_kind) = smaller_type_kind else {
return Err(
format_err!(
"Not all macro expansions are compatible types"
)
.into()
)
};

if smaller_type_kind != canon_type_kind {
*canonical = new;
}
}

None => canonical = Some(new),
}
} else {
Err(format_err!("Not all macro expansions are compatible types"))
}
} else {
Ok(Some((expr, ty)))
}
})?
.ok_or_else(|| format_err!("Could not find a valid type for macro"))?;

Ok(())
});

(expr_id, result)
})
.collect();

let Some((val, result_type_id)) = canonical else {
// If none of the expansions could be converted, try returning the first error we got.
let err = match expr_results.swap_remove_index(0) {
Some((_, Err(err))) => err,
_ => format_err!("Could not find a valid type for macro").into(),
};
return Err(err);
};

let converted = ConvertedMacro {
result_type_id,
expr_results,
};
val.wrap_unsafe()
.to_pure_expr()
.map(|val| (val, ty))
.map(|val| (val, converted))
.ok_or_else(|| TranslationError::generic("Macro expansion is not a pure expression"))

// TODO: Validate that all replacements are equivalent and pick the most
Expand Down Expand Up @@ -157,35 +200,44 @@ impl<'c> Translation<'c> {

// Find the first macro after the macro we're currently expanding, if any.
let first_macro = macros
.splitn(2, |macro_id| ctx.expanding_macro(macro_id))
.last()
.unwrap()
.iter()
.position(|macro_id| ctx.is_converting_macro(macro_id))
.map_or(macros, |index| &macros[index + 1..])
.first();
let macro_id = match first_macro {
Some(macro_id) => macro_id,
None => return Ok(None),
};

trace!(" found macro expansion: {macro_id:?}");
// Ensure that we've converted this macro and that it has a valid definition.
let expansion = self.macro_expansions.borrow().get(macro_id).cloned();
let macro_ty = match expansion {
// Expansion exists.
Some(Some(expansion)) => expansion.ty,

// Expansion wasn't possible.
Some(None) => return Ok(None),
use std::cell::Ref;
let get_converted = || -> Option<Option<Ref<'_, ConvertedMacro>>> {
Ref::filter_map(self.converted_macros.borrow(), |converted_macros| {
converted_macros.get(macro_id)
})
.ok()
.map(|val| Ref::filter_map(val, Option::as_ref).ok())
};

// Ensure that we've converted this macro and that it has a valid definition.
let mut converted = get_converted();
if converted.is_none() {
// We haven't tried to expand it yet.
None => {
self.convert_decl(ctx, *macro_id)?;
if let Some(Some(expansion)) = self.macro_expansions.borrow().get(macro_id) {
expansion.ty
} else {
return Ok(None);
}
}
self.convert_decl(ctx, *macro_id)?;
converted = get_converted();
}
let Some(Some(converted)) = converted else {
// Expansion wasn't possible.
return Ok(None);
};

if !matches!(converted.expr_results.get(&expr_id), Some(Ok(_))) {
// Expansion succeeded in general, but not for this particular expression.
return Ok(None);
}

let result_type_id = converted.result_type_id;
let rust_name = self
.renamer
.borrow_mut()
Expand All @@ -206,7 +258,7 @@ impl<'c> Translation<'c> {
if let Some(expr_ty) = expr_ty {
self.convert_cast(
ctx,
CQualTypeId::new(macro_ty),
CQualTypeId::new(result_type_id),
expr_ty,
val,
None,
Expand Down
28 changes: 14 additions & 14 deletions c2rust-transpile/src/translator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ pub struct ExprContext {
needs_address: bool,

ternary_needs_parens: bool,
expanding_macro: Option<CDeclId>,
converting_macro: Option<CDeclId>,
}

impl ExprContext {
Expand Down Expand Up @@ -212,15 +212,15 @@ impl ExprContext {
}

/// Are we expanding the given macro in the current context?
pub fn expanding_macro(&self, mac: &CDeclId) -> bool {
match self.expanding_macro {
pub fn is_converting_macro(&self, mac: &CDeclId) -> bool {
match self.converting_macro {
Some(expanding) => expanding == *mac,
None => false,
}
}
pub fn set_expanding_macro(self, mac: CDeclId) -> Self {
pub fn set_converting_macro(self, mac: CDeclId) -> Self {
ExprContext {
expanding_macro: Some(mac),
converting_macro: Some(mac),
..self
}
}
Expand Down Expand Up @@ -260,9 +260,9 @@ impl FuncContext {
}
}

#[derive(Clone)]
struct MacroExpansion {
ty: CTypeId,
struct ConvertedMacro {
result_type_id: CTypeId,
expr_results: IndexMap<CExprId, TranslationResult<()>>,
}

type ZeroInits = IndexMap<CDeclId, (WithStmts<Box<Expr>>, IndexSet<Import>)>;
Expand All @@ -283,7 +283,7 @@ pub struct Translation<'c> {
zero_inits: RefCell<ZeroInits>,
function_context: RefCell<FuncContext>,
potential_flexible_array_members: RefCell<IndexSet<CDeclId>>,
macro_expansions: RefCell<IndexMap<CDeclId, Option<MacroExpansion>>>,
converted_macros: RefCell<IndexMap<CDeclId, Option<ConvertedMacro>>>,
/// Sets of imports deferred while translating nested expressions for caching. Imports are
/// deferred when caching translations to make them pure and thus cache the translation
/// alongside its required imports. Each additional nested level of caching translation
Expand Down Expand Up @@ -694,7 +694,7 @@ pub fn translate(
is_bitfield_write: false,
needs_address: false,
ternary_needs_parens: false,
expanding_macro: None,
converting_macro: None,
};

{
Expand Down Expand Up @@ -1508,7 +1508,7 @@ impl<'c> Translation<'c> {
zero_inits: RefCell::new(IndexMap::new()),
function_context: RefCell::new(FuncContext::new()),
potential_flexible_array_members: RefCell::new(IndexSet::new()),
macro_expansions: RefCell::new(IndexMap::new()),
converted_macros: RefCell::new(IndexMap::new()),
deferred_imports: RefCell::new(Vec::new()),
comment_context,
comment_store: RefCell::new(CommentStore::new()),
Expand Down Expand Up @@ -3008,7 +3008,7 @@ impl<'c> Translation<'c> {
.get_decl(&decl_id)
.ok_or_else(|| format_err!("Missing declref {:?}", decl_id))?
.kind;
if ctx.expanding_macro.is_some() {
if ctx.converting_macro.is_some() {
// TODO Determining which declarations have been declared within the scope of the const macro expr
// vs. which are out-of-scope of the const macro is non-trivial,
// so for now, we don't allow const macros referencing any declarations.
Expand Down Expand Up @@ -4324,8 +4324,8 @@ impl<'c> Translation<'c> {
} => add_use_items_for_type(typ),

CDeclKind::MacroObject { .. } => {
if let Some(Some(expansion)) = self.macro_expansions.borrow().get(&decl_id) {
add_use_items_for_type(expansion.ty)
if let Some(Some(converted)) = self.converted_macros.borrow().get(&decl_id) {
add_use_items_for_type(converted.result_type_id)
}
}

Expand Down
Loading