Backed out 5 changesets (bug 1890092, bug 1888683) for causing build bustages & crash...
[gecko.git] / third_party / rust / uniffi_bindgen / src / bindings / ruby / gen_ruby / mod.rs
blob1f1bf8e299d36a794e36cc248dd22d5719f85b89
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/. */
5 use anyhow::Result;
6 use askama::Template;
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.
27 ///
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 {
33     match t {
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(),
48         // API defined types.
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(),
61         // Recursive types.
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)),
68         Type::Map {
69             key_type,
70             value_type,
71         } => format!(
72             "Map{}{}",
73             canonical_name(key_type).to_upper_camel_case(),
74             canonical_name(value_type).to_upper_camel_case()
75         ),
76         // A type that exists externally.
77         Type::External { name, .. } | Type::Custom { name, .. } => format!("Type{name}"),
78     }
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)]
85 pub struct Config {
86     cdylib_name: Option<String>,
87     cdylib_path: Option<String>,
90 impl Config {
91     pub fn cdylib_name(&self) -> String {
92         self.cdylib_name
93             .clone()
94             .unwrap_or_else(|| "uniffi".to_string())
95     }
97     pub fn custom_cdylib_path(&self) -> bool {
98         self.cdylib_path.is_some()
99     }
101     pub fn cdylib_path(&self) -> String {
102         self.cdylib_path.clone().unwrap_or_default()
103     }
106 impl BindingsConfig for Config {
107     fn update_from_ci(&mut self, ci: &ComponentInterface) {
108         self.cdylib_name
109             .get_or_insert_with(|| format!("uniffi_{}", ci.namespace()));
110     }
112     fn update_from_cdylib_name(&mut self, cdylib_name: &str) {
113         self.cdylib_name
114             .get_or_insert_with(|| cdylib_name.to_string());
115     }
117     fn update_from_dependency_configs(&mut self, _config_map: HashMap<&str, &Self>) {}
120 #[derive(Template)]
121 #[template(syntax = "rb", escape = "none", path = "wrapper.rb")]
122 pub struct RubyWrapper<'a> {
123     config: Config,
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 {
129         Self {
130             config,
131             ci,
132             canonical_name: &canonical_name,
133         }
134     }
137 mod filters {
138     use super::*;
139     pub use crate::backend::filters::*;
141     pub fn type_ffi(type_: &FfiType) -> Result<String, askama::Error> {
142         Ok(match type_ {
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")
159             }
160             FfiType::ForeignExecutorHandle => {
161                 unimplemented!("Foreign executors are not implemented")
162             }
163             FfiType::RustFutureHandle
164             | FfiType::RustFutureContinuationCallback
165             | FfiType::RustFutureContinuationData => {
166                 unimplemented!("Async functions are not implemented")
167             }
168         })
169     }
171     pub fn literal_rb(literal: &Literal) -> Result<String, askama::Error> {
172         Ok(match literal {
173             Literal::Boolean(v) => {
174                 if *v {
175                     "true".into()
176                 } else {
177                     "false".into()
178                 }
179             }
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)?)
188                 }
189                 _ => panic!("Unexpected type in enum literal: {type_:?}"),
190             },
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}"),
196             },
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}"),
201             },
202             Literal::Float(string, _type_) => string.clone(),
203         })
204     }
206     pub fn class_name_rb(nm: &str) -> Result<String, askama::Error> {
207         Ok(nm.to_string().to_upper_camel_case())
208     }
210     pub fn fn_name_rb(nm: &str) -> Result<String, askama::Error> {
211         Ok(nm.to_string().to_snake_case())
212     }
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()))
219     }
221     pub fn enum_name_rb(nm: &str) -> Result<String, askama::Error> {
222         Ok(nm.to_string().to_shouty_snake_case())
223     }
225     pub fn coerce_rb(nm: &str, ns: &str, type_: &Type) -> Result<String, askama::Error> {
226         Ok(match type_ {
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")
243             }
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" {
248                     nm.to_string()
249                 } else {
250                     format!("{nm}.map {{ |v| {coerce_code} }}")
251                 }
252             }
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" {
258                     nm.to_string()
259                 } else {
260                     format!(
261                         "{nm}.each.with_object({{}}) {{ |(k, v), res| res[{k_coerce_code}] = {v_coerce_code} }}"
262                     )
263                 }
264             }
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"),
268         })
269     }
271     pub fn lower_rb(nm: &str, type_: &Type) -> Result<String, askama::Error> {
272         Ok(match type_ {
273             Type::Int8
274             | Type::UInt8
275             | Type::Int16
276             | Type::UInt16
277             | Type::Int32
278             | Type::UInt32
279             | Type::Int64
280             | Type::UInt64
281             | Type::Float32
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")
289             }
290             Type::Enum { .. }
291             | Type::Record { .. }
292             | Type::Optional { .. }
293             | Type::Sequence { .. }
294             | Type::Timestamp
295             | Type::Duration
296             | Type::Map { .. } => format!(
297                 "RustBuffer.alloc_from_{}({})",
298                 class_name_rb(&canonical_name(type_))?,
299                 nm
300             ),
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"),
304         })
305     }
307     pub fn lift_rb(nm: &str, type_: &Type) -> Result<String, askama::Error> {
308         Ok(match type_ {
309             Type::Int8
310             | Type::UInt8
311             | Type::Int16
312             | Type::UInt16
313             | Type::Int32
314             | Type::UInt32
315             | Type::Int64
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")
324             }
325             Type::Enum { .. } => {
326                 format!(
327                     "{}.consumeInto{}",
328                     nm,
329                     class_name_rb(&canonical_name(type_))?
330                 )
331             }
332             Type::Record { .. }
333             | Type::Optional { .. }
334             | Type::Sequence { .. }
335             | Type::Timestamp
336             | Type::Duration
337             | Type::Map { .. } => format!(
338                 "{}.consumeInto{}",
339                 nm,
340                 class_name_rb(&canonical_name(type_))?
341             ),
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"),
345         })
346     }
349 #[cfg(test)]
350 mod test_type {
351     use super::*;
353     #[test]
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");
359         assert_eq!(
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,
366                     })
367                 })
368             }),
369             "OptionalSequenceTypeExample"
370         );
371     }
374 #[cfg(test)]
375 mod tests;