nandi/rustnimpublic Fork
⑂ eb9c264
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.

Download ZIP

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>
nandithebull committed 2026-09-19T22:28:59-07:00 Browse files
eb9c264 parent: daf0fcd
modified src/lower.rs +91 -0
@@ -6766,6 +6766,63 @@ impl Lowerer {
67666766 }
67676767 }
67686768
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+
67696826 // `core::str::from_utf8_unchecked(b)` reinterprets a byte view as a
67706827 // string view; no copy, no validation, same memory.
67716828 if name == "from_utf8_unchecked" && codes.len() == 1 {
@@ -6958,6 +7015,31 @@ impl Lowerer {
69587015 }
69597016
69607017 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+
69617043 let name = m.method.to_string();
69627044 // `chunk_iter.remainder()` — the tail `chunks_exact` will not yield.
69637045 if name == "remainder" && m.args.is_empty() {
@@ -8850,3 +8932,12 @@ impl syn::visit_mut::VisitMut for Renamer<'_> {
88508932 syn::visit_mut::visit_type_mut(self, &mut f.ty);
88518933 }
88528934 }
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 a6826 // `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+}