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

Infer the types Rust infers, and lower guarded bindings

Working `color_quant` end to end, which `--report` made possible by showing
one failing item at a time instead of one per run. Each of these is the
inference Rust itself does, read from the same place:

  * `let mut v = Vec::new();` takes its element type from the first `push`,
    looking through loops and branches, since a `Vec` is usually filled by
    one. A cast says its own type, which most pushes are, so the operands do
    not have to be in scope.
  * `let mut x;` takes its type from the first assignment.
  * `let x = match ..` with statement arms takes its type from the arms. Rust
    gives them all the same type, so whichever one types answers for all.
  * `match x { n if n <= 100 => .. }` -- a guard on a plain binding. The
    binding has to be live while the guard runs and a Nim `elif` cannot
    introduce one; it does not need to, because an irrefutable `n` is another
    name for the scrutinee, which is already in a temporary. A guard on a
    *destructuring* pattern is still refused, and now says why.
  * An assignment is an expression in Rust, of type `()`.

Plus `cmp::max`/`min`, the float predicates, and `f64::MAX`. That last one is
not `high(float64)`: Nim's is `inf` and Rust's is the largest *finite* value,
so the constants are written out.

DESIGN.md records a real difference found on the way and left alone: Rust's
`{}` on `f64::MAX` writes 309 digits where Nim's `$` writes
`1.7976931348623157e308`. Ordinary magnitudes agree, which is why nothing
caught it before.

