Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for constants defined with if + cfg! macro #1023

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 164 additions & 29 deletions src/bindgen/ir/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ pub enum Literal {
ty: Type,
value: Box<Literal>,
},
PanicMacro(PanicMacroKind),
}

impl Literal {
Expand Down Expand Up @@ -156,6 +157,7 @@ impl Literal {
}
}
Literal::Expr(..) => {}
Literal::PanicMacro(_) => {}
}
}

Expand All @@ -181,6 +183,7 @@ impl Literal {
Literal::FieldAccess { ref base, .. } => base.is_valid(bindings),
Literal::Struct { ref path, .. } => bindings.struct_exists(path),
Literal::Cast { ref value, .. } => value.is_valid(bindings),
Literal::PanicMacro(_) => true,
}
}

Expand Down Expand Up @@ -210,6 +213,7 @@ impl Literal {
true
}
Literal::Cast { ref value, .. } => value.visit(visitor),
Literal::PanicMacro(_) => true,
}
}

Expand Down Expand Up @@ -285,9 +289,89 @@ impl Literal {
ty.rename_for_config(config, &GenericParams::default());
value.rename_for_config(config);
}
Literal::PanicMacro(_) => {}
}
}

// Handles `if` expressions with `cfg!` macros
pub fn load_many(
expr: &syn::Expr,
og_cfg: Option<&Cfg>,
prev_cfgs: &mut Vec<Cfg>,
) -> Result<Vec<(Literal, Option<Cfg>)>, String> {
let mut vec = Vec::new();
match *expr {
syn::Expr::If(syn::ExprIf {
ref cond,
ref then_branch,
ref else_branch,
..
}) => match cond.as_ref() {
syn::Expr::Macro(syn::ExprMacro { mac, .. }) => {
if !mac.path.is_ident("cfg") {
return Err(format!(
"Unsupported if expression with macro {:?}",
mac.path
));
}

let cfg: Cfg =
syn::parse2(mac.tokens.clone()).map_err(|err| err.to_string())?;

let lit = then_branch
.stmts
.last()
.map(|stmt| match stmt {
syn::Stmt::Expr(expr, None) => Literal::load(expr),
syn::Stmt::Macro(stmt_macro) => Literal::from_macro(&stmt_macro.mac),
_ => Err("Unsupported block without expression".to_string()),
})
.ok_or("Unsupported block without expression".to_string())??;

vec.push((lit, Cfg::append(og_cfg, Some(cfg.clone()))));
prev_cfgs.push(cfg);

if let Some((_, else_expr)) = else_branch {
match else_expr.as_ref() {
syn::Expr::Block(expr_block) => {
let lit = expr_block
.block
.stmts
.last()
.map(|stmt| match stmt {
syn::Stmt::Expr(expr, None) => Literal::load(expr),
syn::Stmt::Macro(stmt_macro) => {
Literal::from_macro(&stmt_macro.mac)
}
_ => {
Err("Unsupported block without expression".to_string())
}
})
.ok_or("Unsupported block without expression".to_string())??;
let cfg = Cfg::append(
og_cfg,
Some(Cfg::Not(Box::new(Cfg::Any(prev_cfgs.clone())))),
);
vec.push((lit, cfg));
}
syn::Expr::If(_) => {
vec.extend(Literal::load_many(else_expr, og_cfg, prev_cfgs)?)
}
_ => unreachable!(),
}
}
}
_ => return Err(format!("Unsupported if expression. {:?}", *expr)),
},
_ => {
let lit = Literal::load(expr)?;
let cfg = Cfg::append(og_cfg, None);
vec.push((lit, cfg));
}
}
Ok(vec)
}

// Translate from full blown `syn::Expr` into a simpler `Literal` type
pub fn load(expr: &syn::Expr) -> Result<Literal, String> {
match *expr {
Expand Down Expand Up @@ -476,11 +560,33 @@ impl Literal {
}
}

syn::Expr::Macro(syn::ExprMacro { ref mac, .. }) => Literal::from_macro(mac),
_ => Err(format!("Unsupported expression. {:?}", *expr)),
}
}
}

