Add the standard-library functions the dependency crates actually call
Each of these came off a real crate's first failure, and each is the same
operation on both sides rather than a near-enough one:
* `discriminant_value` -- what `#[derive(PartialOrd)]` expands to. A C-like
enum's discriminant is its ordinal, which is what Nim's `ord` gives.
* `size_of` / `align_of` -- compile-time constants from the same C ABI both
languages compile against.
* `from_elem` -- what `vec![v; n]` expands to.
* `null` / `null_mut` -- a null pointer is `nil`.
* `T::max_value()` / `T::min_value()` -- the older spelling of `T::MAX`.
* `(a..=b).contains(&x)` -- a range used as a value, which is the only way
rustnim meets one outside a `for`. Nim's `x in a..b` is the same test and
does not need the range to exist as an object.
Eight of the first sixty dependency crates move past their first error on
these. None of them *passes* yet: each crate has a queue of these, and the
per-crate measure only moves when a queue empties.
tests/cases/066.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>eb9c264 parent: daf0fcd modified
src/lower.rs +91 -0 | @@ -6766,6 +6766,63 @@ impl Lowerer { | ||
| 6766 | 6766 | } |
| 6767 | 6767 | } |
| 6768 | 6768 | |
| 6769 | + // A small set of standard-library functions whose Nim equivalent is | |
| 6770 | + // exact. Each is here because it appeared in a real crate, and each is | |
| 6771 | + // the same operation on both sides rather than a near-enough one. | |
| 6772 | + match name.as_str() { | |
| 6773 | + // `#[derive(PartialOrd)]` expands to this. The discriminant of a | |
| 6774 | + // C-like enum is its ordinal, which is what Nim's `ord` gives. | |
| 6775 | + "discriminant_value" if codes.len() == 1 => { | |
| 6776 | + return Ok(Val::new( | |
| 6777 | + format!("ord({})", codes[0]), | |
| 6778 | + Some(Nim::Prim("int".into())), | |
| 6779 | + )); | |
| 6780 | + } | |
| 6781 | + // Both are compile-time constants defined by the same C ABI. | |
| 6782 | + "size_of" | "align_of" if codes.is_empty() => { | |
| 6783 | + let nim = if name == "size_of" { "sizeof" } else { "alignof" }; | |
| 6784 | + if let Some(seg) = p.path.segments.last() { | |
| 6785 | + if let syn::PathArguments::AngleBracketed(a) = &seg.arguments { | |
| 6786 | + if let Some(t) = a.args.iter().find_map(|g| match g { | |
| 6787 | + GenericArgument::Type(t) => self.map_ty(t).ok(), | |
| 6788 | + _ => None, | |
| 6789 | + }) { | |
| 6790 | + return Ok(Val::new( | |
| 6791 | + format!("{}({})", nim, t.render()), | |
| 6792 | + Some(Nim::Prim("int".into())), | |
| 6793 | + )); | |
| 6794 | + } | |
| 6795 | + } | |
| 6796 | + } | |
| 6797 | + } | |
| 6798 | + // `vec![v; n]`, after expansion. | |
| 6799 | + "from_elem" if codes.len() == 2 => { | |
| 6800 | + let t = args[0].ty.clone().map(|t| Nim::Seq(Box::new(t.owned()))); | |
| 6801 | + return Ok(Val::new( | |
| 6802 | + format!("newSeqWith(int({}), {})", codes[1], codes[0]), | |
| 6803 | + t, | |
| 6804 | + )); | |
| 6805 | + } | |
| 6806 | + // A null pointer is `nil`; Nim's is the same address. | |
| 6807 | + "null" | "null_mut" if codes.is_empty() => { | |
| 6808 | + return Ok(Val::new("nil", expect.cloned())); | |
| 6809 | + } | |
| 6810 | + _ => {} | |
| 6811 | + } | |
| 6812 | + | |
| 6813 | + // `T::max_value()` / `T::min_value()`: the same values as `T::MAX` and | |
| 6814 | + // `T::MIN`, under the older spelling several crates still use. | |
| 6815 | + if matches!(name.as_str(), "max_value" | "min_value") && codes.is_empty() { | |
| 6816 | + if let Some(q) = p.path.segments.iter().rev().nth(1) { | |
| 6817 | + if let Some(t @ Nim::Prim(_)) = ty::prim(&q.ident.to_string()) { | |
| 6818 | + if t.is_integer() && t.as_i128().is_none() { | |
| 6819 | + let f = if name == "max_value" { "high" } else { "low" }; | |
| 6820 | + return Ok(Val::new(format!("{}({})", f, t.render()), Some(t))); | |
| 6821 | + } | |
| 6822 | + } | |
| 6823 | + } | |
| 6824 | + } | |
| 6825 | + | |
| 6769 | 6826 | // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a |
| 6770 | 6827 | // string view; no copy, no validation, same memory. |
| 6771 | 6828 | if name == "from_utf8_unchecked" && codes.len() == 1 { |
| @@ -6958,6 +7015,31 @@ impl Lowerer { | ||
| 6958 | 7015 | } |
| 6959 | 7016 | |
| 6960 | 7017 | fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> { |
| 7018 | + // `(a..b).contains(&x)` -- a range used as a value, which is the only | |
| 7019 | + // way rustnim meets one outside a `for`. Nim's `x in a..b` is the same | |
| 7020 | + // test, and does not need the range to exist as an object. | |
| 7021 | + if m.method == "contains" && m.args.len() == 1 { | |
| 7022 | + if let Expr::Range(r) = strip_parens(&m.receiver) { | |
| 7023 | + let (Some(lo), Some(hi)) = (&r.start, &r.end) else { | |
| 7024 | + return Err("an unbounded range has no `contains` in Nim".into()); | |
| 7025 | + }; | |
| 7026 | + let inclusive = matches!(r.limits, syn::RangeLimits::Closed(_)); | |
| 7027 | + let x = self.expr(&m.args[0])?; | |
| 7028 | + let want = x.ty.clone(); | |
| 7029 | + let lo = self.expr_at(lo, want.as_ref())?; | |
| 7030 | + let hi = self.expr_at(hi, want.as_ref())?; | |
| 7031 | + let hi_code = if inclusive { | |
| 7032 | + hi.code | |
| 7033 | + } else { | |
| 7034 | + format!("pred({})", hi.code) | |
| 7035 | + }; | |
| 7036 | + return Ok(Val::new( | |
| 7037 | + format!("({} in {} .. {})", x.code, lo.code, hi_code), | |
| 7038 | + Some(Nim::Prim("bool".into())), | |
| 7039 | + )); | |
| 7040 | + } | |
| 7041 | + } | |
| 7042 | + | |
| 6961 | 7043 | let name = m.method.to_string(); |
| 6962 | 7044 | // `chunk_iter.remainder()` — the tail `chunks_exact` will not yield. |
| 6963 | 7045 | if name == "remainder" && m.args.is_empty() { |
| @@ -8850,3 +8932,12 @@ impl syn::visit_mut::VisitMut for Renamer<'_> { | ||
| 8850 | 8932 | syn::visit_mut::visit_type_mut(self, &mut f.ty); |
| 8851 | 8933 | } |
| 8852 | 8934 | } |
| 8935 | + | |
| 8936 | +/// Look through `(..)` and invisible groups to the expression inside. | |
| 8937 | +fn strip_parens(e: &Expr) -> &Expr { | |
| 8938 | + match e { | |
| 8939 | + Expr::Paren(p) => strip_parens(&p.expr), | |
| 8940 | + Expr::Group(g) => strip_parens(&g.expr), | |
| 8941 | + other => other, | |
| 8942 | + } | |
| 8943 | +} | |
| @@ -6766,6 +6766,63 @@ impl Lowerer { | |||
| 6766 | } | 6766 | } |
| 6767 | } | 6767 | } |
| 6768 | 6768 | ||
| 6769 | + // A small set of standard-library functions whose Nim equivalent is | ||
| 6770 | + // exact. Each is here because it appeared in a real crate, and each is | ||
| 6771 | + // the same operation on both sides rather than a near-enough one. | ||
| 6772 | + match name.as_str() { | ||
| 6773 | + // `#[derive(PartialOrd)]` expands to this. The discriminant of a | ||
| 6774 | + // C-like enum is its ordinal, which is what Nim's `ord` gives. | ||
| 6775 | + "discriminant_value" if codes.len() == 1 => { | ||
| 6776 | + return Ok(Val::new( | ||
| 6777 | + format!("ord({})", codes[0]), | ||
| 6778 | + Some(Nim::Prim("int".into())), | ||
| 6779 | + )); | ||
| 6780 | + } | ||
| 6781 | + // Both are compile-time constants defined by the same C ABI. | ||
| 6782 | + "size_of" | "align_of" if codes.is_empty() => { | ||
| 6783 | + let nim = if name == "size_of" { "sizeof" } else { "alignof" }; | ||
| 6784 | + if let Some(seg) = p.path.segments.last() { | ||
| 6785 | + if let syn::PathArguments::AngleBracketed(a) = &seg.arguments { | ||
| 6786 | + if let Some(t) = a.args.iter().find_map(|g| match g { | ||
| 6787 | + GenericArgument::Type(t) => self.map_ty(t).ok(), | ||
| 6788 | + _ => None, | ||
| 6789 | + }) { | ||
| 6790 | + return Ok(Val::new( | ||
| 6791 | + format!("{}({})", nim, t.render()), | ||
| 6792 | + Some(Nim::Prim("int".into())), | ||
| 6793 | + )); | ||
| 6794 | + } | ||
| 6795 | + } | ||
| 6796 | + } | ||
| 6797 | + } | ||
| 6798 | + // `vec![v; n]`, after expansion. | ||
| 6799 | + "from_elem" if codes.len() == 2 => { | ||
| 6800 | + let t = args[0].ty.clone().map(|t| Nim::Seq(Box::new(t.owned()))); | ||
| 6801 | + return Ok(Val::new( | ||
| 6802 | + format!("newSeqWith(int({}), {})", codes[1], codes[0]), | ||
| 6803 | + t, | ||
| 6804 | + )); | ||
| 6805 | + } | ||
| 6806 | + // A null pointer is `nil`; Nim's is the same address. | ||
| 6807 | + "null" | "null_mut" if codes.is_empty() => { | ||
| 6808 | + return Ok(Val::new("nil", expect.cloned())); | ||
| 6809 | + } | ||
| 6810 | + _ => {} | ||
| 6811 | + } | ||
| 6812 | + | ||
| 6813 | + // `T::max_value()` / `T::min_value()`: the same values as `T::MAX` and | ||
| 6814 | + // `T::MIN`, under the older spelling several crates still use. | ||
| 6815 | + if matches!(name.as_str(), "max_value" | "min_value") && codes.is_empty() { | ||
| 6816 | + if let Some(q) = p.path.segments.iter().rev().nth(1) { | ||
| 6817 | + if let Some(t @ Nim::Prim(_)) = ty::prim(&q.ident.to_string()) { | ||
| 6818 | + if t.is_integer() && t.as_i128().is_none() { | ||
| 6819 | + let f = if name == "max_value" { "high" } else { "low" }; | ||
| 6820 | + return Ok(Val::new(format!("{}({})", f, t.render()), Some(t))); | ||
| 6821 | + } | ||
| 6822 | + } | ||
| 6823 | + } | ||
| 6824 | + } | ||
| 6825 | + | ||
| 6769 | // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a | 6826 | // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a |
| 6770 | // string view; no copy, no validation, same memory. | 6827 | // string view; no copy, no validation, same memory. |
| 6771 | if name == "from_utf8_unchecked" && codes.len() == 1 { | 6828 | if name == "from_utf8_unchecked" && codes.len() == 1 { |
| @@ -6958,6 +7015,31 @@ impl Lowerer { | |||
| 6958 | } | 7015 | } |
| 6959 | 7016 | ||
| 6960 | fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> { | 7017 | fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> { |
| 7018 | + // `(a..b).contains(&x)` -- a range used as a value, which is the only | ||
| 7019 | + // way rustnim meets one outside a `for`. Nim's `x in a..b` is the same | ||
| 7020 | + // test, and does not need the range to exist as an object. | ||
| 7021 | + if m.method == "contains" && m.args.len() == 1 { | ||
| 7022 | + if let Expr::Range(r) = strip_parens(&m.receiver) { | ||
| 7023 | + let (Some(lo), Some(hi)) = (&r.start, &r.end) else { | ||
| 7024 | + return Err("an unbounded range has no `contains` in Nim".into()); | ||
| 7025 | + }; | ||
| 7026 | + let inclusive = matches!(r.limits, syn::RangeLimits::Closed(_)); | ||
| 7027 | + let x = self.expr(&m.args[0])?; | ||
| 7028 | + let want = x.ty.clone(); | ||
| 7029 | + let lo = self.expr_at(lo, want.as_ref())?; | ||
| 7030 | + let hi = self.expr_at(hi, want.as_ref())?; | ||
| 7031 | + let hi_code = if inclusive { | ||
| 7032 | + hi.code | ||
| 7033 | + } else { | ||
| 7034 | + format!("pred({})", hi.code) | ||
| 7035 | + }; | ||
| 7036 | + return Ok(Val::new( | ||
| 7037 | + format!("({} in {} .. {})", x.code, lo.code, hi_code), | ||
| 7038 | + Some(Nim::Prim("bool".into())), | ||
| 7039 | + )); | ||
| 7040 | + } | ||
| 7041 | + } | ||
| 7042 | + | ||
| 6961 | let name = m.method.to_string(); | 7043 | let name = m.method.to_string(); |
| 6962 | // `chunk_iter.remainder()` — the tail `chunks_exact` will not yield. | 7044 | // `chunk_iter.remainder()` — the tail `chunks_exact` will not yield. |
| 6963 | if name == "remainder" && m.args.is_empty() { | 7045 | if name == "remainder" && m.args.is_empty() { |
| @@ -8850,3 +8932,12 @@ impl syn::visit_mut::VisitMut for Renamer<'_> { | |||
| 8850 | syn::visit_mut::visit_type_mut(self, &mut f.ty); | 8932 | syn::visit_mut::visit_type_mut(self, &mut f.ty); |
| 8851 | } | 8933 | } |
| 8852 | } | 8934 | } |
| 8935 | + | ||
| 8936 | +/// Look through `(..)` and invisible groups to the expression inside. | ||
| 8937 | +fn strip_parens(e: &Expr) -> &Expr { | ||
| 8938 | + match e { | ||
| 8939 | + Expr::Paren(p) => strip_parens(&p.expr), | ||
| 8940 | + Expr::Group(g) => strip_parens(&g.expr), | ||
| 8941 | + other => other, | ||
| 8942 | + } | ||
| 8943 | +} | ||
added
tests/cases/066-std-subset.rs +33 -0 | new file mode 100644 | ||
| @@ -0,0 +1,33 @@ | ||
| 1 | +// Standard-library functions whose Nim equivalent is exact. Each of these | |
| 2 | +// appeared in a real crate; each is the same operation on both sides. | |
| 3 | +use std::mem::{align_of, size_of}; | |
| 4 | + | |
| 5 | +#[derive(PartialEq, PartialOrd)] | |
| 6 | +enum Rank { | |
| 7 | + Low, | |
| 8 | + Mid, | |
| 9 | + High, | |
| 10 | +} | |
| 11 | + | |
| 12 | +fn main() { | |
| 13 | + // `size_of`/`align_of` are compile-time constants from the same C ABI. | |
| 14 | + println!("{} {}", size_of::<u32>(), align_of::<u32>()); | |
| 15 | + println!("{} {}", size_of::<u64>(), align_of::<u8>()); | |
| 16 | + | |
| 17 | + // The older spelling of `T::MAX` / `T::MIN`. | |
| 18 | + println!("{} {}", u8::max_value(), i16::min_value()); | |
| 19 | + println!("{}", u32::max_value()); | |
| 20 | + | |
| 21 | + // `vec![v; n]` expands to `from_elem`. | |
| 22 | + let v = vec![7u8; 4]; | |
| 23 | + println!("{} {}", v.len(), v[3]); | |
| 24 | + | |
| 25 | + // `(a..=b).contains(&x)` — Nim's `x in a..b` is the same test. | |
| 26 | + for r in [1u32, 2, 20, 36, 37] { | |
| 27 | + println!("{} {}", r, (2..=36).contains(&r)); | |
| 28 | + } | |
| 29 | + println!("{}", (0..10).contains(&10)); | |
| 30 | + | |
| 31 | + // `#[derive(PartialOrd)]` compares discriminants. | |
| 32 | + println!("{} {}", Rank::Low < Rank::High, Rank::Mid > Rank::High); | |
| 33 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | +// Standard-library functions whose Nim equivalent is exact. Each of these | ||
| 2 | +// appeared in a real crate; each is the same operation on both sides. | ||
| 3 | +use std::mem::{align_of, size_of}; | ||
| 4 | + | ||
| 5 | +#[derive(PartialEq, PartialOrd)] | ||
| 6 | +enum Rank { | ||
| 7 | + Low, | ||
| 8 | + Mid, | ||
| 9 | + High, | ||
| 10 | +} | ||
| 11 | + | ||
| 12 | +fn main() { | ||
| 13 | + // `size_of`/`align_of` are compile-time constants from the same C ABI. | ||
| 14 | + println!("{} {}", size_of::<u32>(), align_of::<u32>()); | ||
| 15 | + println!("{} {}", size_of::<u64>(), align_of::<u8>()); | ||
| 16 | + | ||
| 17 | + // The older spelling of `T::MAX` / `T::MIN`. | ||
| 18 | + println!("{} {}", u8::max_value(), i16::min_value()); | ||
| 19 | + println!("{}", u32::max_value()); | ||
| 20 | + | ||
| 21 | + // `vec![v; n]` expands to `from_elem`. | ||
| 22 | + let v = vec![7u8; 4]; | ||
| 23 | + println!("{} {}", v.len(), v[3]); | ||
| 24 | + | ||
| 25 | + // `(a..=b).contains(&x)` — Nim's `x in a..b` is the same test. | ||
| 26 | + for r in [1u32, 2, 20, 36, 37] { | ||
| 27 | + println!("{} {}", r, (2..=36).contains(&r)); | ||
| 28 | + } | ||
| 29 | + println!("{}", (0..10).contains(&10)); | ||
| 30 | + | ||
| 31 | + // `#[derive(PartialOrd)]` compares discriminants. | ||
| 32 | + println!("{} {}", Rank::Low < Rank::High, Rank::Mid > Rank::High); | ||
| 33 | +} | ||