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>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 | ||
| 1310 | 1310 | global match, and qualify the emitted Nim name where a bare one would clash. |
| 1311 | 1311 | That is a real change to the shape of the lowering, not an incremental patch, |
| 1312 | 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. | |
| @@ -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 { | ||
| 379 | 379 | /// missing the items that failed is not a translation of anything. |
| 380 | 380 | pub report: bool, |
| 381 | 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 | 386 | /// Bounds discovered while rewriting `impl Trait` parameters. `rewrite_apit` |
| 383 | 387 | /// runs from `&self`, so they are parked here and folded into `bounds` by |
| 384 | 388 | /// whoever called it. |
| @@ -483,6 +487,8 @@ impl Lowerer { | ||
| 483 | 487 | enum_module: HashMap::new(), |
| 484 | 488 | report: false, |
| 485 | 489 | errors: Vec::new(), |
| 490 | + vec_hint: None, | |
| 491 | + deferred_hint: None, | |
| 486 | 492 | pending_apit_bounds: std::cell::RefCell::new(Vec::new()), |
| 487 | 493 | bounds: HashMap::new(), |
| 488 | 494 | const_names_at: std::cell::Cell::new(None), |
| @@ -3756,6 +3762,7 @@ impl Lowerer { | ||
| 3756 | 3762 | let mut tail = None; |
| 3757 | 3763 | for (i, st) in b.stmts.iter().enumerate() { |
| 3758 | 3764 | let last = i + 1 == n; |
| 3765 | + let rest = &b.stmts[i + 1..]; | |
| 3759 | 3766 | match st { |
| 3760 | 3767 | Stmt::Expr(e, None) if last && expressible(e) => { |
| 3761 | 3768 | tail = Some(self.expr_at(e, expect)?) |
| @@ -3772,6 +3779,12 @@ impl Lowerer { | ||
| 3772 | 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 | 3788 | _ => self.stmt(st)?, |
| 3776 | 3789 | } |
| 3777 | 3790 | } |
| @@ -3807,7 +3820,7 @@ impl Lowerer { | ||
| 3807 | 3820 | |
| 3808 | 3821 | fn stmt_inner(&mut self, s: &Stmt) -> Result<(), String> { |
| 3809 | 3822 | match s { |
| 3810 | - Stmt::Local(l) => self.local(l), | |
| 3823 | + Stmt::Local(l) => self.local(l, &[]), | |
| 3811 | 3824 | Stmt::Expr(e, _) => { |
| 3812 | 3825 | let v = self.expr_stmt(e)?; |
| 3813 | 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 | 4009 | let (name, mutable, ann): (String, bool, Option<Nim>) = match &l.pat { |
| 3843 | 4010 | Pat::Ident(i) => (i.ident.to_string(), i.mutability.is_some(), None), |
| 3844 | 4011 | Pat::Type(t) => match &*t.pat { |
| @@ -3858,7 +4025,18 @@ impl Lowerer { | ||
| 3858 | 4025 | // `let x: T;` — Nim's `var x: T` zero-initialises, which Rust does |
| 3859 | 4026 | // not. Rust's own rules make reading it before assignment illegal, |
| 3860 | 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 | 4040 | let t = t.owned(); |
| 3863 | 4041 | self.line(&format!("var {}: {}", ident(&name), t.render())); |
| 3864 | 4042 | self.bind(&name, t); |
| @@ -3871,16 +4049,26 @@ impl Lowerer { | ||
| 3871 | 4049 | if !expressible(&init.expr) && name != "_" { |
| 3872 | 4050 | // The initialiser is an `if`/`match` whose arms are statements. |
| 3873 | 4051 | // 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(); | |
| 3884 | 4072 | self.line(&format!("var {}: {}", ident(&name), t.render())); |
| 3885 | 4073 | self.bind(&name, t.clone()); |
| 3886 | 4074 | let target = ident(&name); |
| @@ -4881,9 +5069,27 @@ impl Lowerer { | ||
| 4881 | 5069 | Pat::Guard(g) => (&*g.pat, Some(&*g.guard)), |
| 4882 | 5070 | p => (p, None), |
| 4883 | 5071 | }; |
| 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" | |
| 4887 | 5093 | .into()); |
| 4888 | 5094 | } |
| 4889 | 5095 | let test = self.pat_test(pat, name, t)?; |
| @@ -4921,6 +5127,9 @@ impl Lowerer { | ||
| 4921 | 5127 | self.indent -= 1; |
| 4922 | 5128 | self.arm_body_at(&arm.body, before)?; |
| 4923 | 5129 | self.pop_scope(); |
| 5130 | + if guard_scope { | |
| 5131 | + self.pop_scope(); | |
| 5132 | + } | |
| 4924 | 5133 | if closed { |
| 4925 | 5134 | break; |
| 4926 | 5135 | } |
| @@ -5250,6 +5459,23 @@ impl Lowerer { | ||
| 5250 | 5459 | ); |
| 5251 | 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 | 5479 | if t.is_integer() { |
| 5254 | 5480 | let f = if name == "MAX" { "high" } else { "low" }; |
| 5255 | 5481 | return Ok(Val::new( |
| @@ -5688,6 +5914,13 @@ impl Lowerer { | ||
| 5688 | 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 | 5924 | // A `match` in value position. Nim's `case` is an expression only |
| 5692 | 5925 | // when every arm is one, and Rust's arms routinely bind and |
| 5693 | 5926 | // destructure -- so instead the whole thing is lowered as the |
| @@ -6671,6 +6904,7 @@ impl Lowerer { | ||
| 6671 | 6904 | (Some("Vec"), "new", 0) | (Some("Vec"), "with_capacity", 1) => { |
| 6672 | 6905 | let elem = match expect { |
| 6673 | 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 | 6909 | return Err( |
| 6676 | 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 | 7113 | // `assert!` and `unreachable!` expand to these. The message is |
| 6867 | 7114 | // already built by the expansion; `rsPanic` raises the same |
| 6868 | 7115 | // `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 | 7617 | "recip" if matches!(&rt, Some(Nim::Prim(p)) if p.starts_with("float")) => { |
| 7359 | 7618 | let t = rt.clone().unwrap(); |
| 7360 | 7619 | (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` by | 387 | /// 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 does | 4025 | // `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 = ann | 4052 | + // 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 only | 5924 | // A `match` in value position. Nim's `case` is an expression only |
| 5692 | // when every arm is one, and Rust's arms routinely bind and | 5925 | // when every arm is one, and Rust's arms routinely bind and |
| 5693 | // destructure -- so instead the whole thing is lowered as the | 5926 | // 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 is | 7113 | // `assert!` and `unreachable!` expand to these. The message is |
| 6867 | // already built by the expansion; `rsPanic` raises the same | 7114 | // already built by the expansion; `rsPanic` raises the same |
| 6868 | // `RustPanic` every other panic here does, so the exit status and | 7115 | // `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() { | ||
| 46 | 46 | println!("{} {}", 4.0f64.recip(), 0.5f32.recip()); |
| 47 | 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 | 59 | // `#[derive(PartialOrd)]` compares discriminants. |
| 50 | 60 | println!("{} {}", Rank::Low < Rank::High, Rank::Mid > Rank::High); |
| 51 | 61 | } |
| @@ -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 | +} | ||