impl Literal {
fn from_macro(mac: &syn::Macro) -> Result<Literal, String> {
let path = &mac.path;
let tokens = &mac.tokens;
if path.is_ident("todo") {
Ok(Literal::PanicMacro(PanicMacroKind::Todo))
} else if path.is_ident("unreachable") {
Ok(Literal::PanicMacro(PanicMacroKind::Unreachable))
} else if path.is_ident("unimplemented") {
Ok(Literal::PanicMacro(PanicMacroKind::Unimplemented))
} else if path.is_ident("panic") {
// We only support simple messages, i.e. string literals
let msg: syn::LitStr = syn::parse2(tokens.clone())
.map_err(|_| "Unsupported `panic!` with complex message".to_string())?;
Ok(Literal::PanicMacro(PanicMacroKind::Panic(msg.value())))
} else {
Err(format!("Unsupported macro as literal. {:?}", path))
}
}
}

#[derive(Debug, Clone)]
pub struct Constant {
pub path: Path,
Expand All @@ -501,7 +607,7 @@ impl Constant {
expr: &syn::Expr,
attrs: &[syn::Attribute],
associated_to: Option<Path>,
) -> Result<Constant, String> {
) -> Result<Vec<Constant>, String> {
let ty = Type::load(ty)?;
let mut ty = match ty {
Some(ty) => ty,
Expand All @@ -510,22 +616,34 @@ impl Constant {
}
};

let mut lit = Literal::load(expr)?;

if let Some(ref associated_to) = associated_to {
ty.replace_self_with(associated_to);
lit.replace_self_with(associated_to);
}

Ok(Constant::new(
path,
ty,
lit,
Cfg::append(mod_cfg, Cfg::load(attrs)),
AnnotationSet::load(attrs)?,
Documentation::load(attrs),
associated_to,
))
let og_cfg = Cfg::append(mod_cfg, Cfg::load(attrs));

let mut prev_cfgs = Vec::new();
let lits = Literal::load_many(expr, og_cfg.as_ref(), &mut prev_cfgs)?;

let mut consts = Vec::with_capacity(lits.len());

for (mut lit, cfg) in lits {
if let Some(ref associated_to) = associated_to {
lit.replace_self_with(associated_to);
}

consts.push(Constant::new(
path.clone(),
ty.clone(),
lit,
cfg,
AnnotationSet::load(attrs)?,
Documentation::load(attrs),
associated_to.clone(),
));
}

Ok(consts)
}

pub fn new(
Expand Down Expand Up @@ -690,27 +808,36 @@ impl Constant {
let allow_constexpr = config.constant.allow_constexpr && self.value.can_be_constexpr();
match config.language {
Language::Cxx if config.constant.allow_static_const || allow_constexpr => {
if allow_constexpr {
out.write("constexpr ")
}
if matches!(self.value, Literal::PanicMacro(_)) {
write!(out, "#error ");
language_backend.write_literal(out, value);
} else {
if allow_constexpr {
out.write("constexpr ")
}

if config.constant.allow_static_const {
out.write(if in_body { "inline " } else { "static " });
}
if config.constant.allow_static_const {
out.write(if in_body { "inline " } else { "static " });
}

if let Type::Ptr { is_const: true, .. } = self.ty {
// Nothing.
} else {
out.write("const ");
}
if let Type::Ptr { is_const: true, .. } = self.ty {
// Nothing.
} else {
out.write("const ");
}

language_backend.write_type(out, &self.ty);
write!(out, " {} = ", name);
language_backend.write_literal(out, value);
write!(out, ";");
language_backend.write_type(out, &self.ty);
write!(out, " {} = ", name);
language_backend.write_literal(out, value);
write!(out, ";");
}
}
Language::Cxx | Language::C => {
write!(out, "#define {} ", name);
if matches!(self.value, Literal::PanicMacro(_)) {
write!(out, "#error ");
} else {
write!(out, "#define {} ", name);
}
language_backend.write_literal(out, value);
}
Language::Cython => {
Expand All @@ -726,3 +853,11 @@ impl Constant {
condition.write_after(config, out);
}
}

#[derive(Debug, Clone)]
pub enum PanicMacroKind {
Todo,
Unreachable,
Unimplemented,
Panic(String),
}
13 changes: 11 additions & 2 deletions src/bindgen/language_backend/clike.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::bindgen::ir::{
to_known_assoc_constant, ConditionWrite, DeprecatedNoteKind, Documentation, Enum, EnumVariant,
Field, GenericParams, Item, Literal, OpaqueItem, ReprAlign, Static, Struct, ToCondition, Type,
Typedef, Union,
Field, GenericParams, Item, Literal, OpaqueItem, PanicMacroKind, ReprAlign, Static, Struct,
ToCondition, Type, Typedef, Union,
};
use crate::bindgen::language_backend::LanguageBackend;
use crate::bindgen::rename::IdentifierType;
Expand Down Expand Up @@ -921,6 +921,15 @@ impl LanguageBackend for CLikeLanguageBackend<'_> {
}
write!(out, " }}");
}
Literal::PanicMacro(panic_macro_kind) => {
match panic_macro_kind {
PanicMacroKind::Todo => write!(out, r#""not yet implemented""#),
PanicMacroKind::Unreachable => write!(out, r#""reached unreachable code""#),
PanicMacroKind::Unimplemented => write!(out, r#""not implemented""#),
// Debug print of &str and String already add the `"`
PanicMacroKind::Panic(msg) => write!(out, "{:?}", msg),
}
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/bindgen/language_backend/cython.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,9 @@ impl LanguageBackend for CythonLanguageBackend<'_> {
}
write!(out, " }}");
}
Literal::PanicMacro(_) => {
warn!("SKIP: Not implemented")
}
}
}

Expand Down
40 changes: 22 additions & 18 deletions src/bindgen/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -807,20 +807,23 @@ impl Parse {
&item.attrs,
Some(impl_path.clone()),
) {
Ok(constant) => {
Ok(constants) => {
info!("Take {}::{}::{}.", crate_name, impl_path, &item.ident);
let mut any = false;
self.structs.for_items_mut(&impl_path, |item| {
any = true;
item.add_associated_constant(constant.clone());
});
// Handle associated constants to other item types that are
// not structs like enums or such as regular constants.
if !any && !self.constants.try_insert(constant) {
error!(
"Conflicting name for constant {}::{}::{}.",
crate_name, impl_path, &item.ident,
);

for constant in constants {
let mut any = false;
self.structs.for_items_mut(&impl_path, |item| {
any = true;
item.add_associated_constant(constant.clone());
});
// Handle associated constants to other item types that are
// not structs like enums or such as regular constants.
if !any && !self.constants.try_insert(constant) {
error!(
"Conflicting name for constant {}::{}::{}.",
crate_name, impl_path, &item.ident,
);
}
}
}
Err(msg) => {
Expand Down Expand Up @@ -858,12 +861,13 @@ impl Parse {

let path = Path::new(item.ident.unraw().to_string());
match Constant::load(path, mod_cfg, &item.ty, &item.expr, &item.attrs, None) {
Ok(constant) => {
Ok(constants) => {
info!("Take {}::{}.", crate_name, &item.ident);

let full_name = constant.path.clone();
if !self.constants.try_insert(constant) {
error!("Conflicting name for constant {}", full_name);
for constant in constants {
let full_name = constant.path.clone();
if !self.constants.try_insert(constant) {
error!("Conflicting name for constant {}", full_name);
}
}
}
Err(msg) => {
Expand Down
Loading
Loading