nandi/rustnimpublic Fork 0
12c0a01b02392b18af24b583ba4ff65ad040e86a
Commits
Clone
git clone https://git.rickub.com/nandi/rustnim.git
git clone ssh://git@rickub.com/nandi/rustnim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

ty.rs · 318 lines · 12.1 KBRust Blame HistoryRaw
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 18h ago1//! Rust type -> Nim type mapping.
2//!
3//! Integer width is preserved exactly. Anything that cannot be represented
4//! faithfully in Nim is reported as an error rather than approximated: a
5//! silently widened integer would change the meaning of wrapping arithmetic,
6//! which is precisely the kind of code people write in Rust.
7
8use syn::{GenericArgument, PathArguments, Type, TypeParamBound};
9
10#[derive(Debug, Clone, PartialEq)]
11pub enum Nim {
12 Prim(String),
13 Seq(Box<Nim>),
14 OpenArray(Box<Nim>),
15 Array(usize, Box<Nim>),
16 Tuple(Vec<Nim>),
17 Named(String, Vec<Nim>),
18 Var(Box<Nim>),
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago19 /// `impl Fn(A) -> B` / `fn(A) -> B`. Left at Nim's default calling
20 /// convention (`closure`), which accepts both a plain top-level proc and
21 /// a closure that captures -- and Rust's `impl Fn` accepts both too.
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago22 Proc(Vec<Nim>, Box<Nim>),
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 18h ago23 Unit,
24}
25
26impl Nim {
27 pub fn render(&self) -> String {
28 match self {
29 Nim::Prim(s) => s.clone(),
30 Nim::Seq(t) => format!("seq[{}]", t.render()),
31 Nim::OpenArray(t) => format!("openArray[{}]", t.render()),
32 Nim::Array(n, t) => format!("array[{}, {}]", n, t.render()),
33 Nim::Tuple(ts) => {
34 let inner: Vec<String> = ts.iter().map(|t| t.render()).collect();
35 format!("({})", inner.join(", "))
36 }
37 Nim::Named(n, args) if args.is_empty() => n.clone(),
38 Nim::Named(n, args) => {
39 let inner: Vec<String> = args.iter().map(|t| t.render()).collect();
40 format!("{}[{}]", n, inner.join(", "))
41 }
42 Nim::Var(t) => format!("var {}", t.render()),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago43 Nim::Proc(args, ret) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago44 // Nim's proc types name their parameters even when the name is
45 // never used.
46 let inner: Vec<String> = args
47 .iter()
48 .enumerate()
49 .map(|(i, t)| format!("a{}: {}", i, t.render()))
50 .collect();
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago51 match &**ret {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago52 Nim::Unit => format!("proc ({})", inner.join(", ")),
53 r => format!("proc ({}): {}", inner.join(", "), r.render()),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago54 }
55 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 18h ago56 Nim::Unit => "void".into(),
57 }
58 }
59
Add display.rs and the alloc half: all of base16ct now goes through afb2a6e nandithebull 17h ago60 /// Strip a `var`, which is a parameter-passing mode rather than a type.
61 /// Unlike `owned`, this keeps a view a view.
62 pub fn unvar(self) -> Nim {
63 match self {
64 Nim::Var(t) => t.unvar(),
65 other => other,
66 }
67 }
68
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 18h ago69 /// Owned form: a borrowed slice parameter is `openArray[T]`, but the same
70 /// type in an owned position (a field, a return value) must be `seq[T]`.
71 pub fn owned(self) -> Nim {
72 match self {
73 Nim::OpenArray(t) => Nim::Seq(t),
74 Nim::Var(t) => t.owned(),
75 other => other,
76 }
77 }
78
79 pub fn is_integer(&self) -> bool {
80 matches!(self, Nim::Prim(p) if matches!(p.as_str(),
81 "int8"|"int16"|"int32"|"int64"|"int"|
82 "uint8"|"uint16"|"uint32"|"uint64"|"uint"))
83 }
84
85 pub fn is_unsigned(&self) -> bool {
86 matches!(self, Nim::Prim(p) if p.starts_with("uint"))
87 }
88}
89
90pub fn prim(name: &str) -> Option<Nim> {
91 let mapped = match name {
92 "i8" => "int8",
93 "i16" => "int16",
94 "i32" => "int32",
95 "i64" => "int64",
96 "isize" => "int",
97 "u8" => "uint8",
98 "u16" => "uint16",
99 "u32" => "uint32",
100 "u64" => "uint64",
101 "usize" => "uint",
102 "f32" => "float32",
103 "f64" => "float64",
104 "bool" => "bool",
105 "char" => "Rune",
106 "str" | "String" => "string",
107 _ => return None,
108 };
109 Some(Nim::Prim(mapped.into()))
110}
111
112/// Types we refuse rather than approximate.
113pub fn rejected(name: &str) -> Option<&'static str> {
114 match name {
115 "i128" | "u128" => Some("128-bit integers have no faithful Nim equivalent"),
116 _ => None,
117 }
118}
119
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago120fn ret_ty(r: &syn::ReturnType) -> Result<Nim, String> {
121 match r {
122 syn::ReturnType::Default => Ok(Nim::Unit),
123 syn::ReturnType::Type(_, t) => Ok(map(t)?.owned()),
124 }
125}
126
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago127/// Types from `core::fmt` that must not be confused with a user type of the
128/// same short name. `fmt::Error` and a crate's own `Error` are different
129/// types, and collapsing a path to its last segment would merge them.
130fn std_qualified(p: &syn::Path) -> Option<Nim> {
131 let segs: Vec<String> = p.segments.iter().map(|s| s.ident.to_string()).collect();
132 if segs.len() < 2 {
133 return None;
134 }
135 let (q, name) = (&segs[segs.len() - 2], segs.last()?.as_str());
136 if q != "fmt" {
137 return None;
138 }
139 Some(Nim::Prim(
140 match name {
141 "Error" => "FmtError",
142 "Formatter" => "Formatter",
143 // `fmt::Result` is `Result<(), fmt::Error>`. A formatting impl is
144 // lowered to a proc that returns the formatted string, so the
145 // result type is erased there; this spelling exists so that a
146 // signature mentioning it still maps to something.
147 "Result" => "FmtResult",
148 _ => return None,
149 }
150 .into(),
151 ))
152}
153
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 18h ago154pub fn map(t: &Type) -> Result<Nim, String> {
155 match t {
156 Type::Path(p) => {
Add trait impls and slice iterators; base16ct's decoder now goes through ae9f986 nandithebull 17h ago157 if let Some(n) = std_qualified(&p.path) {
158 return Ok(n);
159 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 18h ago160 let seg = p
161 .path
162 .segments
163 .last()
164 .ok_or_else(|| "empty type path".to_string())?;
165 let name = seg.ident.to_string();
166
167 if let Some(why) = rejected(&name) {
168 return Err(format!("unsupported type `{}`: {}", name, why));
169 }
170
171 let args: Vec<Nim> = match &seg.arguments {
172 PathArguments::AngleBracketed(a) => a
173 .args
174 .iter()
175 .filter_map(|g| match g {
176 GenericArgument::Type(t) => Some(map(t)),
177 _ => None,
178 })
179 .collect::<Result<_, _>>()?,
180 _ => vec![],
181 };
182
183 match (name.as_str(), args.len()) {
Lower the `log` facade, and add explicit enum discriminants 12c0a01 nandithebull 14h ago184 // The `log` facade's types, under shim names.
185 ("Level", 0) => Ok(Nim::Prim("RsLogLevel".into())),
186 ("LevelFilter", 0) => Ok(Nim::Prim("RsLogFilter".into())),
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 18h ago187 ("Vec", 1) => Ok(Nim::Seq(Box::new(args[0].clone().owned()))),
188 ("Option", 1) => Ok(Nim::Named("Option".into(), args)),
189 ("Result", 2) => Ok(Nim::Named("Result".into(), args)),
190 ("Box", 1) => Ok(args[0].clone()),
191 _ => {
192 if let Some(p) = prim(&name) {
193 Ok(p)
194 } else {
195 Ok(Nim::Named(name, args))
196 }
197 }
198 }
199 }
200 // &T is a value in Nim; &mut T becomes a `var` parameter. The caller
201 // decides whether a `var` is legal in the position it is used.
202 Type::Reference(r) => {
Add closures and unsafe; base16ct's lower.rs and upper.rs go through 0a375d8 nandithebull 17h ago203 // `&str` is a borrowed view of characters, not an owned string.
204 // Nim accepts a `string` argument for an `openArray[char]`
205 // parameter, so a literal still passes straight through.
206 if let Type::Path(p) = &*r.elem {
207 if p.path.is_ident("str") {
208 return Ok(Nim::OpenArray(Box::new(Nim::Prim("char".into()))));
209 }
210 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 18h ago211 let inner = map(&r.elem)?;
212 if r.mutability.is_some() {
213 Ok(Nim::Var(Box::new(inner)))
214 } else {
215 Ok(inner)
216 }
217 }
218 Type::Slice(s) => Ok(Nim::OpenArray(Box::new(map(&s.elem)?))),
219 Type::Array(a) => {
220 let len = match &a.len {
221 syn::Expr::Lit(syn::ExprLit {
222 lit: syn::Lit::Int(i),
223 ..
224 }) => i
225 .base10_parse::<usize>()
226 .map_err(|e| format!("array length: {}", e))?,
227 _ => return Err("array length must be a literal".into()),
228 };
229 Ok(Nim::Array(len, Box::new(map(&a.elem)?)))
230 }
231 Type::Tuple(t) if t.elems.is_empty() => Ok(Nim::Unit),
232 Type::Tuple(t) => Ok(Nim::Tuple(
233 t.elems.iter().map(map).collect::<Result<_, _>>()?,
234 )),
235 Type::Paren(p) => map(&p.elem),
236 Type::Group(g) => map(&g.elem),
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago237 Type::FnPtr(f) => {
238 let args: Vec<Nim> = f
239 .inputs
240 .iter()
241 .map(|a| map(&a.ty))
242 .collect::<Result<_, _>>()?;
243 Ok(Nim::Proc(args, Box::new(ret_ty(&f.output)?)))
244 }
Lower `bitflags!` directly, checked against the real crate af6e50f nandithebull 15h ago245 // `dyn Fn(A) -> B` is a callable, exactly as `impl Fn(A) -> B` is.
246 // Other trait objects need a vtable, which the lowering builds from
247 // the trait's declaration; `dyn` is spelled `<Trait>Dyn` there.
248 Type::TraitObject(t) => {
249 for b in &t.bounds {
250 if let TypeParamBound::Trait(tb) = b {
251 if let Some(seg) = tb.path.segments.last() {
252 let n = seg.ident.to_string();
253 if n == "Fn" || n == "FnMut" || n == "FnOnce" {
254 if let PathArguments::Parenthesized(a) = &seg.arguments {
255 let args: Vec<Nim> = a
256 .inputs
257 .iter()
258 .map(|a| map(&a.ty))
259 .collect::<Result<_, _>>()?;
260 return Ok(Nim::Proc(args, Box::new(ret_ty(&a.output)?)));
261 }
262 }
263 // `dyn T + Send + Sync`: the auto traits carry no
264 // methods, so the first real bound names the object.
265 if !matches!(n.as_str(), "Send" | "Sync" | "Unpin" | "Sized") {
266 return Ok(Nim::Named(format!("{n}Dyn"), vec![]));
267 }
268 }
269 }
270 }
271 Err("a trait object with no nameable trait bound".into())
272 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 18h ago273 Type::ImplTrait(i) => {
274 // `impl AsRef<[u8]>` and friends: fall back to the bound's own
275 // shape where we can recognise it, since Nim has no impl-trait.
276 for b in &i.bounds {
277 if let TypeParamBound::Trait(tb) = b {
278 if let Some(seg) = tb.path.segments.last() {
Add enums, Option/Result, `?`, and the machinery base16ct needs around them b0ccd80 nandithebull 18h ago279 // `impl Fn(A) -> B` is a callable; Nim has a proc type
280 // for exactly this.
281 if seg.ident == "Fn" || seg.ident == "FnMut" || seg.ident == "FnOnce" {
282 if let PathArguments::Parenthesized(a) = &seg.arguments {
283 let args: Vec<Nim> = a
284 .inputs
285 .iter()
286 .map(|a| map(&a.ty))
287 .collect::<Result<_, _>>()?;
288 return Ok(Nim::Proc(args, Box::new(ret_ty(&a.output)?)));
289 }
290 }
Scaffold rustnim: Rust->Nim transpiler, type mapping 1a218c2 nandi 18h ago291 if seg.ident == "AsRef" || seg.ident == "Into" {
292 if let PathArguments::AngleBracketed(a) = &seg.arguments {
293 for g in &a.args {
294 if let GenericArgument::Type(t) = g {
295 return map(t);
296 }
297 }
298 }
299 }
300 }
301 }
302 }
303 Err("unsupported `impl Trait` type".into())
304 }
305 Type::Infer(_) => Err("inferred type in a position that needs a name".into()),
306 other => Err(format!("unsupported type form: {:?}", discriminant(other))),
307 }
308}
309
310fn discriminant(t: &Type) -> &'static str {
311 match t {
312 Type::Ptr(_) => "raw pointer",
313 Type::TraitObject(_) => "trait object",
314 Type::Never(_) => "never",
315 Type::Macro(_) => "macro",
316 _ => "other",
317 }
318}