Infer integer widths from use, as Rust does
Five inferences, each reading from where Rust reads it, found by translating
`color_quant` end to end:
* `let mut i = 0;` takes its type from the first typed value the binding is
compared or combined with. A bare literal says only what Rust *defaults*
to, and `while i < samplepixels` is what makes it a `usize`.
* That scan sees bindings declared later and inside loops, which is where
the peer usually is.
* `vec![0; 256]` into a `Vec<usize>` is a `usize` zero: the element takes
its type from the expected `Vec<T>`, and lowering the literal first made
it an `i32`.
* `type Neuron = Quad<f64>;` used as `Neuron { .. }`. An alias is expanded
rather than emitted, so the name does not exist in Nim -- but the type it
stands for does, and it carries the generic arguments too.
* `match n { 0 => 1, n => n }` is `n`'s type. Rust gives every arm the same
one, but an arm that is a bare literal only reports a default, so an arm
carrying a real type is preferred.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>2693452 parent: 71ee2a9 modified
src/lower.rs +239 -7 | @@ -402,6 +402,8 @@ pub struct Lowerer { | ||
| 402 | 402 | vec_hint: Option<Nim>, |
| 403 | 403 | /// Type for a `let x;` with no initialiser, from its first assignment. |
| 404 | 404 | deferred_hint: Option<Nim>, |
| 405 | + /// Types of bindings a hint scan will meet before they are lowered. | |
| 406 | + pending_lets: HashMap<String, Nim>, | |
| 405 | 407 | /// Bounds discovered while rewriting `impl Trait` parameters. `rewrite_apit` |
| 406 | 408 | /// runs from `&self`, so they are parked here and folded into `bounds` by |
| 407 | 409 | /// whoever called it. |
| @@ -508,6 +510,7 @@ impl Lowerer { | ||
| 508 | 510 | errors: Vec::new(), |
| 509 | 511 | vec_hint: None, |
| 510 | 512 | deferred_hint: None, |
| 513 | + pending_lets: HashMap::new(), | |
| 511 | 514 | pending_apit_bounds: std::cell::RefCell::new(Vec::new()), |
| 512 | 515 | bounds: HashMap::new(), |
| 513 | 516 | const_names_at: std::cell::Cell::new(None), |
| @@ -2327,16 +2330,25 @@ impl Lowerer { | ||
| 2327 | 2330 | Expr::Match(m) => { |
| 2328 | 2331 | let scrut = self.expr(&m.expr).ok()?; |
| 2329 | 2332 | let st = scrut.ty.clone()?; |
| 2333 | + // Rust gives every arm the same type, but an arm that is a | |
| 2334 | + // bare literal only says what that literal *defaults* to. So | |
| 2335 | + // an arm that carries a real type is preferred: in | |
| 2336 | + // `match n { 0 => 1, n => n }` the answer is `n`'s type, and | |
| 2337 | + // taking the first arm would make it `i32`. | |
| 2338 | + let mut fallback = None; | |
| 2330 | 2339 | for arm in &m.arms { |
| 2331 | 2340 | self.push_scope(); |
| 2332 | 2341 | let bound = self.pat_bind(&arm.pat, &scrut.code, &st).is_ok(); |
| 2333 | 2342 | let t = if bound { self.result_ty_inner(&arm.body) } else { None }; |
| 2334 | 2343 | self.pop_scope(); |
| 2335 | - if t.is_some() { | |
| 2336 | - return t; | |
| 2344 | + let Some(t) = t else { continue }; | |
| 2345 | + if is_bare_literal(&arm.body) { | |
| 2346 | + fallback.get_or_insert(t); | |
| 2347 | + } else { | |
| 2348 | + return Some(t); | |
| 2337 | 2349 | } |
| 2338 | 2350 | } |
| 2339 | - None | |
| 2351 | + fallback | |
| 2340 | 2352 | } |
| 2341 | 2353 | Expr::If(i) => { |
| 2342 | 2354 | let then = i.then_branch.stmts.last().and_then(|st| match st { |
| @@ -3913,7 +3925,9 @@ impl Lowerer { | ||
| 3913 | 3925 | // else already says what it holds. |
| 3914 | 3926 | let hint = self.vec_elem_hint(l, rest); |
| 3915 | 3927 | let outer_hint = std::mem::replace(&mut self.vec_hint, hint); |
| 3916 | - let deferred = self.deferred_let_hint(l, rest); | |
| 3928 | + let deferred = self | |
| 3929 | + .deferred_let_hint(l, rest) | |
| 3930 | + .or_else(|| self.literal_let_hint(l, rest)); | |
| 3917 | 3931 | let outer_def = std::mem::replace(&mut self.deferred_hint, deferred); |
| 3918 | 3932 | let r = self.local_inner(l); |
| 3919 | 3933 | self.vec_hint = outer_hint; |
| @@ -3921,6 +3935,163 @@ impl Lowerer { | ||
| 3921 | 3935 | return r; |
| 3922 | 3936 | } |
| 3923 | 3937 | |
| 3938 | + /// The type of `let x = 0;` -- an unsuffixed literal, which says only what | |
| 3939 | + /// Rust *defaults* to -- read off the first place the binding meets a | |
| 3940 | + /// typed value. `let mut i = 0; while i < samplepixels` makes `i` a | |
| 3941 | + /// `usize` in Rust, and for the same reason here. | |
| 3942 | + fn literal_let_hint(&mut self, l: &Local, rest: &[Stmt]) -> Option<Nim> { | |
| 3943 | + if matches!(&l.pat, Pat::Type(_)) { | |
| 3944 | + return None; | |
| 3945 | + } | |
| 3946 | + let Pat::Ident(id) = &l.pat else { return None }; | |
| 3947 | + let init = l.init.as_ref()?; | |
| 3948 | + if !is_bare_literal(&init.expr) { | |
| 3949 | + return None; | |
| 3950 | + } | |
| 3951 | + let name = id.ident.to_string(); | |
| 3952 | + // Types for bindings this scan will meet before they are lowered. | |
| 3953 | + let outer = std::mem::take(&mut self.pending_lets); | |
| 3954 | + self.collect_pending_lets(rest); | |
| 3955 | + let mut found = None; | |
| 3956 | + self.find_peer(&name, rest, &mut found); | |
| 3957 | + self.pending_lets = outer; | |
| 3958 | + found.filter(|t| t.is_integer() || matches!(t, Nim::Prim(p) if p.starts_with("float"))) | |
| 3959 | + } | |
| 3960 | + | |
| 3961 | + /// Record what each later `let` in these statements binds, for peers that | |
| 3962 | + /// are not in scope yet. Only the ones whose type is plain to see. | |
| 3963 | + fn collect_pending_lets(&mut self, stmts: &[Stmt]) { | |
| 3964 | + for st in stmts { | |
| 3965 | + match st { | |
| 3966 | + Stmt::Local(l) => { | |
| 3967 | + let (n, ann) = match &l.pat { | |
| 3968 | + Pat::Ident(i) => (i.ident.to_string(), None), | |
| 3969 | + Pat::Type(t) => match &*t.pat { | |
| 3970 | + Pat::Ident(i) => { | |
| 3971 | + (i.ident.to_string(), self.map_ty(&t.ty).ok()) | |
| 3972 | + } | |
| 3973 | + _ => continue, | |
| 3974 | + }, | |
| 3975 | + _ => continue, | |
| 3976 | + }; | |
| 3977 | + let t = ann.or_else(|| { | |
| 3978 | + let i = l.init.as_ref()?; | |
| 3979 | + if is_bare_literal(&i.expr) { | |
| 3980 | + return None; | |
| 3981 | + } | |
| 3982 | + self.pushed_ty(&i.expr) | |
| 3983 | + }); | |
| 3984 | + if let Some(t) = t { | |
| 3985 | + self.pending_lets.entry(n).or_insert(t); | |
| 3986 | + } | |
| 3987 | + } | |
| 3988 | + Stmt::Expr(e, _) => match e { | |
| 3989 | + Expr::ForLoop(f) => { | |
| 3990 | + // A `for i in 0..n` binding is typed by the range's | |
| 3991 | + // bounds, and is a peer like any other. | |
| 3992 | + if let (Pat::Ident(id), Expr::Range(r)) = (&*f.pat, strip_parens(&f.expr)) { | |
| 3993 | + let b = r.end.as_deref().or(r.start.as_deref()); | |
| 3994 | + if let Some(b) = b { | |
| 3995 | + if !is_bare_literal(b) { | |
| 3996 | + if let Some(t) = self.pushed_ty(b) { | |
| 3997 | + self.pending_lets | |
| 3998 | + .entry(id.ident.to_string()) | |
| 3999 | + .or_insert(t); | |
| 4000 | + } | |
| 4001 | + } | |
| 4002 | + } | |
| 4003 | + } | |
| 4004 | + self.collect_pending_lets(&f.body.stmts) | |
| 4005 | + } | |
| 4006 | + Expr::While(w) => self.collect_pending_lets(&w.body.stmts), | |
| 4007 | + Expr::Loop(l) => self.collect_pending_lets(&l.body.stmts), | |
| 4008 | + Expr::Block(b) => self.collect_pending_lets(&b.block.stmts), | |
| 4009 | + _ => {} | |
| 4010 | + }, | |
| 4011 | + _ => {} | |
| 4012 | + } | |
| 4013 | + } | |
| 4014 | + } | |
| 4015 | + | |
| 4016 | + /// The type of whatever `name` is first compared or combined with. | |
| 4017 | + fn find_peer(&mut self, name: &str, stmts: &[Stmt], out: &mut Option<Nim>) { | |
| 4018 | + for st in stmts { | |
| 4019 | + if out.is_some() { | |
| 4020 | + return; | |
| 4021 | + } | |
| 4022 | + match st { | |
| 4023 | + Stmt::Expr(e, _) => self.find_peer_expr(name, e, out), | |
| 4024 | + Stmt::Local(l) => { | |
| 4025 | + if let Some(i) = &l.init { | |
| 4026 | + self.find_peer_expr(name, &i.expr, out); | |
| 4027 | + } | |
| 4028 | + } | |
| 4029 | + _ => {} | |
| 4030 | + } | |
| 4031 | + } | |
| 4032 | + } | |
| 4033 | + | |
| 4034 | + fn find_peer_expr(&mut self, name: &str, e: &Expr, out: &mut Option<Nim>) { | |
| 4035 | + if out.is_some() { | |
| 4036 | + return; | |
| 4037 | + } | |
| 4038 | + let names = |x: &Expr| { | |
| 4039 | + matches!(x, Expr::Path(p) | |
| 4040 | + if p.path.segments.len() == 1 && p.path.segments[0].ident == name) | |
| 4041 | + }; | |
| 4042 | + match e { | |
| 4043 | + Expr::Binary(b) => { | |
| 4044 | + let other = if names(&b.left) { | |
| 4045 | + Some(&*b.right) | |
| 4046 | + } else if names(&b.right) { | |
| 4047 | + Some(&*b.left) | |
| 4048 | + } else { | |
| 4049 | + None | |
| 4050 | + }; | |
| 4051 | + if let Some(o) = other { | |
| 4052 | + if !is_bare_literal(o) { | |
| 4053 | + if let Some(t) = self.pushed_ty(o) { | |
| 4054 | + *out = Some(t); | |
| 4055 | + return; | |
| 4056 | + } | |
| 4057 | + // The peer may be declared *after* this binding and | |
| 4058 | + // inside a loop, so it is not in scope yet. Its own | |
| 4059 | + // `let` says what it is. | |
| 4060 | + if let Expr::Path(p) = strip_parens(o) { | |
| 4061 | + if p.path.segments.len() == 1 { | |
| 4062 | + let n = p.path.segments[0].ident.to_string(); | |
| 4063 | + if let Some(t) = self.pending_lets.get(&n).cloned() { | |
| 4064 | + *out = Some(t); | |
| 4065 | + return; | |
| 4066 | + } | |
| 4067 | + } | |
| 4068 | + } | |
| 4069 | + } | |
| 4070 | + } | |
| 4071 | + self.find_peer_expr(name, &b.left, out); | |
| 4072 | + self.find_peer_expr(name, &b.right, out); | |
| 4073 | + } | |
| 4074 | + Expr::While(w) => { | |
| 4075 | + self.find_peer_expr(name, &w.cond, out); | |
| 4076 | + self.find_peer(name, &w.body.stmts, out); | |
| 4077 | + } | |
| 4078 | + Expr::ForLoop(f) => self.find_peer(name, &f.body.stmts, out), | |
| 4079 | + Expr::Loop(l) => self.find_peer(name, &l.body.stmts, out), | |
| 4080 | + Expr::Block(b) => self.find_peer(name, &b.block.stmts, out), | |
| 4081 | + Expr::Unsafe(u) => self.find_peer(name, &u.block.stmts, out), | |
| 4082 | + Expr::If(i) => { | |
| 4083 | + self.find_peer_expr(name, &i.cond, out); | |
| 4084 | + self.find_peer(name, &i.then_branch.stmts, out); | |
| 4085 | + } | |
| 4086 | + Expr::Assign(a) => self.find_peer_expr(name, &a.right, out), | |
| 4087 | + Expr::Index(ix) => { | |
| 4088 | + // `xs[i]` makes `i` an index, which says nothing about width. | |
| 4089 | + self.find_peer_expr(name, &ix.expr, out); | |
| 4090 | + } | |
| 4091 | + _ => {} | |
| 4092 | + } | |
| 4093 | + } | |
| 4094 | + | |
| 3924 | 4095 | /// The type of a `let x;` binding, read off the first assignment to it. |
| 3925 | 4096 | fn deferred_let_hint(&mut self, l: &Local, rest: &[Stmt]) -> Option<Nim> { |
| 3926 | 4097 | if l.init.is_some() || matches!(&l.pat, Pat::Type(_)) { |
| @@ -4137,7 +4308,11 @@ impl Lowerer { | ||
| 4137 | 4308 | return Ok(()); |
| 4138 | 4309 | } |
| 4139 | 4310 | |
| 4140 | - let v = self.expr_at(&init.expr, ann.as_ref())?; | |
| 4311 | + // With no annotation, an unsuffixed literal initialiser takes the type | |
| 4312 | + // of whatever the binding is first compared or combined with, which is | |
| 4313 | + // where Rust gets it. `deferred_hint` carries that. | |
| 4314 | + let want = ann.clone().or_else(|| self.deferred_hint.clone()); | |
| 4315 | + let v = self.expr_at(&init.expr, want.as_ref())?; | |
| 4141 | 4316 | |
| 4142 | 4317 | // `let s = &buf[..n]` binds a view of a place that is already in |
| 4143 | 4318 | // scope. Nim's borrow checker will not let a `let` borrow out of a |
| @@ -5876,6 +6051,28 @@ impl Lowerer { | ||
| 5876 | 6051 | .ok_or("`Self` outside an `impl` block")?, |
| 5877 | 6052 | other => other.to_string(), |
| 5878 | 6053 | }; |
| 6054 | + // `type Neuron = Quad<f64>;` used as a literal's name. An | |
| 6055 | + // alias is expanded rather than emitted, so the name does not | |
| 6056 | + // exist in Nim -- but the type it stands for does, and it | |
| 6057 | + // carries the generic arguments the literal would otherwise | |
| 6058 | + // have to infer. | |
| 6059 | + let mut alias_args: Option<Vec<Nim>> = None; | |
| 6060 | + let name = { | |
| 6061 | + let ty = syn::Type::Path(syn::TypePath { | |
| 6062 | + attrs: Vec::new(), | |
| 6063 | + qself: None, | |
| 6064 | + path: s.path.clone(), | |
| 6065 | + }); | |
| 6066 | + match self.map_ty(&ty) { | |
| 6067 | + Ok(Nim::Named(n, a)) if n != name && self.structs.contains_key(&n) => { | |
| 6068 | + if !a.is_empty() { | |
| 6069 | + alias_args = Some(a); | |
| 6070 | + } | |
| 6071 | + n | |
| 6072 | + } | |
| 6073 | + _ => name, | |
| 6074 | + } | |
| 6075 | + }; | |
| 5879 | 6076 | let mut parts = Vec::new(); |
| 5880 | 6077 | let mut field_tys: Vec<(Nim, Nim)> = Vec::new(); |
| 5881 | 6078 | for f in &s.fields { |
| @@ -5901,6 +6098,9 @@ impl Lowerer { | ||
| 5901 | 6098 | Nim::Named(name.clone(), vec![]) |
| 5902 | 6099 | } else { |
| 5903 | 6100 | match expect { |
| 6101 | + _ if alias_args.as_ref().is_some_and(|a| a.len() == gp.len()) => { | |
| 6102 | + Nim::Named(name.clone(), alias_args.clone().unwrap()) | |
| 6103 | + } | |
| 5904 | 6104 | Some(Nim::Named(n, a)) if *n == name && a.len() == gp.len() => { |
| 5905 | 6105 | Nim::Named(name.clone(), a.clone()) |
| 5906 | 6106 | } |
| @@ -7309,9 +7509,20 @@ impl Lowerer { | ||
| 7309 | 7509 | } |
| 7310 | 7510 | // `vec![v; n]`, after expansion. |
| 7311 | 7511 | "from_elem" if codes.len() == 2 => { |
| 7312 | - let t = args[0].ty.clone().map(|t| Nim::Seq(Box::new(t.owned()))); | |
| 7512 | + // The element takes its type from the expected `Vec<T>` where | |
| 7513 | + // there is one: `vec![0; 256]` into a `Vec<usize>` is a `usize` | |
| 7514 | + // zero, and lowering the literal before knowing that makes it | |
| 7515 | + // an `i32`. | |
| 7516 | + let want = match expect { | |
| 7517 | + Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => Some((**e).clone()), | |
| 7518 | + _ => None, | |
| 7519 | + }; | |
| 7520 | + let v = self.expr_at(&c.args[0], want.as_ref())?; | |
| 7521 | + let t = want | |
| 7522 | + .or_else(|| v.ty.clone().map(|t| t.owned())) | |
| 7523 | + .map(|t| Nim::Seq(Box::new(t))); | |
| 7313 | 7524 | return Ok(Val::new( |
| 7314 | - format!("newSeqWith(int({}), {})", codes[1], codes[0]), | |
| 7525 | + format!("newSeqWith(int({}), {})", codes[1], v.code), | |
| 7315 | 7526 | t, |
| 7316 | 7527 | )); |
| 7317 | 7528 | } |
| @@ -9581,3 +9792,24 @@ fn strip_parens(e: &Expr) -> &Expr { | ||
| 9581 | 9792 | other => other, |
| 9582 | 9793 | } |
| 9583 | 9794 | } |
| 9795 | + | |
| 9796 | +/// Whether an expression is an integer or float literal with no suffix, whose | |
| 9797 | +/// type is therefore Rust's default rather than anything the program said. | |
| 9798 | +fn is_bare_literal(e: &Expr) -> bool { | |
| 9799 | + match e { | |
| 9800 | + Expr::Lit(l) => match &l.lit { | |
| 9801 | + syn::Lit::Int(i) => i.suffix().is_empty(), | |
| 9802 | + syn::Lit::Float(f) => f.suffix().is_empty(), | |
| 9803 | + _ => false, | |
| 9804 | + }, | |
| 9805 | + Expr::Unary(u) => is_bare_literal(&u.expr), | |
| 9806 | + Expr::Paren(p) => is_bare_literal(&p.expr), | |
| 9807 | + Expr::Group(g) => is_bare_literal(&g.expr), | |
| 9808 | + Expr::Block(b) => b | |
| 9809 | + .block | |
| 9810 | + .stmts | |
| 9811 | + .last() | |
| 9812 | + .is_some_and(|s| matches!(s, Stmt::Expr(e, None) if is_bare_literal(e))), | |
| 9813 | + _ => false, | |
| 9814 | + } | |
| 9815 | +} | |
| @@ -402,6 +402,8 @@ pub struct Lowerer { | |||
| 402 | vec_hint: Option<Nim>, | 402 | vec_hint: Option<Nim>, |
| 403 | /// Type for a `let x;` with no initialiser, from its first assignment. | 403 | /// Type for a `let x;` with no initialiser, from its first assignment. |
| 404 | deferred_hint: Option<Nim>, | 404 | deferred_hint: Option<Nim>, |
| 405 | + /// Types of bindings a hint scan will meet before they are lowered. | ||
| 406 | + pending_lets: HashMap<String, Nim>, | ||
| 405 | /// Bounds discovered while rewriting `impl Trait` parameters. `rewrite_apit` | 407 | /// Bounds discovered while rewriting `impl Trait` parameters. `rewrite_apit` |
| 406 | /// runs from `&self`, so they are parked here and folded into `bounds` by | 408 | /// runs from `&self`, so they are parked here and folded into `bounds` by |
| 407 | /// whoever called it. | 409 | /// whoever called it. |
| @@ -508,6 +510,7 @@ impl Lowerer { | |||
| 508 | errors: Vec::new(), | 510 | errors: Vec::new(), |
| 509 | vec_hint: None, | 511 | vec_hint: None, |
| 510 | deferred_hint: None, | 512 | deferred_hint: None, |
| 513 | + pending_lets: HashMap::new(), | ||
| 511 | pending_apit_bounds: std::cell::RefCell::new(Vec::new()), | 514 | pending_apit_bounds: std::cell::RefCell::new(Vec::new()), |
| 512 | bounds: HashMap::new(), | 515 | bounds: HashMap::new(), |
| 513 | const_names_at: std::cell::Cell::new(None), | 516 | const_names_at: std::cell::Cell::new(None), |
| @@ -2327,16 +2330,25 @@ impl Lowerer { | |||
| 2327 | Expr::Match(m) => { | 2330 | Expr::Match(m) => { |
| 2328 | let scrut = self.expr(&m.expr).ok()?; | 2331 | let scrut = self.expr(&m.expr).ok()?; |
| 2329 | let st = scrut.ty.clone()?; | 2332 | let st = scrut.ty.clone()?; |
| 2333 | + // Rust gives every arm the same type, but an arm that is a | ||
| 2334 | + // bare literal only says what that literal *defaults* to. So | ||
| 2335 | + // an arm that carries a real type is preferred: in | ||
| 2336 | + // `match n { 0 => 1, n => n }` the answer is `n`'s type, and | ||
| 2337 | + // taking the first arm would make it `i32`. | ||
| 2338 | + let mut fallback = None; | ||
| 2330 | for arm in &m.arms { | 2339 | for arm in &m.arms { |
| 2331 | self.push_scope(); | 2340 | self.push_scope(); |
| 2332 | let bound = self.pat_bind(&arm.pat, &scrut.code, &st).is_ok(); | 2341 | let bound = self.pat_bind(&arm.pat, &scrut.code, &st).is_ok(); |
| 2333 | let t = if bound { self.result_ty_inner(&arm.body) } else { None }; | 2342 | let t = if bound { self.result_ty_inner(&arm.body) } else { None }; |
| 2334 | self.pop_scope(); | 2343 | self.pop_scope(); |
| 2335 | - if t.is_some() { | 2344 | + let Some(t) = t else { continue }; |
| 2336 | - return t; | 2345 | + if is_bare_literal(&arm.body) { |
| 2346 | + fallback.get_or_insert(t); | ||
| 2347 | + } else { | ||
| 2348 | + return Some(t); | ||
| 2337 | } | 2349 | } |
| 2338 | } | 2350 | } |
| 2339 | - None | 2351 | + fallback |
| 2340 | } | 2352 | } |
| 2341 | Expr::If(i) => { | 2353 | Expr::If(i) => { |
| 2342 | let then = i.then_branch.stmts.last().and_then(|st| match st { | 2354 | let then = i.then_branch.stmts.last().and_then(|st| match st { |
| @@ -3913,7 +3925,9 @@ impl Lowerer { | |||
| 3913 | // else already says what it holds. | 3925 | // else already says what it holds. |
| 3914 | let hint = self.vec_elem_hint(l, rest); | 3926 | let hint = self.vec_elem_hint(l, rest); |
| 3915 | let outer_hint = std::mem::replace(&mut self.vec_hint, hint); | 3927 | let outer_hint = std::mem::replace(&mut self.vec_hint, hint); |
| 3916 | - let deferred = self.deferred_let_hint(l, rest); | 3928 | + let deferred = self |
| 3929 | + .deferred_let_hint(l, rest) | ||
| 3930 | + .or_else(|| self.literal_let_hint(l, rest)); | ||
| 3917 | let outer_def = std::mem::replace(&mut self.deferred_hint, deferred); | 3931 | let outer_def = std::mem::replace(&mut self.deferred_hint, deferred); |
| 3918 | let r = self.local_inner(l); | 3932 | let r = self.local_inner(l); |
| 3919 | self.vec_hint = outer_hint; | 3933 | self.vec_hint = outer_hint; |
| @@ -3921,6 +3935,163 @@ impl Lowerer { | |||
| 3921 | return r; | 3935 | return r; |
| 3922 | } | 3936 | } |
| 3923 | 3937 | ||
| 3938 | + /// The type of `let x = 0;` -- an unsuffixed literal, which says only what | ||
| 3939 | + /// Rust *defaults* to -- read off the first place the binding meets a | ||
| 3940 | + /// typed value. `let mut i = 0; while i < samplepixels` makes `i` a | ||
| 3941 | + /// `usize` in Rust, and for the same reason here. | ||
| 3942 | + fn literal_let_hint(&mut self, l: &Local, rest: &[Stmt]) -> Option<Nim> { | ||
| 3943 | + if matches!(&l.pat, Pat::Type(_)) { | ||
| 3944 | + return None; | ||
| 3945 | + } | ||
| 3946 | + let Pat::Ident(id) = &l.pat else { return None }; | ||
| 3947 | + let init = l.init.as_ref()?; | ||
| 3948 | + if !is_bare_literal(&init.expr) { | ||
| 3949 | + return None; | ||
| 3950 | + } | ||
| 3951 | + let name = id.ident.to_string(); | ||
| 3952 | + // Types for bindings this scan will meet before they are lowered. | ||
| 3953 | + let outer = std::mem::take(&mut self.pending_lets); | ||
| 3954 | + self.collect_pending_lets(rest); | ||
| 3955 | + let mut found = None; | ||
| 3956 | + self.find_peer(&name, rest, &mut found); | ||
| 3957 | + self.pending_lets = outer; | ||
| 3958 | + found.filter(|t| t.is_integer() || matches!(t, Nim::Prim(p) if p.starts_with("float"))) | ||
| 3959 | + } | ||
| 3960 | + | ||
| 3961 | + /// Record what each later `let` in these statements binds, for peers that | ||
| 3962 | + /// are not in scope yet. Only the ones whose type is plain to see. | ||
| 3963 | + fn collect_pending_lets(&mut self, stmts: &[Stmt]) { | ||
| 3964 | + for st in stmts { | ||
| 3965 | + match st { | ||
| 3966 | + Stmt::Local(l) => { | ||
| 3967 | + let (n, ann) = match &l.pat { | ||
| 3968 | + Pat::Ident(i) => (i.ident.to_string(), None), | ||
| 3969 | + Pat::Type(t) => match &*t.pat { | ||
| 3970 | + Pat::Ident(i) => { | ||
| 3971 | + (i.ident.to_string(), self.map_ty(&t.ty).ok()) | ||
| 3972 | + } | ||
| 3973 | + _ => continue, | ||
| 3974 | + }, | ||
| 3975 | + _ => continue, | ||
| 3976 | + }; | ||
| 3977 | + let t = ann.or_else(|| { | ||
| 3978 | + let i = l.init.as_ref()?; | ||
| 3979 | + if is_bare_literal(&i.expr) { | ||
| 3980 | + return None; | ||
| 3981 | + } | ||
| 3982 | + self.pushed_ty(&i.expr) | ||
| 3983 | + }); | ||
| 3984 | + if let Some(t) = t { | ||
| 3985 | + self.pending_lets.entry(n).or_insert(t); | ||
| 3986 | + } | ||
| 3987 | + } | ||
| 3988 | + Stmt::Expr(e, _) => match e { | ||
| 3989 | + Expr::ForLoop(f) => { | ||
| 3990 | + // A `for i in 0..n` binding is typed by the range's | ||
| 3991 | + // bounds, and is a peer like any other. | ||
| 3992 | + if let (Pat::Ident(id), Expr::Range(r)) = (&*f.pat, strip_parens(&f.expr)) { | ||
| 3993 | + let b = r.end.as_deref().or(r.start.as_deref()); | ||
| 3994 | + if let Some(b) = b { | ||
| 3995 | + if !is_bare_literal(b) { | ||
| 3996 | + if let Some(t) = self.pushed_ty(b) { | ||
| 3997 | + self.pending_lets | ||
| 3998 | + .entry(id.ident.to_string()) | ||
| 3999 | + .or_insert(t); | ||
| 4000 | + } | ||
| 4001 | + } | ||
| 4002 | + } | ||
| 4003 | + } | ||
| 4004 | + self.collect_pending_lets(&f.body.stmts) | ||
| 4005 | + } | ||
| 4006 | + Expr::While(w) => self.collect_pending_lets(&w.body.stmts), | ||
| 4007 | + Expr::Loop(l) => self.collect_pending_lets(&l.body.stmts), | ||
| 4008 | + Expr::Block(b) => self.collect_pending_lets(&b.block.stmts), | ||
| 4009 | + _ => {} | ||
| 4010 | + }, | ||
| 4011 | + _ => {} | ||
| 4012 | + } | ||
| 4013 | + } | ||
| 4014 | + } | ||
| 4015 | + | ||
| 4016 | + /// The type of whatever `name` is first compared or combined with. | ||
| 4017 | + fn find_peer(&mut self, name: &str, stmts: &[Stmt], out: &mut Option<Nim>) { | ||
| 4018 | + for st in stmts { | ||
| 4019 | + if out.is_some() { | ||
| 4020 | + return; | ||
| 4021 | + } | ||
| 4022 | + match st { | ||
| 4023 | + Stmt::Expr(e, _) => self.find_peer_expr(name, e, out), | ||
| 4024 | + Stmt::Local(l) => { | ||
| 4025 | + if let Some(i) = &l.init { | ||
| 4026 | + self.find_peer_expr(name, &i.expr, out); | ||
| 4027 | + } | ||
| 4028 | + } | ||
| 4029 | + _ => {} | ||
| 4030 | + } | ||
| 4031 | + } | ||
| 4032 | + } | ||
| 4033 | + | ||
| 4034 | + fn find_peer_expr(&mut self, name: &str, e: &Expr, out: &mut Option<Nim>) { | ||
| 4035 | + if out.is_some() { | ||
| 4036 | + return; | ||
| 4037 | + } | ||
| 4038 | + let names = |x: &Expr| { | ||
| 4039 | + matches!(x, Expr::Path(p) | ||
| 4040 | + if p.path.segments.len() == 1 && p.path.segments[0].ident == name) | ||
| 4041 | + }; | ||
| 4042 | + match e { | ||
| 4043 | + Expr::Binary(b) => { | ||
| 4044 | + let other = if names(&b.left) { | ||
| 4045 | + Some(&*b.right) | ||
| 4046 | + } else if names(&b.right) { | ||
| 4047 | + Some(&*b.left) | ||
| 4048 | + } else { | ||
| 4049 | + None | ||
| 4050 | + }; | ||
| 4051 | + if let Some(o) = other { | ||
| 4052 | + if !is_bare_literal(o) { | ||
| 4053 | + if let Some(t) = self.pushed_ty(o) { | ||
| 4054 | + *out = Some(t); | ||
| 4055 | + return; | ||
| 4056 | + } | ||
| 4057 | + // The peer may be declared *after* this binding and | ||
| 4058 | + // inside a loop, so it is not in scope yet. Its own | ||
| 4059 | + // `let` says what it is. | ||
| 4060 | + if let Expr::Path(p) = strip_parens(o) { | ||
| 4061 | + if p.path.segments.len() == 1 { | ||
| 4062 | + let n = p.path.segments[0].ident.to_string(); | ||
| 4063 | + if let Some(t) = self.pending_lets.get(&n).cloned() { | ||
| 4064 | + *out = Some(t); | ||
| 4065 | + return; | ||
| 4066 | + } | ||
| 4067 | + } | ||
| 4068 | + } | ||
| 4069 | + } | ||
| 4070 | + } | ||
| 4071 | + self.find_peer_expr(name, &b.left, out); | ||
| 4072 | + self.find_peer_expr(name, &b.right, out); | ||
| 4073 | + } | ||
| 4074 | + Expr::While(w) => { | ||
| 4075 | + self.find_peer_expr(name, &w.cond, out); | ||
| 4076 | + self.find_peer(name, &w.body.stmts, out); | ||
| 4077 | + } | ||
| 4078 | + Expr::ForLoop(f) => self.find_peer(name, &f.body.stmts, out), | ||
| 4079 | + Expr::Loop(l) => self.find_peer(name, &l.body.stmts, out), | ||
| 4080 | + Expr::Block(b) => self.find_peer(name, &b.block.stmts, out), | ||
| 4081 | + Expr::Unsafe(u) => self.find_peer(name, &u.block.stmts, out), | ||
| 4082 | + Expr::If(i) => { | ||
| 4083 | + self.find_peer_expr(name, &i.cond, out); | ||
| 4084 | + self.find_peer(name, &i.then_branch.stmts, out); | ||
| 4085 | + } | ||
| 4086 | + Expr::Assign(a) => self.find_peer_expr(name, &a.right, out), | ||
| 4087 | + Expr::Index(ix) => { | ||
| 4088 | + // `xs[i]` makes `i` an index, which says nothing about width. | ||
| 4089 | + self.find_peer_expr(name, &ix.expr, out); | ||
| 4090 | + } | ||
| 4091 | + _ => {} | ||
| 4092 | + } | ||
| 4093 | + } | ||
| 4094 | + | ||
| 3924 | /// The type of a `let x;` binding, read off the first assignment to it. | 4095 | /// The type of a `let x;` binding, read off the first assignment to it. |
| 3925 | fn deferred_let_hint(&mut self, l: &Local, rest: &[Stmt]) -> Option<Nim> { | 4096 | fn deferred_let_hint(&mut self, l: &Local, rest: &[Stmt]) -> Option<Nim> { |
| 3926 | if l.init.is_some() || matches!(&l.pat, Pat::Type(_)) { | 4097 | if l.init.is_some() || matches!(&l.pat, Pat::Type(_)) { |
| @@ -4137,7 +4308,11 @@ impl Lowerer { | |||
| 4137 | return Ok(()); | 4308 | return Ok(()); |
| 4138 | } | 4309 | } |
| 4139 | 4310 | ||
| 4140 | - let v = self.expr_at(&init.expr, ann.as_ref())?; | 4311 | + // With no annotation, an unsuffixed literal initialiser takes the type |
| 4312 | + // of whatever the binding is first compared or combined with, which is | ||
| 4313 | + // where Rust gets it. `deferred_hint` carries that. | ||
| 4314 | + let want = ann.clone().or_else(|| self.deferred_hint.clone()); | ||
| 4315 | + let v = self.expr_at(&init.expr, want.as_ref())?; | ||
| 4141 | 4316 | ||
| 4142 | // `let s = &buf[..n]` binds a view of a place that is already in | 4317 | // `let s = &buf[..n]` binds a view of a place that is already in |
| 4143 | // scope. Nim's borrow checker will not let a `let` borrow out of a | 4318 | // scope. Nim's borrow checker will not let a `let` borrow out of a |
| @@ -5876,6 +6051,28 @@ impl Lowerer { | |||
| 5876 | .ok_or("`Self` outside an `impl` block")?, | 6051 | .ok_or("`Self` outside an `impl` block")?, |
| 5877 | other => other.to_string(), | 6052 | other => other.to_string(), |
| 5878 | }; | 6053 | }; |
| 6054 | + // `type Neuron = Quad<f64>;` used as a literal's name. An | ||
| 6055 | + // alias is expanded rather than emitted, so the name does not | ||
| 6056 | + // exist in Nim -- but the type it stands for does, and it | ||
| 6057 | + // carries the generic arguments the literal would otherwise | ||
| 6058 | + // have to infer. | ||
| 6059 | + let mut alias_args: Option<Vec<Nim>> = None; | ||
| 6060 | + let name = { | ||
| 6061 | + let ty = syn::Type::Path(syn::TypePath { | ||
| 6062 | + attrs: Vec::new(), | ||
| 6063 | + qself: None, | ||
| 6064 | + path: s.path.clone(), | ||
| 6065 | + }); | ||
| 6066 | + match self.map_ty(&ty) { | ||
| 6067 | + Ok(Nim::Named(n, a)) if n != name && self.structs.contains_key(&n) => { | ||
| 6068 | + if !a.is_empty() { | ||
| 6069 | + alias_args = Some(a); | ||
| 6070 | + } | ||
| 6071 | + n | ||
| 6072 | + } | ||
| 6073 | + _ => name, | ||
| 6074 | + } | ||
| 6075 | + }; | ||
| 5879 | let mut parts = Vec::new(); | 6076 | let mut parts = Vec::new(); |
| 5880 | let mut field_tys: Vec<(Nim, Nim)> = Vec::new(); | 6077 | let mut field_tys: Vec<(Nim, Nim)> = Vec::new(); |
| 5881 | for f in &s.fields { | 6078 | for f in &s.fields { |
| @@ -5901,6 +6098,9 @@ impl Lowerer { | |||
| 5901 | Nim::Named(name.clone(), vec![]) | 6098 | Nim::Named(name.clone(), vec![]) |
| 5902 | } else { | 6099 | } else { |
| 5903 | match expect { | 6100 | match expect { |
| 6101 | + _ if alias_args.as_ref().is_some_and(|a| a.len() == gp.len()) => { | ||
| 6102 | + Nim::Named(name.clone(), alias_args.clone().unwrap()) | ||
| 6103 | + } | ||
| 5904 | Some(Nim::Named(n, a)) if *n == name && a.len() == gp.len() => { | 6104 | Some(Nim::Named(n, a)) if *n == name && a.len() == gp.len() => { |
| 5905 | Nim::Named(name.clone(), a.clone()) | 6105 | Nim::Named(name.clone(), a.clone()) |
| 5906 | } | 6106 | } |
| @@ -7309,9 +7509,20 @@ impl Lowerer { | |||
| 7309 | } | 7509 | } |
| 7310 | // `vec![v; n]`, after expansion. | 7510 | // `vec![v; n]`, after expansion. |
| 7311 | "from_elem" if codes.len() == 2 => { | 7511 | "from_elem" if codes.len() == 2 => { |
| 7312 | - let t = args[0].ty.clone().map(|t| Nim::Seq(Box::new(t.owned()))); | 7512 | + // The element takes its type from the expected `Vec<T>` where |
| 7513 | + // there is one: `vec![0; 256]` into a `Vec<usize>` is a `usize` | ||
| 7514 | + // zero, and lowering the literal before knowing that makes it | ||
| 7515 | + // an `i32`. | ||
| 7516 | + let want = match expect { | ||
| 7517 | + Some(Nim::Seq(e)) | Some(Nim::OpenArray(e)) => Some((**e).clone()), | ||
| 7518 | + _ => None, | ||
| 7519 | + }; | ||
| 7520 | + let v = self.expr_at(&c.args[0], want.as_ref())?; | ||
| 7521 | + let t = want | ||
| 7522 | + .or_else(|| v.ty.clone().map(|t| t.owned())) | ||
| 7523 | + .map(|t| Nim::Seq(Box::new(t))); | ||
| 7313 | return Ok(Val::new( | 7524 | return Ok(Val::new( |
| 7314 | - format!("newSeqWith(int({}), {})", codes[1], codes[0]), | 7525 | + format!("newSeqWith(int({}), {})", codes[1], v.code), |
| 7315 | t, | 7526 | t, |
| 7316 | )); | 7527 | )); |
| 7317 | } | 7528 | } |
| @@ -9581,3 +9792,24 @@ fn strip_parens(e: &Expr) -> &Expr { | |||
| 9581 | other => other, | 9792 | other => other, |
| 9582 | } | 9793 | } |
| 9583 | } | 9794 | } |
| 9795 | + | ||
| 9796 | +/// Whether an expression is an integer or float literal with no suffix, whose | ||
| 9797 | +/// type is therefore Rust's default rather than anything the program said. | ||
| 9798 | +fn is_bare_literal(e: &Expr) -> bool { | ||
| 9799 | + match e { | ||
| 9800 | + Expr::Lit(l) => match &l.lit { | ||
| 9801 | + syn::Lit::Int(i) => i.suffix().is_empty(), | ||
| 9802 | + syn::Lit::Float(f) => f.suffix().is_empty(), | ||
| 9803 | + _ => false, | ||
| 9804 | + }, | ||
| 9805 | + Expr::Unary(u) => is_bare_literal(&u.expr), | ||
| 9806 | + Expr::Paren(p) => is_bare_literal(&p.expr), | ||
| 9807 | + Expr::Group(g) => is_bare_literal(&g.expr), | ||
| 9808 | + Expr::Block(b) => b | ||
| 9809 | + .block | ||
| 9810 | + .stmts | ||
| 9811 | + .last() | ||
| 9812 | + .is_some_and(|s| matches!(s, Stmt::Expr(e, None) if is_bare_literal(e))), | ||
| 9813 | + _ => false, | ||
| 9814 | + } | ||
| 9815 | +} | ||