1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
8 use serde::{Deserialize, Serialize};
9 use std::borrow::Borrow;
10 use std::collections::HashMap;
12 use crate::interface::*;
13 use crate::BindingsConfig;
15 const RESERVED_WORDS: &[&str] = &[
16 "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else",
17 "elsif", "END", "end", "ensure", "false", "for", "if", "module", "next", "nil", "not", "or",
18 "redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef", "unless",
19 "until", "when", "while", "yield", "__FILE__", "__LINE__",
22 fn is_reserved_word(word: &str) -> bool {
23 RESERVED_WORDS.contains(&word)
26 /// Get the canonical, unique-within-this-component name for a type.
28 /// When generating helper code for foreign language bindings, it's sometimes useful to be
29 /// able to name a particular type in order to e.g. call a helper function that is specific
30 /// to that type. We support this by defining a naming convention where each type gets a
31 /// unique canonical name, constructed recursively from the names of its component types (if any).
32 pub fn canonical_name(t: &Type) -> String {
34 // Builtin primitive types, with plain old names.
35 Type::Int8 => "i8".into(),
36 Type::UInt8 => "u8".into(),
37 Type::Int16 => "i16".into(),
38 Type::UInt16 => "u16".into(),
39 Type::Int32 => "i32".into(),
40 Type::UInt32 => "u32".into(),
41 Type::Int64 => "i64".into(),
42 Type::UInt64 => "u64".into(),
43 Type::Float32 => "f32".into(),
44 Type::Float64 => "f64".into(),
45 Type::String => "string".into(),
46 Type::Bytes => "bytes".into(),
47 Type::Boolean => "bool".into(),
49 // Note that these all get unique names, and the parser ensures that the names do not
50 // conflict with a builtin type. We add a prefix to the name to guard against pathological
51 // cases like a record named `SequenceRecord` interfering with `sequence<Record>`.
52 // However, types that support importing all end up with the same prefix of "Type", so
53 // that the import handling code knows how to find the remote reference.
54 Type::Object { name, .. } => format!("Type{name}"),
55 Type::Enum { name, .. } => format!("Type{name}"),
56 Type::Record { name, .. } => format!("Type{name}"),
57 Type::CallbackInterface { name, .. } => format!("CallbackInterface{name}"),
58 Type::Timestamp => "Timestamp".into(),
59 Type::Duration => "Duration".into(),
60 Type::ForeignExecutor => "ForeignExecutor".into(),
62 // These add a prefix to the name of the underlying type.
63 // The component API definition cannot give names to recursive types, so as long as the
64 // prefixes we add here are all unique amongst themselves, then we have no chance of
65 // acccidentally generating name collisions.
66 Type::Optional { inner_type } => format!("Optional{}", canonical_name(inner_type)),
67 Type::Sequence { inner_type } => format!("Sequence{}", canonical_name(inner_type)),
73 canonical_name(key_type).to_upper_camel_case(),
74 canonical_name(value_type).to_upper_camel_case()
76 // A type that exists externally.
77 Type::External { name, .. } | Type::Custom { name, .. } => format!("Type{name}"),
81 // Some config options for it the caller wants to customize the generated ruby.
82 // Note that this can only be used to control details of the ruby *that do not affect the underlying component*,
83 // since the details of the underlying component are entirely determined by the `ComponentInterface`.
84 #[derive(Debug, Clone, Default, Serialize, Deserialize)]
86 cdylib_name: Option<String>,
87 cdylib_path: Option<String>,
91 pub fn cdylib_name(&self) -> String {
94 .unwrap_or_else(|| "uniffi".to_string())
97 pub fn custom_cdylib_path(&self) -> bool {
98 self.cdylib_path.is_some()
101 pub fn cdylib_path(&self) -> String {
102 self.cdylib_path.clone().unwrap_or_default()
106 impl BindingsConfig for Config {
107 fn update_from_ci(&mut self, ci: &ComponentInterface) {
109 .get_or_insert_with(|| format!("uniffi_{}", ci.namespace()));
112 fn update_from_cdylib_name(&mut self, cdylib_name: &str) {
114 .get_or_insert_with(|| cdylib_name.to_string());
117 fn update_from_dependency_configs(&mut self, _config_map: HashMap<&str, &Self>) {}
121 #[template(syntax = "rb", escape = "none", path = "wrapper.rb")]
122 pub struct RubyWrapper<'a> {
124 ci: &'a ComponentInterface,
125 canonical_name: &'a dyn Fn(&Type) -> String,
127 impl<'a> RubyWrapper<'a> {
128 pub fn new(config: Config, ci: &'a ComponentInterface) -> Self {
132 canonical_name: &canonical_name,
139 pub use crate::backend::filters::*;
141 pub fn type_ffi(type_: &FfiType) -> Result<String, askama::Error> {
143 FfiType::Int8 => ":int8".to_string(),
144 FfiType::UInt8 => ":uint8".to_string(),
145 FfiType::Int16 => ":int16".to_string(),
146 FfiType::UInt16 => ":uint16".to_string(),
147 FfiType::Int32 => ":int32".to_string(),
148 FfiType::UInt32 => ":uint32".to_string(),
149 FfiType::Int64 => ":int64".to_string(),
150 FfiType::UInt64 => ":uint64".to_string(),
151 FfiType::Float32 => ":float".to_string(),
152 FfiType::Float64 => ":double".to_string(),
153 FfiType::RustArcPtr(_) => ":pointer".to_string(),
154 FfiType::RustBuffer(_) => "RustBuffer.by_value".to_string(),
155 FfiType::ForeignBytes => "ForeignBytes".to_string(),
156 FfiType::ForeignCallback => unimplemented!("Callback interfaces are not implemented"),
157 FfiType::ForeignExecutorCallback => {
158 unimplemented!("Foreign executors are not implemented")
160 FfiType::ForeignExecutorHandle => {
161 unimplemented!("Foreign executors are not implemented")
163 FfiType::RustFutureHandle
164 | FfiType::RustFutureContinuationCallback
165 | FfiType::RustFutureContinuationData => {
166 unimplemented!("Async functions are not implemented")
171 pub fn literal_rb(literal: &Literal) -> Result<String, askama::Error> {
173 Literal::Boolean(v) => {
180 // use the double-quote form to match with the other languages, and quote escapes.
181 Literal::String(s) => format!("\"{s}\""),
182 Literal::Null => "nil".into(),
183 Literal::EmptySequence => "[]".into(),
184 Literal::EmptyMap => "{}".into(),
185 Literal::Enum(v, type_) => match type_ {
186 Type::Enum { name, .. } => {
187 format!("{}::{}", class_name_rb(name)?, enum_name_rb(v)?)
189 _ => panic!("Unexpected type in enum literal: {type_:?}"),
191 // https://docs.ruby-lang.org/en/2.0.0/syntax/literals_rdoc.html
192 Literal::Int(i, radix, _) => match radix {
193 Radix::Octal => format!("0o{i:o}"),
194 Radix::Decimal => format!("{i}"),
195 Radix::Hexadecimal => format!("{i:#x}"),
197 Literal::UInt(i, radix, _) => match radix {
198 Radix::Octal => format!("0o{i:o}"),
199 Radix::Decimal => format!("{i}"),
200 Radix::Hexadecimal => format!("{i:#x}"),
202 Literal::Float(string, _type_) => string.clone(),
206 pub fn class_name_rb(nm: &str) -> Result<String, askama::Error> {
207 Ok(nm.to_string().to_upper_camel_case())
210 pub fn fn_name_rb(nm: &str) -> Result<String, askama::Error> {
211 Ok(nm.to_string().to_snake_case())
214 pub fn var_name_rb(nm: &str) -> Result<String, askama::Error> {
215 let nm = nm.to_string();
216 let prefix = if is_reserved_word(&nm) { "_" } else { "" };
218 Ok(format!("{prefix}{}", nm.to_snake_case()))
221 pub fn enum_name_rb(nm: &str) -> Result<String, askama::Error> {
222 Ok(nm.to_string().to_shouty_snake_case())
225 pub fn coerce_rb(nm: &str, ns: &str, type_: &Type) -> Result<String, askama::Error> {
227 Type::Int8 => format!("{ns}::uniffi_in_range({nm}, \"i8\", -2**7, 2**7)"),
228 Type::Int16 => format!("{ns}::uniffi_in_range({nm}, \"i16\", -2**15, 2**15)"),
229 Type::Int32 => format!("{ns}::uniffi_in_range({nm}, \"i32\", -2**31, 2**31)"),
230 Type::Int64 => format!("{ns}::uniffi_in_range({nm}, \"i64\", -2**63, 2**63)"),
231 Type::UInt8 => format!("{ns}::uniffi_in_range({nm}, \"u8\", 0, 2**8)"),
232 Type::UInt16 => format!("{ns}::uniffi_in_range({nm}, \"u16\", 0, 2**16)"),
233 Type::UInt32 => format!("{ns}::uniffi_in_range({nm}, \"u32\", 0, 2**32)"),
234 Type::UInt64 => format!("{ns}::uniffi_in_range({nm}, \"u64\", 0, 2**64)"),
235 Type::Float32 | Type::Float64 => nm.to_string(),
236 Type::Boolean => format!("{nm} ? true : false"),
237 Type::Object { .. } | Type::Enum { .. } | Type::Record { .. } => nm.to_string(),
238 Type::String => format!("{ns}::uniffi_utf8({nm})"),
239 Type::Bytes => format!("{ns}::uniffi_bytes({nm})"),
240 Type::Timestamp | Type::Duration => nm.to_string(),
241 Type::CallbackInterface { .. } => {
242 panic!("No support for coercing callback interfaces yet")
244 Type::Optional { inner_type: t } => format!("({nm} ? {} : nil)", coerce_rb(nm, ns, t)?),
245 Type::Sequence { inner_type: t } => {
246 let coerce_code = coerce_rb("v", ns, t)?;
247 if coerce_code == "v" {
250 format!("{nm}.map {{ |v| {coerce_code} }}")
253 Type::Map { value_type: t, .. } => {
254 let k_coerce_code = coerce_rb("k", ns, &Type::String)?;
255 let v_coerce_code = coerce_rb("v", ns, t)?;
257 if k_coerce_code == "k" && v_coerce_code == "v" {
261 "{nm}.each.with_object({{}}) {{ |(k, v), res| res[{k_coerce_code}] = {v_coerce_code} }}"
265 Type::External { .. } => panic!("No support for external types, yet"),
266 Type::Custom { .. } => panic!("No support for custom types, yet"),
267 Type::ForeignExecutor => unimplemented!("Foreign executors are not implemented"),
271 pub fn lower_rb(nm: &str, type_: &Type) -> Result<String, askama::Error> {
282 | Type::Float64 => nm.to_string(),
283 Type::Boolean => format!("({nm} ? 1 : 0)"),
284 Type::String => format!("RustBuffer.allocFromString({nm})"),
285 Type::Bytes => format!("RustBuffer.allocFromBytes({nm})"),
286 Type::Object { name, .. } => format!("({}._uniffi_lower {nm})", class_name_rb(name)?),
287 Type::CallbackInterface { .. } => {
288 panic!("No support for lowering callback interfaces yet")
291 | Type::Record { .. }
292 | Type::Optional { .. }
293 | Type::Sequence { .. }
296 | Type::Map { .. } => format!(
297 "RustBuffer.alloc_from_{}({})",
298 class_name_rb(&canonical_name(type_))?,
301 Type::External { .. } => panic!("No support for lowering external types, yet"),
302 Type::Custom { .. } => panic!("No support for lowering custom types, yet"),
303 Type::ForeignExecutor => unimplemented!("Foreign executors are not implemented"),
307 pub fn lift_rb(nm: &str, type_: &Type) -> Result<String, askama::Error> {
316 | Type::UInt64 => format!("{nm}.to_i"),
317 Type::Float32 | Type::Float64 => format!("{nm}.to_f"),
318 Type::Boolean => format!("1 == {nm}"),
319 Type::String => format!("{nm}.consumeIntoString"),
320 Type::Bytes => format!("{nm}.consumeIntoBytes"),
321 Type::Object { name, .. } => format!("{}._uniffi_allocate({nm})", class_name_rb(name)?),
322 Type::CallbackInterface { .. } => {
323 panic!("No support for lifting callback interfaces, yet")
325 Type::Enum { .. } => {
329 class_name_rb(&canonical_name(type_))?
333 | Type::Optional { .. }
334 | Type::Sequence { .. }
337 | Type::Map { .. } => format!(
340 class_name_rb(&canonical_name(type_))?
342 Type::External { .. } => panic!("No support for lifting external types, yet"),
343 Type::Custom { .. } => panic!("No support for lifting custom types, yet"),
344 Type::ForeignExecutor => unimplemented!("Foreign executors are not implemented"),
354 fn test_canonical_names() {
355 // Non-exhaustive, but gives a bit of a flavour of what we want.
356 assert_eq!(canonical_name(&Type::UInt8), "u8");
357 assert_eq!(canonical_name(&Type::String), "string");
358 assert_eq!(canonical_name(&Type::Bytes), "bytes");
360 canonical_name(&Type::Optional {
361 inner_type: Box::new(Type::Sequence {
362 inner_type: Box::new(Type::Object {
363 module_path: "anything".to_string(),
364 name: "Example".into(),
365 imp: ObjectImpl::Struct,
369 "OptionalSequenceTypeExample"