tests/cases/067.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-19T23:12:04-07:00 Browse files
c51a504 parent: 4172f11
modified DESIGN.md +13 -0
@@ -1310,3 +1310,16 @@ The fix is the one already used for functions: key the type tables by
13101310 global match, and qualify the emitted Nim name where a bare one would clash.
13111311 That is a real change to the shape of the lowering, not an incremental patch,
13121312 which is why it is written down here rather than attempted in passing.
1313+
1314+### `Display` for floats at extreme magnitudes
1315+
1316+Rust's `{}` on an `f64` writes the full decimal expansion --
1317+`f64::MAX` prints as 309 digits -- where Nim's `$` writes
1318+`1.7976931348623157e308`. Both round-trip; they are not the same string.
1319+
1320+Found by `tests/cases/066` when it printed `f64::MAX` directly. Ordinary
1321+magnitudes agree, which is why no earlier case caught it, and why this is
1322+recorded rather than fixed: matching Rust here means implementing its float
1323+`Display` rather than borrowing Nim's, and nothing in the dependency graph so
1324+far prints a float that large. It is a real difference and it is written down;
1325+a program that does print one will differ.
@@ -1310,3 +1310,16 @@ The fix is the one already used for functions: key the type tables by
1310 global match, and qualify the emitted Nim name where a bare one would clash.1310 global match, and qualify the emitted Nim name where a bare one would clash.
1311 That is a real change to the shape of the lowering, not an incremental patch,1311 That is a real change to the shape of the lowering, not an incremental patch,
1312 which is why it is written down here rather than attempted in passing.1312 which is why it is written down here rather than attempted in passing.
1313+
1314+### `Display` for floats at extreme magnitudes
1315+
1316+Rust's `{}` on an `f64` writes the full decimal expansion --
1317+`f64::MAX` prints as 309 digits -- where Nim's `$` writes
1318+`1.7976931348623157e308`. Both round-trip; they are not the same string.
1319+
1320+Found by `tests/cases/066` when it printed `f64::MAX` directly. Ordinary
1321+magnitudes agree, which is why no earlier case caught it, and why this is
1322+recorded rather than fixed: matching Rust here means implementing its float
1323+`Display` rather than borrowing Nim's, and nothing in the dependency graph so
1324+far prints a float that large. It is a real difference and it is written down;
1325+a program that does print one will differ.
modified src/lower.rs +275 -16
@@ -379,6 +379,10 @@ pub struct Lowerer {
379379 /// missing the items that failed is not a translation of anything.
380380 pub report: bool,
381381 errors: Vec<String>,
382+ /// Element type for an un-annotated `Vec` binding, from its first push.
383+ vec_hint: Option<Nim>,
384+ /// Type for a `let x;` with no initialiser, from its first assignment.
385+ deferred_hint: Option<Nim>,
382386 /// Bounds discovered while rewriting `impl Trait` parameters. `rewrite_apit`
383387 /// runs from `&self`, so they are parked here and folded into `bounds` by
384388 /// whoever called it.
@@ -483,6 +487,8 @@ impl Lowerer {
483487 enum_module: HashMap::new(),
484488 report: false,
485489 errors: Vec::new(),
490+ vec_hint: None,
491+ deferred_hint: None,
486492 pending_apit_bounds: std::cell::RefCell::new(Vec::new()),
487493 bounds: HashMap::new(),
488494 const_names_at: std::cell::Cell::new(None),
@@ -3756,6 +3762,7 @@ impl Lowerer {
37563762 let mut tail = None;
37573763 for (i, st) in b.stmts.iter().enumerate() {
37583764 let last = i + 1 == n;
3765+ let rest = &b.stmts[i + 1..];
37593766 match st {
37603767 Stmt::Expr(e, None) if last && expressible(e) => {
37613768 tail = Some(self.expr_at(e, expect)?)
@@ -3772,6 +3779,12 @@ impl Lowerer {
37723779 None => self.stmt(st)?,
37733780 }
37743781 }
3782+ Stmt::Local(l) => {
3783+ self.local(l, rest)?;
3784+ for lk in std::mem::take(&mut self.pending_locks) {
3785+ self.line(&format!("release({})", lk));
3786+ }
3787+ }
37753788 _ => self.stmt(st)?,
37763789 }
37773790 }
@@ -3807,7 +3820,7 @@ impl Lowerer {
38073820
38083821 fn stmt_inner(&mut self, s: &Stmt) -> Result<(), String> {
38093822 match s {
3810- Stmt::Local(l) => self.local(l),
3823+ Stmt::Local(l) => self.local(l, &[]),
38113824 Stmt::Expr(e, _) => {
38123825 let v = self.expr_stmt(e)?;
38133826 if let Some(v) = v {
@@ -3838,7 +3851,161 @@ impl Lowerer {
38383851 }
38393852 }
38403853
3841- fn local(&mut self, l: &Local) -> Result<(), String> {
3854+ fn local(&mut self, l: &Local, rest: &[Stmt]) -> Result<(), String> {
3855+ // `let mut v = Vec::new();` has no element type of its own -- Rust
3856+ // takes it from the first `push`, and so does this. Only when there is
3857+ // no annotation and the initialiser is the empty constructor; anything
3858+ // else already says what it holds.
3859+ let hint = self.vec_elem_hint(l, rest);
3860+ let outer_hint = std::mem::replace(&mut self.vec_hint, hint);
3861+ let deferred = self.deferred_let_hint(l, rest);
3862+ let outer_def = std::mem::replace(&mut self.deferred_hint, deferred);
3863+ let r = self.local_inner(l);
3864+ self.vec_hint = outer_hint;
3865+ self.deferred_hint = outer_def;
3866+ return r;
3867+ }
3868+
3869+ /// The type of a `let x;` binding, read off the first assignment to it.
3870+ fn deferred_let_hint(&mut self, l: &Local, rest: &[Stmt]) -> Option<Nim> {
3871+ if l.init.is_some() || matches!(&l.pat, Pat::Type(_)) {
3872+ return None;
3873+ }
3874+ let Pat::Ident(id) = &l.pat else { return None };
3875+ let name = id.ident.to_string();
3876+ let mut found = None;
3877+ self.find_assign(&name, rest, &mut found);
3878+ found
3879+ }
3880+
3881+ fn find_assign(&mut self, name: &str, stmts: &[Stmt], out: &mut Option<Nim>) {
3882+ for st in stmts {
3883+ if out.is_some() {
3884+ return;
3885+ }
3886+ let Stmt::Expr(e, _) = st else { continue };
3887+ self.find_assign_expr(name, e, out);
3888+ }
3889+ }
3890+
3891+ fn find_assign_expr(&mut self, name: &str, e: &Expr, out: &mut Option<Nim>) {
3892+ if out.is_some() {
3893+ return;
3894+ }
3895+ match e {
3896+ Expr::Assign(a) => {
3897+ if let Expr::Path(p) = &*a.left {
3898+ if p.path.segments.len() == 1 && p.path.segments[0].ident == name {
3899+ *out = self.pushed_ty(&a.right);
3900+ }
3901+ }
3902+ }
3903+ Expr::ForLoop(f) => self.find_assign(name, &f.body.stmts, out),
3904+ Expr::While(w) => self.find_assign(name, &w.body.stmts, out),
3905+ Expr::Loop(l) => self.find_assign(name, &l.body.stmts, out),
3906+ Expr::Block(b) => self.find_assign(name, &b.block.stmts, out),
3907+ Expr::Unsafe(u) => self.find_assign(name, &u.block.stmts, out),
3908+ Expr::If(i) => {
3909+ self.find_assign(name, &i.then_branch.stmts, out);
3910+ if let Some((_, els)) = &i.else_branch {
3911+ self.find_assign_expr(name, els, out);
3912+ }
3913+ }
3914+ Expr::Match(m) => {
3915+ for arm in &m.arms {
3916+ self.find_assign_expr(name, &arm.body, out);
3917+ }
3918+ }
3919+ _ => {}
3920+ }
3921+ }
3922+
3923+ /// The element type of a `Vec` binding, read off the first `push` on it.
3924+ fn vec_elem_hint(&mut self, l: &Local, rest: &[Stmt]) -> Option<Nim> {
3925+ if matches!(&l.pat, Pat::Type(_)) {
3926+ return None;
3927+ }
3928+ let Pat::Ident(id) = &l.pat else { return None };
3929+ let name = id.ident.to_string();
3930+ let init = l.init.as_ref()?;
3931+ let Expr::Call(c) = &*init.expr else { return None };
3932+ let Expr::Path(p) = &*c.func else { return None };
3933+ let segs: Vec<String> = p.path.segments.iter().map(|x| x.ident.to_string()).collect();
3934+ if segs.len() < 2 || segs[segs.len() - 2] != "Vec" {
3935+ return None;
3936+ }
3937+ if !matches!(segs[segs.len() - 1].as_str(), "new" | "with_capacity") {
3938+ return None;
3939+ }
3940+ let mut found = None;
3941+ self.find_push(&name, rest, &mut found);
3942+ found
3943+ }
3944+
3945+ /// The first `name.push(x)` anywhere in these statements, and the type of
3946+ /// what it pushes. The push is routinely inside a loop -- that is what a
3947+ /// `Vec` is usually filled by -- so this looks through nested blocks.
3948+ fn find_push(&mut self, name: &str, stmts: &[Stmt], out: &mut Option<Nim>) {
3949+ for st in stmts {
3950+ if out.is_some() {
3951+ return;
3952+ }
3953+ let e = match st {
3954+ Stmt::Expr(e, _) => e,
3955+ _ => continue,
3956+ };
3957+ self.find_push_expr(name, e, out);
3958+ }
3959+ }
3960+
3961+ fn find_push_expr(&mut self, name: &str, e: &Expr, out: &mut Option<Nim>) {
3962+ if out.is_some() {
3963+ return;
3964+ }
3965+ match e {
3966+ Expr::MethodCall(m) if m.method == "push" && m.args.len() == 1 => {
3967+ if let Expr::Path(r) = &*m.receiver {
3968+ if r.path.segments.len() == 1 && r.path.segments[0].ident == name {
3969+ *out = self.pushed_ty(&m.args[0]);
3970+ return;
3971+ }
3972+ }
3973+ self.find_push_expr(name, &m.receiver, out);
3974+ }
3975+ Expr::ForLoop(f) => self.find_push(name, &f.body.stmts, out),
3976+ Expr::While(w) => self.find_push(name, &w.body.stmts, out),
3977+ Expr::Loop(l) => self.find_push(name, &l.body.stmts, out),
3978+ Expr::Block(b) => self.find_push(name, &b.block.stmts, out),
3979+ Expr::Unsafe(u) => self.find_push(name, &u.block.stmts, out),
3980+ Expr::If(i) => {
3981+ self.find_push(name, &i.then_branch.stmts, out);
3982+ if let Some((_, els)) = &i.else_branch {
3983+ self.find_push_expr(name, els, out);
3984+ }
3985+ }
3986+ Expr::Match(m) => {
3987+ for arm in &m.arms {
3988+ self.find_push_expr(name, &arm.body, out);
3989+ }
3990+ }
3991+ _ => {}
3992+ }
3993+ }
3994+
3995+ /// The type of a pushed expression, without needing its operands in scope.
3996+ /// A cast says its own type, which is what most of these are; anything
3997+ /// else has to lower, and does so in a scratch buffer.
3998+ fn pushed_ty(&mut self, e: &Expr) -> Option<Nim> {
3999+ if let Expr::Cast(c) = e {
4000+ return self.map_ty(&c.ty).ok();
4001+ }
4002+ let save = self.out.len();
4003+ let t = self.expr(e).ok().and_then(|v| v.ty);
4004+ self.out.truncate(save);
4005+ t.map(|t| t.owned())
4006+ }
4007+
4008+ fn local_inner(&mut self, l: &Local) -> Result<(), String> {
38424009 let (name, mutable, ann): (String, bool, Option<Nim>) = match &l.pat {
38434010 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), None),
38444011 Pat::Type(t) => match &*t.pat {
@@ -3858,7 +4025,18 @@ impl Lowerer {
38584025 // `let x: T;` — Nim's `var x: T` zero-initialises, which Rust does
38594026 // not. Rust's own rules make reading it before assignment illegal,
38604027 // so the two agree on every program rustc accepts.
3861- let t = ann.ok_or("`let` without an initialiser needs a type annotation")?;
4028+ // Failing an annotation, the first assignment says the type --
4029+ // which is where Rust gets it too.
4030+ let t = match ann.or_else(|| self.deferred_hint.clone()) {
4031+ Some(t) => t,
4032+ None => {
4033+ return Err(
4034+ "`let` without an initialiser needs a type annotation, and \
4035+ no later assignment to it says one either"
4036+ .into(),
4037+ )
4038+ }
4039+ };
38624040 let t = t.owned();
38634041 self.line(&format!("var {}: {}", ident(&name), t.render()));
38644042 self.bind(&name, t);
@@ -3871,16 +4049,26 @@ impl Lowerer {
38714049 if !expressible(&init.expr) && name != "_" {
38724050 // The initialiser is an `if`/`match` whose arms are statements.
38734051 // Declare first, then let each arm assign into the binding.
3874- let t = ann
3875- .clone()
3876- .ok_or_else(|| {
3877- format!(
3878- "`let {name} = match/if ...` needs a type annotation: \
3879- its arms are statements, so the binding must be \
3880- declared before they run"
3881- )
3882- })?
3883- .owned();
4052+ // The annotation if there is one; otherwise the arms say it --
4053+ // Rust gives every arm the same type, so whichever one types
4054+ // answers for all of them.
4055+ let t = match ann.clone() {
4056+ Some(t) => t,
4057+ None => {
4058+ let save = self.out.len();
4059+ let probe = self.result_ty_inner(&init.expr);
4060+ self.out.truncate(save);
4061+ probe.ok_or_else(|| {
4062+ format!(
4063+ "`let {name} = match/if ...` needs a type annotation: \
4064+ its arms are statements, so the binding must be \
4065+ declared before they run, and none of them says \
4066+ what type it holds"
4067+ )
4068+ })?
4069+ }
4070+ }
4071+ .owned();
38844072 self.line(&format!("var {}: {}", ident(&name), t.render()));
38854073 self.bind(&name, t.clone());
38864074 let target = ident(&name);
@@ -4881,9 +5069,27 @@ impl Lowerer {
48815069 Pat::Guard(g) => (&*g.pat, Some(&*g.guard)),
48825070 p => (p, None),
48835071 };
4884- if guard.is_some() && binds(pat) {
4885- return Err("a `match` guard on a binding pattern is not \
4886- implemented yet"
5072+ // A guard on a plain binding -- `n if n <= 100` -- needs the
5073+ // binding in scope while the guard runs, and a Nim `elif` cannot
5074+ // introduce one. It does not have to: an irrefutable `n` is
5075+ // another name for the scrutinee, which is already in a
5076+ // temporary, so binding it for the guard's sake is enough.
5077+ let mut guard_scope = false;
5078+ if let (Some(_), Pat::Ident(id)) = (guard, pat) {
5079+ if id.subpat.is_none() {
5080+ self.push_scope();
5081+ self.bind(&id.ident.to_string(), t.clone());
5082+ self.bind_alias(
5083+ &id.ident.to_string(),
5084+ Alias::Value { code: name.to_string(), ty: Some(t.clone()) },
5085+ );
5086+ guard_scope = true;
5087+ }
5088+ }
5089+ if guard.is_some() && binds(pat) && !guard_scope {
5090+ return Err("a `match` guard on a destructuring pattern is not \
5091+ implemented yet: the bindings it introduces are not \
5092+ in scope where Nim evaluates the condition"
48875093 .into());
48885094 }
48895095 let test = self.pat_test(pat, name, t)?;
@@ -4921,6 +5127,9 @@ impl Lowerer {
49215127 self.indent -= 1;
49225128 self.arm_body_at(&arm.body, before)?;
49235129 self.pop_scope();
5130+ if guard_scope {
5131+ self.pop_scope();
5132+ }
49245133 if closed {
49255134 break;
49265135 }
@@ -5250,6 +5459,23 @@ impl Lowerer {
52505459 );
52515460 return Ok(Val::new(n, Some(t)));
52525461 }
5462+ // A float's `MAX` is its largest *finite* value.
5463+ // Nim's `high(float64)` is `inf`, which is a
5464+ // different number, so these are written out.
5465+ if let Nim::Prim(p) = &t {
5466+ if p.starts_with("float") {
5467+ let mag = if p == "float32" {
5468+ "3.4028234663852886e38"
5469+ } else {
5470+ "1.7976931348623157e308"
5471+ };
5472+ let sign = if name == "MIN" { "-" } else { "" };
5473+ return Ok(Val::new(
5474+ format!("{sign}{mag}'{}", if p == "float32" { "f32" } else { "f64" }),
5475+ Some(t),
5476+ ));
5477+ }
5478+ }
52535479 if t.is_integer() {
52545480 let f = if name == "MAX" { "high" } else { "low" };
52555481 return Ok(Val::new(
@@ -5688,6 +5914,13 @@ impl Lowerer {
56885914 Err("block expression with statements in value position is not implemented yet".into())
56895915 }
56905916 }
5917+ // An assignment is an expression in Rust, of type `()`. In Nim it
5918+ // is a statement, so it is emitted as one and the value it stands
5919+ // for is the unit -- which is what Rust's is.
5920+ Expr::Assign(_) | Expr::Break(_) | Expr::Continue(_) => {
5921+ self.expr_stmt(e)?;
5922+ Ok(Val::new(String::new(), Some(Nim::Unit)))
5923+ }
56915924 // A `match` in value position. Nim's `case` is an expression only
56925925 // when every arm is one, and Rust's arms routinely bind and
56935926 // destructure -- so instead the whole thing is lowered as the
@@ -6671,6 +6904,7 @@ impl Lowerer {
66716904 (Some("Vec"), "new", 0) | (Some("Vec"), "with_capacity", 1) => {
66726905 let elem = match expect {
66736906 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => (**e).clone(),
6907+ _ if self.vec_hint.is_some() => self.vec_hint.clone().unwrap(),
66746908 _ => {
66756909 return Err(
66766910 "`Vec::new()` needs a known element type; annotate the \
@@ -6863,6 +7097,19 @@ impl Lowerer {
68637097 }
68647098 }
68657099 }
7100+ // `cmp::max` / `cmp::min` are Nim's `max` / `min` on the same
7101+ // ordering.
7102+ "max" | "min" if codes.len() == 2 => {
7103+ // Both operands are the same type in Rust; the second is
7104+ // re-lowered against the first so an unsuffixed literal takes
7105+ // its width from it rather than defaulting to `i32`.
7106+ let t = args[0].ty.clone().or_else(|| args[1].ty.clone());
7107+ let b = self.expr_at(&c.args[1], t.as_ref())?;
7108+ return Ok(Val::new(
7109+ format!("{}({}, {})", name, codes[0], b.code),
7110+ t,
7111+ ));
7112+ }
68667113 // `assert!` and `unreachable!` expand to these. The message is
68677114 // already built by the expansion; `rsPanic` raises the same
68687115 // `RustPanic` every other panic here does, so the exit status and
@@ -7355,6 +7602,18 @@ impl Lowerer {
73557602 )
73567603 }
73577604 }
7605+ // The float classification predicates. Nim spells them the same
7606+ // way and they test the same IEEE-754 bits.
7607+ "is_finite" | "is_nan" | "is_infinite"
7608+ if matches!(&rt, Some(Nim::Prim(p)) if p.starts_with("float")) =>
7609+ {
7610+ let f = match name.as_str() {
7611+ "is_nan" => format!("({0} != {0})", recv.code),
7612+ "is_infinite" => format!("({0} == Inf or {0} == -Inf)", recv.code),
7613+ _ => format!("({0} == {0} and {0} != Inf and {0} != -Inf)", recv.code),
7614+ };
7615+ (f, Some(Nim::Prim("bool".into())))
7616+ }
73587617 "recip" if matches!(&rt, Some(Nim::Prim(p)) if p.starts_with("float")) => {
73597618 let t = rt.clone().unwrap();
73607619 (format!("({}(1.0) / {})", t.render(), recv.code), Some(t))
@@ -379,6 +379,10 @@ pub struct Lowerer {
379 /// missing the items that failed is not a translation of anything.379 /// missing the items that failed is not a translation of anything.
380 pub report: bool,380 pub report: bool,
381 errors: Vec<String>,381 errors: Vec<String>,
382+ /// Element type for an un-annotated `Vec` binding, from its first push.
383+ vec_hint: Option<Nim>,
384+ /// Type for a `let x;` with no initialiser, from its first assignment.
385+ deferred_hint: Option<Nim>,
382 /// Bounds discovered while rewriting `impl Trait` parameters. `rewrite_apit`386 /// Bounds discovered while rewriting `impl Trait` parameters. `rewrite_apit`
383 /// runs from `&self`, so they are parked here and folded into `bounds` by387 /// runs from `&self`, so they are parked here and folded into `bounds` by
384 /// whoever called it.388 /// whoever called it.
@@ -483,6 +487,8 @@ impl Lowerer {
483 enum_module: HashMap::new(),487 enum_module: HashMap::new(),
484 report: false,488 report: false,
485 errors: Vec::new(),489 errors: Vec::new(),
490+ vec_hint: None,
491+ deferred_hint: None,
486 pending_apit_bounds: std::cell::RefCell::new(Vec::new()),492 pending_apit_bounds: std::cell::RefCell::new(Vec::new()),
487 bounds: HashMap::new(),493 bounds: HashMap::new(),
488 const_names_at: std::cell::Cell::new(None),494 const_names_at: std::cell::Cell::new(None),
@@ -3756,6 +3762,7 @@ impl Lowerer {
3756 let mut tail = None;3762 let mut tail = None;
3757 for (i, st) in b.stmts.iter().enumerate() {3763 for (i, st) in b.stmts.iter().enumerate() {
3758 let last = i + 1 == n;3764 let last = i + 1 == n;
3765+ let rest = &b.stmts[i + 1..];
3759 match st {3766 match st {
3760 Stmt::Expr(e, None) if last && expressible(e) => {3767 Stmt::Expr(e, None) if last && expressible(e) => {
3761 tail = Some(self.expr_at(e, expect)?)3768 tail = Some(self.expr_at(e, expect)?)
@@ -3772,6 +3779,12 @@ impl Lowerer {
3772 None => self.stmt(st)?,3779 None => self.stmt(st)?,
3773 }3780 }
3774 }3781 }
3782+ Stmt::Local(l) => {
3783+ self.local(l, rest)?;
3784+ for lk in std::mem::take(&mut self.pending_locks) {
3785+ self.line(&format!("release({})", lk));
3786+ }
3787+ }
3775 _ => self.stmt(st)?,3788 _ => self.stmt(st)?,
3776 }3789 }
3777 }3790 }
@@ -3807,7 +3820,7 @@ impl Lowerer {
3807 3820
3808 fn stmt_inner(&mut self, s: &Stmt) -> Result<(), String> {3821 fn stmt_inner(&mut self, s: &Stmt) -> Result<(), String> {
3809 match s {3822 match s {
3810- Stmt::Local(l) => self.local(l),3823+ Stmt::Local(l) => self.local(l, &[]),
3811 Stmt::Expr(e, _) => {3824 Stmt::Expr(e, _) => {
3812 let v = self.expr_stmt(e)?;3825 let v = self.expr_stmt(e)?;
3813 if let Some(v) = v {3826 if let Some(v) = v {
@@ -3838,7 +3851,161 @@ impl Lowerer {
3838 }3851 }
3839 }3852 }
3840 3853
3841- fn local(&mut self, l: &Local) -> Result<(), String> {3854+ fn local(&mut self, l: &Local, rest: &[Stmt]) -> Result<(), String> {
3855+ // `let mut v = Vec::new();` has no element type of its own -- Rust
3856+ // takes it from the first `push`, and so does this. Only when there is
3857+ // no annotation and the initialiser is the empty constructor; anything
3858+ // else already says what it holds.
3859+ let hint = self.vec_elem_hint(l, rest);
3860+ let outer_hint = std::mem::replace(&mut self.vec_hint, hint);
3861+ let deferred = self.deferred_let_hint(l, rest);
3862+ let outer_def = std::mem::replace(&mut self.deferred_hint, deferred);
3863+ let r = self.local_inner(l);
3864+ self.vec_hint = outer_hint;
3865+ self.deferred_hint = outer_def;
3866+ return r;
3867+ }
3868+
3869+ /// The type of a `let x;` binding, read off the first assignment to it.
3870+ fn deferred_let_hint(&mut self, l: &Local, rest: &[Stmt]) -> Option<Nim> {
3871+ if l.init.is_some() || matches!(&l.pat, Pat::Type(_)) {
3872+ return None;
3873+ }
3874+ let Pat::Ident(id) = &l.pat else { return None };
3875+ let name = id.ident.to_string();
3876+ let mut found = None;
3877+ self.find_assign(&name, rest, &mut found);
3878+ found
3879+ }
3880+
3881+ fn find_assign(&mut self, name: &str, stmts: &[Stmt], out: &mut Option<Nim>) {
3882+ for st in stmts {
3883+ if out.is_some() {
3884+ return;
3885+ }
3886+ let Stmt::Expr(e, _) = st else { continue };
3887+ self.find_assign_expr(name, e, out);
3888+ }
3889+ }
3890+
3891+ fn find_assign_expr(&mut self, name: &str, e: &Expr, out: &mut Option<Nim>) {
3892+ if out.is_some() {
3893+ return;
3894+ }
3895+ match e {
3896+ Expr::Assign(a) => {
3897+ if let Expr::Path(p) = &*a.left {
3898+ if p.path.segments.len() == 1 && p.path.segments[0].ident == name {
3899+ *out = self.pushed_ty(&a.right);
3900+ }
3901+ }
3902+ }
3903+ Expr::ForLoop(f) => self.find_assign(name, &f.body.stmts, out),
3904+ Expr::While(w) => self.find_assign(name, &w.body.stmts, out),
3905+ Expr::Loop(l) => self.find_assign(name, &l.body.stmts, out),
3906+ Expr::Block(b) => self.find_assign(name, &b.block.stmts, out),
3907+ Expr::Unsafe(u) => self.find_assign(name, &u.block.stmts, out),
3908+ Expr::If(i) => {
3909+ self.find_assign(name, &i.then_branch.stmts, out);
3910+ if let Some((_, els)) = &i.else_branch {
3911+ self.find_assign_expr(name, els, out);
3912+ }
3913+ }
3914+ Expr::Match(m) => {
3915+ for arm in &m.arms {
3916+ self.find_assign_expr(name, &arm.body, out);
3917+ }
3918+ }
3919+ _ => {}
3920+ }
3921+ }
3922+
3923+ /// The element type of a `Vec` binding, read off the first `push` on it.
3924+ fn vec_elem_hint(&mut self, l: &Local, rest: &[Stmt]) -> Option<Nim> {
3925+ if matches!(&l.pat, Pat::Type(_)) {
3926+ return None;
3927+ }
3928+ let Pat::Ident(id) = &l.pat else { return None };
3929+ let name = id.ident.to_string();
3930+ let init = l.init.as_ref()?;
3931+ let Expr::Call(c) = &*init.expr else { return None };
3932+ let Expr::Path(p) = &*c.func else { return None };
3933+ let segs: Vec<String> = p.path.segments.iter().map(|x| x.ident.to_string()).collect();
3934+ if segs.len() < 2 || segs[segs.len() - 2] != "Vec" {
3935+ return None;
3936+ }
3937+ if !matches!(segs[segs.len() - 1].as_str(), "new" | "with_capacity") {
3938+ return None;
3939+ }
3940+ let mut found = None;
3941+ self.find_push(&name, rest, &mut found);
3942+ found
3943+ }
3944+
3945+ /// The first `name.push(x)` anywhere in these statements, and the type of
3946+ /// what it pushes. The push is routinely inside a loop -- that is what a
3947+ /// `Vec` is usually filled by -- so this looks through nested blocks.
3948+ fn find_push(&mut self, name: &str, stmts: &[Stmt], out: &mut Option<Nim>) {
3949+ for st in stmts {
3950+ if out.is_some() {
3951+ return;
3952+ }
3953+ let e = match st {
3954+ Stmt::Expr(e, _) => e,
3955+ _ => continue,
3956+ };
3957+ self.find_push_expr(name, e, out);
3958+ }
3959+ }
3960+
3961+ fn find_push_expr(&mut self, name: &str, e: &Expr, out: &mut Option<Nim>) {
3962+ if out.is_some() {
3963+ return;
3964+ }
3965+ match e {
3966+ Expr::MethodCall(m) if m.method == "push" && m.args.len() == 1 => {
3967+ if let Expr::Path(r) = &*m.receiver {
3968+ if r.path.segments.len() == 1 && r.path.segments[0].ident == name {
3969+ *out = self.pushed_ty(&m.args[0]);
3970+ return;
3971+ }
3972+ }
3973+ self.find_push_expr(name, &m.receiver, out);
3974+ }
3975+ Expr::ForLoop(f) => self.find_push(name, &f.body.stmts, out),
3976+ Expr::While(w) => self.find_push(name, &w.body.stmts, out),
3977+ Expr::Loop(l) => self.find_push(name, &l.body.stmts, out),
3978+ Expr::Block(b) => self.find_push(name, &b.block.stmts, out),
3979+ Expr::Unsafe(u) => self.find_push(name, &u.block.stmts, out),
3980+ Expr::If(i) => {
3981+ self.find_push(name, &i.then_branch.stmts, out);
3982+ if let Some((_, els)) = &i.else_branch {
3983+ self.find_push_expr(name, els, out);
3984+ }
3985+ }
3986+ Expr::Match(m) => {
3987+ for arm in &m.arms {
3988+ self.find_push_expr(name, &arm.body, out);
3989+ }
3990+ }
3991+ _ => {}
3992+ }
3993+ }
3994+
3995+ /// The type of a pushed expression, without needing its operands in scope.
3996+ /// A cast says its own type, which is what most of these are; anything
3997+ /// else has to lower, and does so in a scratch buffer.
3998+ fn pushed_ty(&mut self, e: &Expr) -> Option<Nim> {
3999+ if let Expr::Cast(c) = e {
4000+ return self.map_ty(&c.ty).ok();
4001+ }
4002+ let save = self.out.len();
4003+ let t = self.expr(e).ok().and_then(|v| v.ty);
4004+ self.out.truncate(save);
4005+ t.map(|t| t.owned())
4006+ }
4007+
4008+ fn local_inner(&mut self, l: &Local) -> Result<(), String> {
3842 let (name, mutable, ann): (String, bool, Option<Nim>) = match &l.pat {4009 let (name, mutable, ann): (String, bool, Option<Nim>) = match &l.pat {
3843 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), None),4010 Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), None),
3844 Pat::Type(t) => match &*t.pat {4011 Pat::Type(t) => match &*t.pat {
@@ -3858,7 +4025,18 @@ impl Lowerer {
3858 // `let x: T;` — Nim's `var x: T` zero-initialises, which Rust does4025 // `let x: T;` — Nim's `var x: T` zero-initialises, which Rust does
3859 // not. Rust's own rules make reading it before assignment illegal,4026 // not. Rust's own rules make reading it before assignment illegal,
3860 // so the two agree on every program rustc accepts.4027 // so the two agree on every program rustc accepts.
3861- let t = ann.ok_or("`let` without an initialiser needs a type annotation")?;4028+ // Failing an annotation, the first assignment says the type --
4029+ // which is where Rust gets it too.
4030+ let t = match ann.or_else(|| self.deferred_hint.clone()) {
4031+ Some(t) => t,
4032+ None => {
4033+ return Err(
4034+ "`let` without an initialiser needs a type annotation, and \
4035+ no later assignment to it says one either"
4036+ .into(),
4037+ )
4038+ }
4039+ };
3862 let t = t.owned();4040 let t = t.owned();
3863 self.line(&format!("var {}: {}", ident(&name), t.render()));4041 self.line(&format!("var {}: {}", ident(&name), t.render()));
3864 self.bind(&name, t);4042 self.bind(&name, t);
@@ -3871,16 +4049,26 @@ impl Lowerer {
3871 if !expressible(&init.expr) && name != "_" {4049 if !expressible(&init.expr) && name != "_" {
3872 // The initialiser is an `if`/`match` whose arms are statements.4050 // The initialiser is an `if`/`match` whose arms are statements.
3873 // Declare first, then let each arm assign into the binding.4051 // Declare first, then let each arm assign into the binding.
3874- let t = ann4052+ // The annotation if there is one; otherwise the arms say it --
3875- .clone()4053+ // Rust gives every arm the same type, so whichever one types
3876- .ok_or_else(|| {4054+ // answers for all of them.
3877- format!(4055+ let t = match ann.clone() {
3878- "`let {name} = match/if ...` needs a type annotation: \4056+ Some(t) => t,
3879- its arms are statements, so the binding must be \4057+ None => {
3880- declared before they run"4058+ let save = self.out.len();
3881- )4059+ let probe = self.result_ty_inner(&init.expr);
3882- })?4060+ self.out.truncate(save);
3883- .owned();4061+ probe.ok_or_else(|| {
4062+ format!(
4063+ "`let {name} = match/if ...` needs a type annotation: \
4064+ its arms are statements, so the binding must be \
4065+ declared before they run, and none of them says \
4066+ what type it holds"
4067+ )
4068+ })?
4069+ }
4070+ }
4071+ .owned();
3884 self.line(&format!("var {}: {}", ident(&name), t.render()));4072 self.line(&format!("var {}: {}", ident(&name), t.render()));
3885 self.bind(&name, t.clone());4073 self.bind(&name, t.clone());
3886 let target = ident(&name);4074 let target = ident(&name);
@@ -4881,9 +5069,27 @@ impl Lowerer {
4881 Pat::Guard(g) => (&*g.pat, Some(&*g.guard)),5069 Pat::Guard(g) => (&*g.pat, Some(&*g.guard)),
4882 p => (p, None),5070 p => (p, None),
4883 };5071 };
4884- if guard.is_some() && binds(pat) {5072+ // A guard on a plain binding -- `n if n <= 100` -- needs the
4885- return Err("a `match` guard on a binding pattern is not \5073+ // binding in scope while the guard runs, and a Nim `elif` cannot
4886- implemented yet"5074+ // introduce one. It does not have to: an irrefutable `n` is
5075+ // another name for the scrutinee, which is already in a
5076+ // temporary, so binding it for the guard's sake is enough.
5077+ let mut guard_scope = false;
5078+ if let (Some(_), Pat::Ident(id)) = (guard, pat) {
5079+ if id.subpat.is_none() {
5080+ self.push_scope();
5081+ self.bind(&id.ident.to_string(), t.clone());
5082+ self.bind_alias(
5083+ &id.ident.to_string(),
5084+ Alias::Value { code: name.to_string(), ty: Some(t.clone()) },
5085+ );
5086+ guard_scope = true;
5087+ }
5088+ }
5089+ if guard.is_some() && binds(pat) && !guard_scope {
5090+ return Err("a `match` guard on a destructuring pattern is not \
5091+ implemented yet: the bindings it introduces are not \
5092+ in scope where Nim evaluates the condition"
4887 .into());5093 .into());
4888 }5094 }
4889 let test = self.pat_test(pat, name, t)?;5095 let test = self.pat_test(pat, name, t)?;
@@ -4921,6 +5127,9 @@ impl Lowerer {
4921 self.indent -= 1;5127 self.indent -= 1;
4922 self.arm_body_at(&arm.body, before)?;5128 self.arm_body_at(&arm.body, before)?;
4923 self.pop_scope();5129 self.pop_scope();
5130+ if guard_scope {
5131+ self.pop_scope();
5132+ }
4924 if closed {5133 if closed {
4925 break;5134 break;
4926 }5135 }
@@ -5250,6 +5459,23 @@ impl Lowerer {
5250 );5459 );
5251 return Ok(Val::new(n, Some(t)));5460 return Ok(Val::new(n, Some(t)));
5252 }5461 }
5462+ // A float's `MAX` is its largest *finite* value.
5463+ // Nim's `high(float64)` is `inf`, which is a
5464+ // different number, so these are written out.
5465+ if let Nim::Prim(p) = &t {
5466+ if p.starts_with("float") {
5467+ let mag = if p == "float32" {
5468+ "3.4028234663852886e38"
5469+ } else {
5470+ "1.7976931348623157e308"
5471+ };
5472+ let sign = if name == "MIN" { "-" } else { "" };
5473+ return Ok(Val::new(
5474+ format!("{sign}{mag}'{}", if p == "float32" { "f32" } else { "f64" }),
5475+ Some(t),
5476+ ));
5477+ }
5478+ }
5253 if t.is_integer() {5479 if t.is_integer() {
5254 let f = if name == "MAX" { "high" } else { "low" };5480 let f = if name == "MAX" { "high" } else { "low" };
5255 return Ok(Val::new(5481 return Ok(Val::new(
@@ -5688,6 +5914,13 @@ impl Lowerer {
5688 Err("block expression with statements in value position is not implemented yet".into())5914 Err("block expression with statements in value position is not implemented yet".into())
5689 }5915 }
5690 }5916 }
5917+ // An assignment is an expression in Rust, of type `()`. In Nim it
5918+ // is a statement, so it is emitted as one and the value it stands
5919+ // for is the unit -- which is what Rust's is.
5920+ Expr::Assign(_) | Expr::Break(_) | Expr::Continue(_) => {
5921+ self.expr_stmt(e)?;
5922+ Ok(Val::new(String::new(), Some(Nim::Unit)))
5923+ }
5691 // A `match` in value position. Nim's `case` is an expression only5924 // A `match` in value position. Nim's `case` is an expression only
5692 // when every arm is one, and Rust's arms routinely bind and5925 // when every arm is one, and Rust's arms routinely bind and
5693 // destructure -- so instead the whole thing is lowered as the5926 // destructure -- so instead the whole thing is lowered as the
@@ -6671,6 +6904,7 @@ impl Lowerer {
6671 (Some("Vec"), "new", 0) | (Some("Vec"), "with_capacity", 1) => {6904 (Some("Vec"), "new", 0) | (Some("Vec"), "with_capacity", 1) => {
6672 let elem = match expect {6905 let elem = match expect {
6673 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => (**e).clone(),6906 Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => (**e).clone(),
6907+ _ if self.vec_hint.is_some() => self.vec_hint.clone().unwrap(),
6674 _ => {6908 _ => {
6675 return Err(6909 return Err(
6676 "`Vec::new()` needs a known element type; annotate the \6910 "`Vec::new()` needs a known element type; annotate the \
@@ -6863,6 +7097,19 @@ impl Lowerer {
6863 }7097 }
6864 }7098 }
6865 }7099 }
7100+ // `cmp::max` / `cmp::min` are Nim's `max` / `min` on the same
7101+ // ordering.
7102+ "max" | "min" if codes.len() == 2 => {
7103+ // Both operands are the same type in Rust; the second is
7104+ // re-lowered against the first so an unsuffixed literal takes
7105+ // its width from it rather than defaulting to `i32`.
7106+ let t = args[0].ty.clone().or_else(|| args[1].ty.clone());
7107+ let b = self.expr_at(&c.args[1], t.as_ref())?;
7108+ return Ok(Val::new(
7109+ format!("{}({}, {})", name, codes[0], b.code),
7110+ t,
7111+ ));
7112+ }
6866 // `assert!` and `unreachable!` expand to these. The message is7113 // `assert!` and `unreachable!` expand to these. The message is
6867 // already built by the expansion; `rsPanic` raises the same7114 // already built by the expansion; `rsPanic` raises the same
6868 // `RustPanic` every other panic here does, so the exit status and7115 // `RustPanic` every other panic here does, so the exit status and
@@ -7355,6 +7602,18 @@ impl Lowerer {
7355 )7602 )
7356 }7603 }
7357 }7604 }
7605+ // The float classification predicates. Nim spells them the same
7606+ // way and they test the same IEEE-754 bits.
7607+ "is_finite" | "is_nan" | "is_infinite"
7608+ if matches!(&rt, Some(Nim::Prim(p)) if p.starts_with("float")) =>
7609+ {
7610+ let f = match name.as_str() {
7611+ "is_nan" => format!("({0} != {0})", recv.code),
7612+ "is_infinite" => format!("({0} == Inf or {0} == -Inf)", recv.code),
7613+ _ => format!("({0} == {0} and {0} != Inf and {0} != -Inf)", recv.code),
7614+ };
7615+ (f, Some(Nim::Prim("bool".into())))
7616+ }
7358 "recip" if matches!(&rt, Some(Nim::Prim(p)) if p.starts_with("float")) => {7617 "recip" if matches!(&rt, Some(Nim::Prim(p)) if p.starts_with("float")) => {
7359 let t = rt.clone().unwrap();7618 let t = rt.clone().unwrap();
7360 (format!("({}(1.0) / {})", t.render(), recv.code), Some(t))7619 (format!("({}(1.0) / {})", t.render(), recv.code), Some(t))
modified tests/cases/066-std-subset.rs +10 -0
@@ -46,6 +46,16 @@ fn main() {
4646 println!("{} {}", 4.0f64.recip(), 0.5f32.recip());
4747 println!("{} {}", 1.0f32.to_bits(), 2.0f64.to_bits());
4848
49+ // A float's MAX is its largest *finite* value, not infinity. Compared
50+ // rather than printed: Rust's `{}` writes these in full decimal and Nim's
51+ // `$` in scientific notation, which is a separate gap (see DESIGN.md).
52+ println!("{} {}", f64::MAX > 1.0e308, f64::MAX.is_finite());
53+ println!("{} {}", f64::MIN < -1.0e308, f32::MAX > 3.0e38);
54+ println!("{}", f32::MAX.is_finite());
55+
56+ // `cmp::max` / `cmp::min`.
57+ println!("{} {}", std::cmp::max(3u32, 9), std::cmp::min(3u32, 9));
58+
4959 // `#[derive(PartialOrd)]` compares discriminants.
5060 println!("{} {}", Rank::Low < Rank::High, Rank::Mid > Rank::High);
5161 }
@@ -46,6 +46,16 @@ fn main() {
46 println!("{} {}", 4.0f64.recip(), 0.5f32.recip());46 println!("{} {}", 4.0f64.recip(), 0.5f32.recip());
47 println!("{} {}", 1.0f32.to_bits(), 2.0f64.to_bits());47 println!("{} {}", 1.0f32.to_bits(), 2.0f64.to_bits());
48 48
49+ // A float's MAX is its largest *finite* value, not infinity. Compared
50+ // rather than printed: Rust's `{}` writes these in full decimal and Nim's
51+ // `$` in scientific notation, which is a separate gap (see DESIGN.md).
52+ println!("{} {}", f64::MAX > 1.0e308, f64::MAX.is_finite());
53+ println!("{} {}", f64::MIN < -1.0e308, f32::MAX > 3.0e38);
54+ println!("{}", f32::MAX.is_finite());
55+
56+ // `cmp::max` / `cmp::min`.
57+ println!("{} {}", std::cmp::max(3u32, 9), std::cmp::min(3u32, 9));
58+
49 // `#[derive(PartialOrd)]` compares discriminants.59 // `#[derive(PartialOrd)]` compares discriminants.
50 println!("{} {}", Rank::Low < Rank::High, Rank::Mid > Rank::High);60 println!("{} {}", Rank::Low < Rank::High, Rank::Mid > Rank::High);
51 }61 }
added tests/cases/067-guarded-binding.rs +25 -0
new file mode 100644
@@ -0,0 +1,25 @@
1+// A `match` guard on a plain binding. The binding has to be in scope while
2+// the guard runs, and a Nim `elif` cannot introduce one — it does not have to,
3+// because an irrefutable `n` is another name for the scrutinee.
4+fn clamp_low(x: u32) -> u32 {
5+ match x {
6+ n if n <= 100 => 100,
7+ n => n,
8+ }
9+}
10+
11+fn describe(x: i32) -> String {
12+ match x {
13+ n if n < 0 => format!("neg {}", -n),
14+ 0 => String::from("zero"),
15+ n if n % 2 == 0 => format!("even {}", n),
16+ n => format!("odd {}", n),
17+ }
18+}
19+
20+fn main() {
21+ println!("{} {} {}", clamp_low(3), clamp_low(100), clamp_low(250));
22+ for v in [-4i32, 0, 7, 12] {
23+ println!("{}", describe(v));
24+ }
25+}
new file mode 100644
@@ -0,0 +1,25 @@
1+// A `match` guard on a plain binding. The binding has to be in scope while
2+// the guard runs, and a Nim `elif` cannot introduce one — it does not have to,
3+// because an irrefutable `n` is another name for the scrutinee.
4+fn clamp_low(x: u32) -> u32 {
5+ match x {
6+ n if n <= 100 => 100,
7+ n => n,
8+ }
9+}
10+
11+fn describe(x: i32) -> String {
12+ match x {
13+ n if n < 0 => format!("neg {}", -n),
14+ 0 => String::from("zero"),
15+ n if n % 2 == 0 => format!("even {}", n),
16+ n => format!("odd {}", n),
17+ }
18+}
19+
20+fn main() {
21+ println!("{} {} {}", clamp_low(3), clamp_low(100), clamp_low(250));
22+ for v in [-4i32, 0, 7, 12] {
23+ println!("{}", describe(v));
24+ }
25+}