summaryrefslogtreecommitdiffstats
path: root/src/bindgen/ir/constant.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bindgen/ir/constant.rs')
-rw-r--r--src/bindgen/ir/constant.rs813
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);
+ }
+}