diff options
Diffstat (limited to 'src/bindgen/ir/constant.rs')
-rw-r--r-- | src/bindgen/ir/constant.rs | 813 |
1 files changed, 813 insertions, 0 deletions
diff --git a/src/bindgen/ir/constant.rs b/src/bindgen/ir/constant.rs new file mode 100644 index 0000000..d3b9bd4 --- /dev/null +++ b/src/bindgen/ir/constant.rs @@ -0,0 +1,813 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::borrow::Cow; +use std::collections::HashMap; +use std::io::Write; + +use syn::ext::IdentExt; +use syn::{self, UnOp}; + +use crate::bindgen::config::{Config, Language}; +use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver; +use crate::bindgen::dependencies::Dependencies; +use crate::bindgen::ir::{ + AnnotationSet, Cfg, ConditionWrite, Documentation, GenericParams, Item, ItemContainer, Path, + Struct, ToCondition, Type, +}; +use crate::bindgen::library::Library; +use crate::bindgen::writer::{Source, SourceWriter}; +use crate::bindgen::Bindings; + +fn member_to_ident(member: &syn::Member) -> String { + match member { + syn::Member::Named(ref name) => name.unraw().to_string(), + syn::Member::Unnamed(ref index) => format!("_{}", index.index), + } +} + +// TODO: Maybe add support to more std associated constants. +fn to_known_assoc_constant(associated_to: &Path, name: &str) -> Option<String> { + use crate::bindgen::ir::{IntKind, PrimitiveType}; + + if name != "MAX" && name != "MIN" { + return None; + } + + let prim = PrimitiveType::maybe(associated_to.name())?; + let prefix = match prim { + PrimitiveType::Integer { + kind, + signed, + zeroable: _, + } => match kind { + IntKind::B8 => { + if signed { + "INT8" + } else { + "UINT8" + } + } + IntKind::B16 => { + if signed { + "INT16" + } else { + "UINT16" + } + } + IntKind::B32 => { + if signed { + "INT32" + } else { + "UINT32" + } + } + IntKind::B64 => { + if signed { + "INT64" + } else { + "UINT64" + } + } + _ => return None, + }, + _ => return None, + }; + Some(format!("{}_{}", prefix, name)) +} + +#[derive(Debug, Clone)] +pub enum Literal { + Expr(String), + Path { + associated_to: Option<(Path, String)>, + name: String, + }, + PostfixUnaryOp { + op: &'static str, + value: Box<Literal>, + }, + BinOp { + left: Box<Literal>, + op: &'static str, + right: Box<Literal>, + }, + FieldAccess { + base: Box<Literal>, + field: String, + }, + Struct { + path: Path, + export_name: String, + fields: HashMap<String, Literal>, + }, + Cast { + ty: Type, + value: Box<Literal>, + }, +} + +impl Literal { + fn replace_self_with(&mut self, self_ty: &Path) { + match *self { + Literal::PostfixUnaryOp { ref mut value, .. } => { + value.replace_self_with(self_ty); + } + Literal::BinOp { + ref mut left, + ref mut right, + .. + } => { + left.replace_self_with(self_ty); + right.replace_self_with(self_ty); + } + Literal::FieldAccess { ref mut base, .. } => { + base.replace_self_with(self_ty); + } + Literal::Struct { + ref mut path, + ref mut export_name, + ref mut fields, + } => { + if path.replace_self_with(self_ty) { + *export_name = self_ty.name().to_owned(); + } + for ref mut expr in fields.values_mut() { + expr.replace_self_with(self_ty); + } + } + Literal::Cast { + ref mut ty, + ref mut value, + } => { + ty.replace_self_with(self_ty); + value.replace_self_with(self_ty); + } + Literal::Path { + ref mut associated_to, + .. + } => { + if let Some((ref mut path, ref mut export_name)) = *associated_to { + if path.replace_self_with(self_ty) { + *export_name = self_ty.name().to_owned(); + } + } + } + Literal::Expr(..) => {} + } + } + + fn is_valid(&self, bindings: &Bindings) -> bool { + match *self { + Literal::Expr(..) => true, + Literal::Path { + ref associated_to, + ref name, + } => { + if let Some((ref path, _export_name)) = associated_to { + return bindings.struct_exists(path) + || to_known_assoc_constant(path, name).is_some(); + } + true + } + Literal::PostfixUnaryOp { ref value, .. } => value.is_valid(bindings), + Literal::BinOp { + ref left, + ref right, + .. + } => left.is_valid(bindings) && right.is_valid(bindings), + Literal::FieldAccess { ref base, .. } => base.is_valid(bindings), + Literal::Struct { ref path, .. } => bindings.struct_exists(path), + Literal::Cast { ref value, .. } => value.is_valid(bindings), + } + } + + fn can_be_constexpr(&self) -> bool { + !self.has_pointer_casts() + } + + fn visit(&self, visitor: &mut impl FnMut(&Self) -> bool) -> bool { + if !visitor(self) { + return false; + } + match self { + Literal::Expr(..) | Literal::Path { .. } => true, + Literal::PostfixUnaryOp { ref value, .. } => value.visit(visitor), + Literal::BinOp { + ref left, + ref right, + .. + } => left.visit(visitor) && right.visit(visitor), + Literal::FieldAccess { ref base, .. } => base.visit(visitor), + Literal::Struct { ref fields, .. } => { + for (_name, field) in fields.iter() { + if !field.visit(visitor) { + return false; + } + } + true + } + Literal::Cast { ref value, .. } => value.visit(visitor), + } + } + + fn has_pointer_casts(&self) -> bool { + let mut has_pointer_casts = false; + self.visit(&mut |lit| { + if let Literal::Cast { ref ty, .. } = *lit { + has_pointer_casts = has_pointer_casts || ty.is_ptr(); + } + !has_pointer_casts + }); + has_pointer_casts + } + + pub fn uses_only_primitive_types(&self) -> bool { + let mut uses_only_primitive_types = true; + self.visit(&mut |lit| { + // XXX This is a bit sketchy, but alas. + uses_only_primitive_types = uses_only_primitive_types + && match *lit { + Literal::Struct { .. } => false, + Literal::Cast { ref ty, .. } => ty.is_primitive_or_ptr_primitive(), + _ => true, + }; + uses_only_primitive_types + }); + uses_only_primitive_types + } +} + +impl Literal { + pub fn rename_for_config(&mut self, config: &Config) { + match self { + Literal::Struct { + ref mut export_name, + fields, + .. + } => { + config.export.rename(export_name); + for lit in fields.values_mut() { + lit.rename_for_config(config); + } + } + Literal::FieldAccess { ref mut base, .. } => { + base.rename_for_config(config); + } + Literal::Path { + ref mut associated_to, + ref mut name, + } => { + if let Some((_path, ref mut export_name)) = associated_to { + config.export.rename(export_name); + } else { + config.export.rename(name); + } + } + Literal::PostfixUnaryOp { ref mut value, .. } => { + value.rename_for_config(config); + } + Literal::BinOp { + ref mut left, + ref mut right, + .. + } => { + left.rename_for_config(config); + right.rename_for_config(config); + } + Literal::Expr(_) => {} + Literal::Cast { + ref mut ty, + ref mut value, + } => { + ty.rename_for_config(config, &GenericParams::default()); + value.rename_for_config(config); + } + } + } + + // Translate from full blown `syn::Expr` into a simpler `Literal` type + pub fn load(expr: &syn::Expr) -> Result<Literal, String> { + match *expr { + // Match binary expressions of the form `a * b` + syn::Expr::Binary(ref bin_expr) => { + let l = Self::load(&bin_expr.left)?; + let r = Self::load(&bin_expr.right)?; + let op = match bin_expr.op { + syn::BinOp::Add(..) => "+", + syn::BinOp::Sub(..) => "-", + syn::BinOp::Mul(..) => "*", + syn::BinOp::Div(..) => "/", + syn::BinOp::Rem(..) => "%", + syn::BinOp::And(..) => "&&", + syn::BinOp::Or(..) => "||", + syn::BinOp::BitXor(..) => "^", + syn::BinOp::BitAnd(..) => "&", + syn::BinOp::BitOr(..) => "|", + syn::BinOp::Shl(..) => "<<", + syn::BinOp::Shr(..) => ">>", + syn::BinOp::Eq(..) => "==", + syn::BinOp::Lt(..) => "<", + syn::BinOp::Le(..) => "<=", + syn::BinOp::Ne(..) => "!=", + syn::BinOp::Ge(..) => ">=", + syn::BinOp::Gt(..) => ">", + syn::BinOp::AddEq(..) => "+=", + syn::BinOp::SubEq(..) => "-=", + syn::BinOp::MulEq(..) => "*=", + syn::BinOp::DivEq(..) => "/=", + syn::BinOp::RemEq(..) => "%=", + syn::BinOp::BitXorEq(..) => "^=", + syn::BinOp::BitAndEq(..) => "&=", + syn::BinOp::BitOrEq(..) => "|=", + syn::BinOp::ShlEq(..) => "<<=", + syn::BinOp::ShrEq(..) => ">>=", + }; + Ok(Literal::BinOp { + left: Box::new(l), + op, + right: Box::new(r), + }) + } + + // Match literals like true, 'a', 32 etc + syn::Expr::Lit(syn::ExprLit { ref lit, .. }) => { + match lit { + syn::Lit::Byte(ref value) => Ok(Literal::Expr(format!("{}", value.value()))), + syn::Lit::Char(ref value) => Ok(Literal::Expr(match value.value() as u32 { + 0..=255 => format!("'{}'", value.value().escape_default()), + other_code => format!(r"U'\U{:08X}'", other_code), + })), + syn::Lit::Int(ref value) => { + let suffix = match value.suffix() { + "u64" => "ull", + "i64" => "ll", + "u32" => "u", + _ if value.base10_parse::<i64>().is_err() => "ull", + _ => "", + }; + Ok(Literal::Expr(format!( + "{}{}", + value.base10_digits(), + suffix + ))) + } + syn::Lit::Float(ref value) => { + Ok(Literal::Expr(value.base10_digits().to_string())) + } + syn::Lit::Bool(ref value) => Ok(Literal::Expr(format!("{}", value.value))), + // TODO: Add support for byte string and Verbatim + _ => Err(format!("Unsupported literal expression. {:?}", *lit)), + } + } + + syn::Expr::Field(syn::ExprField { + ref base, + ref member, + .. + }) => Ok(Literal::FieldAccess { + base: Box::new(Literal::load(base)?), + field: member_to_ident(member), + }), + + syn::Expr::Call(syn::ExprCall { + ref func, ref args, .. + }) => { + let struct_name = match Literal::load(func)? { + Literal::Path { + associated_to: None, + name, + } => name, + _ => return Err(format!("Unsupported call expression. {:?}", *expr)), + }; + let mut fields = HashMap::<String, Literal>::default(); + for (index, arg) in args.iter().enumerate() { + let ident = + member_to_ident(&syn::Member::Unnamed(syn::Index::from(index))).to_string(); + let value = Literal::load(arg)?; + fields.insert(ident, value); + } + Ok(Literal::Struct { + path: Path::new(struct_name.clone()), + export_name: struct_name, + fields, + }) + } + + syn::Expr::Struct(syn::ExprStruct { + ref path, + ref fields, + .. + }) => { + let struct_name = path.segments[0].ident.unraw().to_string(); + let mut field_map = HashMap::<String, Literal>::default(); + for field in fields { + let ident = member_to_ident(&field.member).to_string(); + let value = Literal::load(&field.expr)?; + field_map.insert(ident, value); + } + Ok(Literal::Struct { + path: Path::new(struct_name.clone()), + export_name: struct_name, + fields: field_map, + }) + } + + syn::Expr::Unary(syn::ExprUnary { + ref op, ref expr, .. + }) => match *op { + UnOp::Not(_) => { + let val = Self::load(expr)?; + Ok(Literal::PostfixUnaryOp { + op: "~", + value: Box::new(val), + }) + } + UnOp::Neg(_) => { + let val = Self::load(expr)?; + Ok(Literal::PostfixUnaryOp { + op: "-", + value: Box::new(val), + }) + } + _ => Err(format!("Unsupported Unary expression. {:?}", *op)), + }, + + // Match identifiers, like `5 << SHIFT` + syn::Expr::Path(syn::ExprPath { ref path, .. }) => { + // Handle only the simplest identifiers and Associated::IDENT + // kind of syntax. + Ok(match path.segments.len() { + 1 => Literal::Path { + associated_to: None, + name: path.segments[0].ident.to_string(), + }, + 2 => { + let struct_name = path.segments[0].ident.to_string(); + Literal::Path { + associated_to: Some((Path::new(&struct_name), struct_name)), + name: path.segments[1].ident.to_string(), + } + } + _ => return Err(format!("Unsupported path expression. {:?}", path)), + }) + } + + syn::Expr::Paren(syn::ExprParen { ref expr, .. }) => Self::load(expr), + + syn::Expr::Cast(syn::ExprCast { + ref expr, ref ty, .. + }) => { + let val = Self::load(expr)?; + match Type::load(ty)? { + Some(ty) => Ok(Literal::Cast { + ty, + value: Box::new(val), + }), + None => Err("Cannot cast to zero sized type.".to_owned()), + } + } + + _ => Err(format!("Unsupported expression. {:?}", *expr)), + } + } + + pub(crate) fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) { + match self { + Literal::Expr(v) => match (&**v, config.language) { + ("true", Language::Cython) => write!(out, "True"), + ("false", Language::Cython) => write!(out, "False"), + (v, _) => write!(out, "{}", v), + }, + Literal::Path { + ref associated_to, + ref name, + } => { + if let Some((ref path, ref export_name)) = associated_to { + if let Some(known) = to_known_assoc_constant(path, name) { + return write!(out, "{}", known); + } + let path_separator = match config.language { + Language::Cython | Language::C => "_", + Language::Cxx => { + if config.structure.associated_constants_in_body { + "::" + } else { + "_" + } + } + }; + write!(out, "{}{}", export_name, path_separator) + } + write!(out, "{}", name) + } + Literal::FieldAccess { + ref base, + ref field, + } => { + write!(out, "("); + base.write(config, out); + write!(out, ").{}", field); + } + Literal::PostfixUnaryOp { op, ref value } => { + write!(out, "{}", op); + value.write(config, out); + } + Literal::BinOp { + ref left, + op, + ref right, + } => { + write!(out, "("); + left.write(config, out); + write!(out, " {} ", op); + right.write(config, out); + write!(out, ")"); + } + Literal::Cast { ref ty, ref value } => { + out.write(if config.language == Language::Cython { + "<" + } else { + "(" + }); + ty.write(config, out); + out.write(if config.language == Language::Cython { + ">" + } else { + ")" + }); + value.write(config, out); + } + Literal::Struct { + export_name, + fields, + path, + } => { + match config.language { + Language::C => write!(out, "({})", export_name), + Language::Cxx => write!(out, "{}", export_name), + Language::Cython => write!(out, "<{}>", export_name), + } + + write!(out, "{{ "); + let mut is_first_field = true; + // In C++, same order as defined is required. + let ordered_fields = out.bindings().struct_field_names(path); + for ordered_key in ordered_fields.iter() { + if let Some(lit) = fields.get(ordered_key) { + if !is_first_field { + write!(out, ", "); + } else { + is_first_field = false; + } + match config.language { + Language::Cxx => write!(out, "/* .{} = */ ", ordered_key), + Language::C => write!(out, ".{} = ", ordered_key), + Language::Cython => {} + } + lit.write(config, out); + } + } + write!(out, " }}"); + } + } + } +} + +#[derive(Debug, Clone)] +pub struct Constant { + pub path: Path, + pub export_name: String, + pub ty: Type, + pub value: Literal, + pub cfg: Option<Cfg>, + pub annotations: AnnotationSet, + pub documentation: Documentation, + pub associated_to: Option<Path>, +} + +impl Constant { + pub fn load( + path: Path, + mod_cfg: Option<&Cfg>, + ty: &syn::Type, + expr: &syn::Expr, + attrs: &[syn::Attribute], + associated_to: Option<Path>, + ) -> Result<Constant, String> { + let ty = Type::load(ty)?; + let mut ty = match ty { + Some(ty) => ty, + None => { + return Err("Cannot have a zero sized const definition.".to_owned()); + } + }; + + 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, + )) + } + + pub fn new( + path: Path, + ty: Type, + value: Literal, + cfg: Option<Cfg>, + annotations: AnnotationSet, + documentation: Documentation, + associated_to: Option<Path>, + ) -> Self { + let export_name = path.name().to_owned(); + Self { + path, + export_name, + ty, + value, + cfg, + annotations, + documentation, + associated_to, + } + } + + pub fn uses_only_primitive_types(&self) -> bool { + self.value.uses_only_primitive_types() && self.ty.is_primitive_or_ptr_primitive() + } +} + +impl Item for Constant { + fn path(&self) -> &Path { + &self.path + } + + fn add_dependencies(&self, library: &Library, out: &mut Dependencies) { + self.ty.add_dependencies(library, out); + } + + fn export_name(&self) -> &str { + &self.export_name + } + + fn cfg(&self) -> Option<&Cfg> { + self.cfg.as_ref() + } + + fn annotations(&self) -> &AnnotationSet { + &self.annotations + } + + fn annotations_mut(&mut self) -> &mut AnnotationSet { + &mut self.annotations + } + + fn container(&self) -> ItemContainer { + ItemContainer::Constant(self.clone()) + } + + fn rename_for_config(&mut self, config: &Config) { + if self.associated_to.is_none() { + config.export.rename(&mut self.export_name); + } + self.value.rename_for_config(config); + self.ty.rename_for_config(config, &GenericParams::default()); // FIXME: should probably propagate something here + } + + fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) { + self.ty.resolve_declaration_types(resolver); + } +} + +impl Constant { + pub fn write_declaration<F: Write>( + &self, + config: &Config, + out: &mut SourceWriter<F>, + associated_to_struct: &Struct, + ) { + debug_assert!(self.associated_to.is_some()); + debug_assert!(config.language == Language::Cxx); + debug_assert!(!associated_to_struct.is_transparent); + debug_assert!(config.structure.associated_constants_in_body); + debug_assert!(config.constant.allow_static_const); + + if let Type::Ptr { is_const: true, .. } = self.ty { + out.write("static "); + } else { + out.write("static const "); + } + self.ty.write(config, out); + write!(out, " {};", self.export_name()) + } + + pub fn write<F: Write>( + &self, + config: &Config, + out: &mut SourceWriter<F>, + associated_to_struct: Option<&Struct>, + ) { + if let Some(assoc) = associated_to_struct { + if assoc.is_generic() { + return; // Not tested / implemented yet, so bail out. + } + } + + if !self.value.is_valid(out.bindings()) { + return; + } + + let associated_to_transparent = associated_to_struct.map_or(false, |s| s.is_transparent); + + let in_body = associated_to_struct.is_some() + && config.language == Language::Cxx + && config.structure.associated_constants_in_body + && config.constant.allow_static_const + && !associated_to_transparent; + + let condition = self.cfg.to_condition(config); + condition.write_before(config, out); + + let name = if in_body { + Cow::Owned(format!( + "{}::{}", + associated_to_struct.unwrap().export_name(), + self.export_name(), + )) + } else if self.associated_to.is_none() { + Cow::Borrowed(self.export_name()) + } else { + let associated_name = match associated_to_struct { + Some(s) => Cow::Borrowed(s.export_name()), + None => { + let mut name = self.associated_to.as_ref().unwrap().name().to_owned(); + config.export.rename(&mut name); + Cow::Owned(name) + } + }; + + Cow::Owned(format!("{}_{}", associated_name, self.export_name())) + }; + + let value = match self.value { + Literal::Struct { + ref fields, + ref path, + .. + } if out.bindings().struct_is_transparent(path) => fields.iter().next().unwrap().1, + _ => &self.value, + }; + + self.documentation.write(config, out); + + 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 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 "); + } + + self.ty.write(config, out); + write!(out, " {} = ", name); + value.write(config, out); + write!(out, ";"); + } + Language::Cxx | Language::C => { + write!(out, "#define {} ", name); + value.write(config, out); + } + Language::Cython => { + out.write("const "); + self.ty.write(config, out); + // For extern Cython declarations the initializer is ignored, + // but still useful as documentation, so we write it as a comment. + write!(out, " {} # = ", name); + value.write(config, out); + } + } + + condition.write_after(config, out); + } +} |