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

Lower shadowing, `T::Assoc` on a generic, and type the string macros

Three things `almost` needed, each a gap that had been sitting in plain sight.

A `let` may shadow another in the same scope in Rust and may not in Nim.
`let scale = ..; let scale = if scale > ..` is ordinary Rust; the second
binding now gets a name of its own and every later use follows it, which is
what shadowing means. The name is chosen *after* the initialiser is lowered,
so `let x = x + 1` still reads the old `x`.

`T::Float` where `T` is a generic parameter becomes a generic parameter of its
own, inferred from the argument at each call. Nim has no way to say "the Float
of T", and this is a weaker static check than Rust's -- Nim will not verify
the argument really is `T::Float`. It is not a weaker *program*, since rustc
checked that before any of this ran, but the check does not survive the
translation and the comment says so.

And a macro in value position was typed as nothing at all unless it was
`write!`. `format!` produces a `String` and always has, so `let s =
format!(..)` failed for want of an annotation. That one was not mine: it
predates this session's work, which I checked by rebuilding at HEAD before
assuming.

tests/cases/073.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-20T13:09:09-07:00 Browse files
c9a5d39 parent: 4d9142c
modified src/lower.rs +145 -6
@@ -389,6 +389,15 @@ pub struct Lowerer {
389389 /// Return type to use instead of the signature's, for a default body
390390 /// whose return type Nim cannot infer.
391391 ret_override: Option<Nim>,
392+ /// Names invented for `T::Assoc` where `T` is a generic parameter.
393+ /// They become generic parameters of whatever signature mentions them.
394+ assoc_params: std::cell::RefCell<std::collections::HashSet<String>>,
395+ /// Generic parameters of the signature currently being mapped. `signature`
396+ /// takes `&self`, and a `T::Assoc` in it needs to know `T` is one.
397+ cur_sig_generics: std::cell::RefCell<Vec<String>>,
398+ /// Nim names for locals whose Rust name is shadowed.
399+ shadow: Vec<HashMap<String, String>>,
400+ shadow_n: usize,
392401 /// Procs that cannot be translated, by Nim name, with the reason.
393402 ///
394403 /// Not an error on its own. A dependency graph carries far more code than
@@ -515,6 +524,10 @@ impl Lowerer {
515524 fn_bounds: HashMap::new(),
516525 type_traits: HashMap::new(),
517526 ret_override: None,
527+ assoc_params: std::cell::RefCell::new(std::collections::HashSet::new()),
528+ cur_sig_generics: std::cell::RefCell::new(Vec::new()),
529+ shadow: vec![HashMap::new()],
530+ shadow_n: 0,
518531 untranslatable: HashMap::new(),
519532 enum_module: HashMap::new(),
520533 report: false,
@@ -564,10 +577,45 @@ impl Lowerer {
564577 fn push_scope(&mut self) {
565578 self.scopes.push(HashMap::new());
566579 self.alias_scopes.push(HashMap::new());
580+ self.shadow.push(HashMap::new());
567581 }
568582 fn pop_scope(&mut self) {
569583 self.scopes.pop();
570584 self.alias_scopes.pop();
585+ self.shadow.pop();
586+ }
587+
588+ /// The Nim name a local goes by.
589+ ///
590+ /// Rust lets a `let` shadow another in the same scope -- `let scale = ..;
591+ /// let scale = if scale > ..` is ordinary Rust -- and Nim does not. The
592+ /// second binding gets a name of its own and every later use follows it,
593+ /// which is what shadowing means.
594+ fn local_name(&self, rust: &str) -> String {
595+ for m in self.shadow.iter().rev() {
596+ if let Some(n) = m.get(rust) {
597+ return n.clone();
598+ }
599+ }
600+ ident(rust)
601+ }
602+
603+ /// Introduce a binding, giving it a fresh name if it shadows one that is
604+ /// still live.
605+ fn bind_local(&mut self, rust: &str) -> String {
606+ let shadowed = self.lookup(rust).is_some() || self.lookup_alias(rust).is_some();
607+ let nim = if shadowed {
608+ let n = self.shadow_n;
609+ self.shadow_n += 1;
610+ format!("{}_s{}", ident(rust), n)
611+ } else {
612+ ident(rust)
613+ };
614+ self.shadow
615+ .last_mut()
616+ .unwrap()
617+ .insert(rust.to_string(), nim.clone());
618+ nim
571619 }
572620 fn bind_alias(&mut self, name: &str, a: Alias) {
573621 self.alias_scopes
@@ -1615,6 +1663,33 @@ impl Lowerer {
16151663 ));
16161664 }
16171665 }
1666+ // `T::Float` where `T` is a generic parameter. Which type that is
1667+ // depends on the instantiation, and Nim has no way to say "the Float
1668+ // of T" -- so it becomes a parameter of its own, inferred from the
1669+ // argument at each call.
1670+ //
1671+ // That is a weaker static check than Rust's: Nim will not verify the
1672+ // argument really is `T::Float`. It is not a weaker *program* -- rustc
1673+ // checked that before this ran, and nothing about what the code does
1674+ // changes -- but the check does not survive the translation, and that
1675+ // is worth knowing.
1676+ if let syn::Type::Path(p) = t {
1677+ let segs: Vec<String> =
1678+ p.path.segments.iter().map(|s| s.ident.to_string()).collect();
1679+ if segs.len() == 2
1680+ && (self
1681+ .fn_generics
1682+ .iter()
1683+ .chain(self.impl_generics.iter())
1684+ .any(|g| *g == segs[0])
1685+ || self.cur_sig_generics.borrow().iter().any(|g| *g == segs[0]))
1686+ && self.assoc.get(&(segs[0].clone(), segs[1].clone())).is_none()
1687+ {
1688+ let n = format!("{}_{}", segs[0], segs[1]);
1689+ self.assoc_params.borrow_mut().insert(n.clone());
1690+ return Ok(Nim::Named(n, Vec::new()));
1691+ }
1692+ }
16181693 // `Self::Item` names an associated type of the enclosing `impl`.
16191694 if let syn::Type::Path(p) = t {
16201695 let segs: Vec<String> =
@@ -2169,6 +2244,34 @@ impl Lowerer {
21692244 format!("[{}]", out.join(", "))
21702245 }
21712246
2247+ /// Add any invented `T::Assoc` names this signature mentions to its
2248+ /// generic parameters. They are inferred from the arguments at each call,
2249+ /// which is the only way Nim can express "the Assoc of T".
2250+ fn add_assoc_params(
2251+ &self,
2252+ gparams: &mut Vec<String>,
2253+ ptys: &[Nim],
2254+ ret: &Nim,
2255+ recv: Option<&Nim>,
2256+ ) {
2257+ let invented = self.assoc_params.borrow().clone();
2258+ let mut mentioned: Vec<String> = invented
2259+ .into_iter()
2260+ .filter(|n| {
2261+ let probe = [n.clone()];
2262+ ptys.iter().any(|p| Self::mentions_any(p, &probe))
2263+ || Self::mentions_any(ret, &probe)
2264+ || recv.is_some_and(|r| Self::mentions_any(r, &probe))
2265+ })
2266+ .collect();
2267+ mentioned.sort();
2268+ for n in mentioned {
2269+ if !gparams.contains(&n) {
2270+ gparams.push(n);
2271+ }
2272+ }
2273+ }
2274+
21722275 fn gen_list(&self, params: &[String]) -> String {
21732276 if params.is_empty() {
21742277 return String::new();
@@ -2249,6 +2352,7 @@ impl Lowerer {
22492352 params.push(g);
22502353 }
22512354 }
2355+ self.add_assoc_params(&mut params, &ptys, &ret, recv);
22522356 let gens = self.gen_list(&params);
22532357 let mut parts = Vec::new();
22542358 if let Some(self_ty) = recv {
@@ -2679,6 +2783,16 @@ impl Lowerer {
26792783 }
26802784
26812785 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {
2786+ let outer = std::mem::replace(
2787+ &mut *self.cur_sig_generics.borrow_mut(),
2788+ Self::generics_of(&sig.generics),
2789+ );
2790+ let r = self.signature_inner(sig);
2791+ *self.cur_sig_generics.borrow_mut() = outer;
2792+ r
2793+ }
2794+
2795+ fn signature_inner(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {
26822796 // `unsafe fn` marks a contract for callers; it does not change what
26832797 // the body means, so it lowers like any other proc.
26842798 // Lifetime parameters carry no runtime meaning and Nim is GC'd, so
@@ -3851,6 +3965,8 @@ impl Lowerer {
38513965 gparams.push(g);
38523966 }
38533967 }
3968+ self.add_assoc_params(&mut gparams, &ptys, &ret, recv_ty.as_ref());
3969+
38543970 // A parameter that appears in no argument cannot be bound by a call.
38553971 // With an `auto` return there is nothing else to bind it either, so it
38563972 // is not a parameter at all -- it was only ever the return type's name.
@@ -4509,9 +4625,16 @@ impl Lowerer {
45094625 if let Expr::Cast(c) = e {
45104626 return self.map_ty(&c.ty).ok();
45114627 }
4628+ // Everything the probe can touch is restored, not just `out`: a
4629+ // `format!` hoists, and a hoist left behind turns up as a binding the
4630+ // real lowering never made.
45124631 let save = self.out.len();
4632+ let saved_hoist = self.hoisted.clone();
4633+ let saved_tmp = self.tmp;
45134634 let t = self.expr(e).ok().and_then(|v| v.ty);
45144635 self.out.truncate(save);
4636+ self.hoisted = saved_hoist;
4637+ self.tmp = saved_tmp;
45154638 t.map(|t| t.owned())
45164639 }
45174640
@@ -4675,10 +4798,13 @@ impl Lowerer {
46754798 let kw = if mutable { "var" } else { "let" };
46764799 // Inside a generic proc the binding's type may mention a parameter Nim
46774800 // will infer; naming it in an annotation would not resolve.
4801+ // The binding's own name comes last: `let x = x + 1` reads the old
4802+ // `x` on the right, so the new name must not be in scope until after.
4803+ let nim = self.bind_local(&name);
46784804 let line = if self.mentions_type_param(&t) {
4679- format!("{} {} = {}", kw, ident(&name), v.code)
4805+ format!("{} {} = {}", kw, nim, v.code)
46804806 } else {
4681- format!("{} {}: {} = {}", kw, ident(&name), t.render(), v.code)
4807+ format!("{} {}: {} = {}", kw, nim, t.render(), v.code)
46824808 };
46834809 self.line(&line);
46844810 self.bind(&name, t);
@@ -6174,7 +6300,7 @@ impl Lowerer {
61746300 });
61756301 }
61766302 if let Some(t) = self.lookup(&name) {
6177- return Ok(Val::new(ident(&name), Some(t)));
6303+ return Ok(Val::new(self.local_name(&name), Some(t)));
61786304 }
61796305 // A top-level function used as a value, e.g. passed to a
61806306 // parameter of `impl Fn(..)` type.
@@ -6332,10 +6458,23 @@ impl Lowerer {
63326458 Ok(Val::new(code, ty))
63336459 }
63346460 Expr::Macro(m) => {
6335- let is_write = matches!(path_name(&m.mac.path).as_str(), "write" | "writeln");
6461+ let which = path_name(&m.mac.path);
63366462 let code = self.macro_call(&m.mac)?;
6337- // A formatter write is a statement that appends, not a value.
6338- let ty = if is_write { Some(Nim::Unit) } else { None };
6463+ let ty = match which.as_str() {
6464+ // A formatter write is a statement that appends, not a
6465+ // value.
6466+ "write" | "writeln" => Some(Nim::Unit),
6467+ // These produce a string and always did; saying so is what
6468+ // lets `let s = format!(..)` be typed without an
6469+ // annotation.
6470+ "format" | "format_args" | "format_args_nl" | "concat"
6471+ | "stringify" | "env" | "include_str" => {
6472+ Some(Nim::Prim("string".into()))
6473+ }
6474+ "panic" | "unreachable" | "assert" | "assert_eq" | "assert_ne"
6475+ | "debug_assert" | "todo" | "unimplemented" => Some(Nim::Unit),
6476+ _ => None,
6477+ };
63396478 Ok(Val::new(code, ty))
63406479 }
63416480 Expr::Struct(s) => {
@@ -389,6 +389,15 @@ pub struct Lowerer {
389 /// Return type to use instead of the signature's, for a default body389 /// Return type to use instead of the signature's, for a default body
390 /// whose return type Nim cannot infer.390 /// whose return type Nim cannot infer.
391 ret_override: Option<Nim>,391 ret_override: Option<Nim>,
392+ /// Names invented for `T::Assoc` where `T` is a generic parameter.
393+ /// They become generic parameters of whatever signature mentions them.
394+ assoc_params: std::cell::RefCell<std::collections::HashSet<String>>,
395+ /// Generic parameters of the signature currently being mapped. `signature`
396+ /// takes `&self`, and a `T::Assoc` in it needs to know `T` is one.
397+ cur_sig_generics: std::cell::RefCell<Vec<String>>,
398+ /// Nim names for locals whose Rust name is shadowed.
399+ shadow: Vec<HashMap<String, String>>,
400+ shadow_n: usize,
392 /// Procs that cannot be translated, by Nim name, with the reason.401 /// Procs that cannot be translated, by Nim name, with the reason.
393 ///402 ///
394 /// Not an error on its own. A dependency graph carries far more code than403 /// Not an error on its own. A dependency graph carries far more code than
@@ -515,6 +524,10 @@ impl Lowerer {
515 fn_bounds: HashMap::new(),524 fn_bounds: HashMap::new(),
516 type_traits: HashMap::new(),525 type_traits: HashMap::new(),
517 ret_override: None,526 ret_override: None,
527+ assoc_params: std::cell::RefCell::new(std::collections::HashSet::new()),
528+ cur_sig_generics: std::cell::RefCell::new(Vec::new()),
529+ shadow: vec![HashMap::new()],
530+ shadow_n: 0,
518 untranslatable: HashMap::new(),531 untranslatable: HashMap::new(),
519 enum_module: HashMap::new(),532 enum_module: HashMap::new(),
520 report: false,533 report: false,
@@ -564,10 +577,45 @@ impl Lowerer {
564 fn push_scope(&mut self) {577 fn push_scope(&mut self) {
565 self.scopes.push(HashMap::new());578 self.scopes.push(HashMap::new());
566 self.alias_scopes.push(HashMap::new());579 self.alias_scopes.push(HashMap::new());
580+ self.shadow.push(HashMap::new());
567 }581 }
568 fn pop_scope(&mut self) {582 fn pop_scope(&mut self) {
569 self.scopes.pop();583 self.scopes.pop();
570 self.alias_scopes.pop();584 self.alias_scopes.pop();
585+ self.shadow.pop();
586+ }
587+
588+ /// The Nim name a local goes by.
589+ ///
590+ /// Rust lets a `let` shadow another in the same scope -- `let scale = ..;
591+ /// let scale = if scale > ..` is ordinary Rust -- and Nim does not. The
592+ /// second binding gets a name of its own and every later use follows it,
593+ /// which is what shadowing means.
594+ fn local_name(&self, rust: &str) -> String {
595+ for m in self.shadow.iter().rev() {
596+ if let Some(n) = m.get(rust) {
597+ return n.clone();
598+ }
599+ }
600+ ident(rust)
601+ }
602+
603+ /// Introduce a binding, giving it a fresh name if it shadows one that is
604+ /// still live.
605+ fn bind_local(&mut self, rust: &str) -> String {
606+ let shadowed = self.lookup(rust).is_some() || self.lookup_alias(rust).is_some();
607+ let nim = if shadowed {
608+ let n = self.shadow_n;
609+ self.shadow_n += 1;
610+ format!("{}_s{}", ident(rust), n)
611+ } else {
612+ ident(rust)
613+ };
614+ self.shadow
615+ .last_mut()
616+ .unwrap()
617+ .insert(rust.to_string(), nim.clone());
618+ nim
571 }619 }
572 fn bind_alias(&mut self, name: &str, a: Alias) {620 fn bind_alias(&mut self, name: &str, a: Alias) {
573 self.alias_scopes621 self.alias_scopes
@@ -1615,6 +1663,33 @@ impl Lowerer {
1615 ));1663 ));
1616 }1664 }
1617 }1665 }
1666+ // `T::Float` where `T` is a generic parameter. Which type that is
1667+ // depends on the instantiation, and Nim has no way to say "the Float
1668+ // of T" -- so it becomes a parameter of its own, inferred from the
1669+ // argument at each call.
1670+ //
1671+ // That is a weaker static check than Rust's: Nim will not verify the
1672+ // argument really is `T::Float`. It is not a weaker *program* -- rustc
1673+ // checked that before this ran, and nothing about what the code does
1674+ // changes -- but the check does not survive the translation, and that
1675+ // is worth knowing.
1676+ if let syn::Type::Path(p) = t {
1677+ let segs: Vec<String> =
1678+ p.path.segments.iter().map(|s| s.ident.to_string()).collect();
1679+ if segs.len() == 2
1680+ && (self
1681+ .fn_generics
1682+ .iter()
1683+ .chain(self.impl_generics.iter())
1684+ .any(|g| *g == segs[0])
1685+ || self.cur_sig_generics.borrow().iter().any(|g| *g == segs[0]))
1686+ && self.assoc.get(&(segs[0].clone(), segs[1].clone())).is_none()
1687+ {
1688+ let n = format!("{}_{}", segs[0], segs[1]);
1689+ self.assoc_params.borrow_mut().insert(n.clone());
1690+ return Ok(Nim::Named(n, Vec::new()));
1691+ }
1692+ }
1618 // `Self::Item` names an associated type of the enclosing `impl`.1693 // `Self::Item` names an associated type of the enclosing `impl`.
1619 if let syn::Type::Path(p) = t {1694 if let syn::Type::Path(p) = t {
1620 let segs: Vec<String> =1695 let segs: Vec<String> =
@@ -2169,6 +2244,34 @@ impl Lowerer {
2169 format!("[{}]", out.join(", "))2244 format!("[{}]", out.join(", "))
2170 }2245 }
2171 2246
2247+ /// Add any invented `T::Assoc` names this signature mentions to its
2248+ /// generic parameters. They are inferred from the arguments at each call,
2249+ /// which is the only way Nim can express "the Assoc of T".
2250+ fn add_assoc_params(
2251+ &self,
2252+ gparams: &mut Vec<String>,
2253+ ptys: &[Nim],
2254+ ret: &Nim,
2255+ recv: Option<&Nim>,
2256+ ) {
2257+ let invented = self.assoc_params.borrow().clone();
2258+ let mut mentioned: Vec<String> = invented
2259+ .into_iter()
2260+ .filter(|n| {
2261+ let probe = [n.clone()];
2262+ ptys.iter().any(|p| Self::mentions_any(p, &probe))
2263+ || Self::mentions_any(ret, &probe)
2264+ || recv.is_some_and(|r| Self::mentions_any(r, &probe))
2265+ })
2266+ .collect();
2267+ mentioned.sort();
2268+ for n in mentioned {
2269+ if !gparams.contains(&n) {
2270+ gparams.push(n);
2271+ }
2272+ }
2273+ }
2274+
2172 fn gen_list(&self, params: &[String]) -> String {2275 fn gen_list(&self, params: &[String]) -> String {
2173 if params.is_empty() {2276 if params.is_empty() {
2174 return String::new();2277 return String::new();
@@ -2249,6 +2352,7 @@ impl Lowerer {
2249 params.push(g);2352 params.push(g);
2250 }2353 }
2251 }2354 }
2355+ self.add_assoc_params(&mut params, &ptys, &ret, recv);
2252 let gens = self.gen_list(&params);2356 let gens = self.gen_list(&params);
2253 let mut parts = Vec::new();2357 let mut parts = Vec::new();
2254 if let Some(self_ty) = recv {2358 if let Some(self_ty) = recv {
@@ -2679,6 +2783,16 @@ impl Lowerer {
2679 }2783 }
2680 2784
2681 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {2785 fn signature(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {
2786+ let outer = std::mem::replace(
2787+ &mut *self.cur_sig_generics.borrow_mut(),
2788+ Self::generics_of(&sig.generics),
2789+ );
2790+ let r = self.signature_inner(sig);
2791+ *self.cur_sig_generics.borrow_mut() = outer;
2792+ r
2793+ }
2794+
2795+ fn signature_inner(&self, sig: &syn::Signature) -> Result<(Vec<Nim>, Nim), String> {
2682 // `unsafe fn` marks a contract for callers; it does not change what2796 // `unsafe fn` marks a contract for callers; it does not change what
2683 // the body means, so it lowers like any other proc.2797 // the body means, so it lowers like any other proc.
2684 // Lifetime parameters carry no runtime meaning and Nim is GC'd, so2798 // Lifetime parameters carry no runtime meaning and Nim is GC'd, so
@@ -3851,6 +3965,8 @@ impl Lowerer {
3851 gparams.push(g);3965 gparams.push(g);
3852 }3966 }
3853 }3967 }
3968+ self.add_assoc_params(&mut gparams, &ptys, &ret, recv_ty.as_ref());
3969+
3854 // A parameter that appears in no argument cannot be bound by a call.3970 // A parameter that appears in no argument cannot be bound by a call.
3855 // With an `auto` return there is nothing else to bind it either, so it3971 // With an `auto` return there is nothing else to bind it either, so it
3856 // is not a parameter at all -- it was only ever the return type's name.3972 // is not a parameter at all -- it was only ever the return type's name.
@@ -4509,9 +4625,16 @@ impl Lowerer {
4509 if let Expr::Cast(c) = e {4625 if let Expr::Cast(c) = e {
4510 return self.map_ty(&c.ty).ok();4626 return self.map_ty(&c.ty).ok();
4511 }4627 }
4628+ // Everything the probe can touch is restored, not just `out`: a
4629+ // `format!` hoists, and a hoist left behind turns up as a binding the
4630+ // real lowering never made.
4512 let save = self.out.len();4631 let save = self.out.len();
4632+ let saved_hoist = self.hoisted.clone();
4633+ let saved_tmp = self.tmp;
4513 let t = self.expr(e).ok().and_then(|v| v.ty);4634 let t = self.expr(e).ok().and_then(|v| v.ty);
4514 self.out.truncate(save);4635 self.out.truncate(save);
4636+ self.hoisted = saved_hoist;
4637+ self.tmp = saved_tmp;
4515 t.map(|t| t.owned())4638 t.map(|t| t.owned())
4516 }4639 }
4517 4640
@@ -4675,10 +4798,13 @@ impl Lowerer {
4675 let kw = if mutable { "var" } else { "let" };4798 let kw = if mutable { "var" } else { "let" };
4676 // Inside a generic proc the binding's type may mention a parameter Nim4799 // Inside a generic proc the binding's type may mention a parameter Nim
4677 // will infer; naming it in an annotation would not resolve.4800 // will infer; naming it in an annotation would not resolve.
4801+ // The binding's own name comes last: `let x = x + 1` reads the old
4802+ // `x` on the right, so the new name must not be in scope until after.
4803+ let nim = self.bind_local(&name);
4678 let line = if self.mentions_type_param(&t) {4804 let line = if self.mentions_type_param(&t) {
4679- format!("{} {} = {}", kw, ident(&name), v.code)4805+ format!("{} {} = {}", kw, nim, v.code)
4680 } else {4806 } else {
4681- format!("{} {}: {} = {}", kw, ident(&name), t.render(), v.code)4807+ format!("{} {}: {} = {}", kw, nim, t.render(), v.code)
4682 };4808 };
4683 self.line(&line);4809 self.line(&line);
4684 self.bind(&name, t);4810 self.bind(&name, t);
@@ -6174,7 +6300,7 @@ impl Lowerer {
6174 });6300 });
6175 }6301 }
6176 if let Some(t) = self.lookup(&name) {6302 if let Some(t) = self.lookup(&name) {
6177- return Ok(Val::new(ident(&name), Some(t)));6303+ return Ok(Val::new(self.local_name(&name), Some(t)));
6178 }6304 }
6179 // A top-level function used as a value, e.g. passed to a6305 // A top-level function used as a value, e.g. passed to a
6180 // parameter of `impl Fn(..)` type.6306 // parameter of `impl Fn(..)` type.
@@ -6332,10 +6458,23 @@ impl Lowerer {
6332 Ok(Val::new(code, ty))6458 Ok(Val::new(code, ty))
6333 }6459 }
6334 Expr::Macro(m) => {6460 Expr::Macro(m) => {
6335- let is_write = matches!(path_name(&m.mac.path).as_str(), "write" | "writeln");6461+ let which = path_name(&m.mac.path);
6336 let code = self.macro_call(&m.mac)?;6462 let code = self.macro_call(&m.mac)?;
6337- // A formatter write is a statement that appends, not a value.6463+ let ty = match which.as_str() {
6338- let ty = if is_write { Some(Nim::Unit) } else { None };6464+ // A formatter write is a statement that appends, not a
6465+ // value.
6466+ "write" | "writeln" => Some(Nim::Unit),
6467+ // These produce a string and always did; saying so is what
6468+ // lets `let s = format!(..)` be typed without an
6469+ // annotation.
6470+ "format" | "format_args" | "format_args_nl" | "concat"
6471+ | "stringify" | "env" | "include_str" => {
6472+ Some(Nim::Prim("string".into()))
6473+ }
6474+ "panic" | "unreachable" | "assert" | "assert_eq" | "assert_ne"
6475+ | "debug_assert" | "todo" | "unimplemented" => Some(Nim::Unit),
6476+ _ => None,
6477+ };
6339 Ok(Val::new(code, ty))6478 Ok(Val::new(code, ty))
6340 }6479 }
6341 Expr::Struct(s) => {6480 Expr::Struct(s) => {
added tests/cases/073-shadowing.rs +30 -0
new file mode 100644
@@ -0,0 +1,30 @@
1+// Rust lets a `let` shadow another in the same scope; Nim does not. The second
2+// binding gets a name of its own and every later use follows it — which is
3+// what shadowing means.
4+fn scale(a: f64, b: f64) -> f64 {
5+ let scale = if a > b { a } else { b };
6+ let scale = if scale > 1.0 { scale } else { 1.0 };
7+ scale * 2.0
8+}
9+
10+fn steps(n: u32) -> String {
11+ let v = n;
12+ let v = v + 1;
13+ let v = v * 2;
14+ // A shadow of a different type entirely.
15+ let v = format!("{}", v);
16+ v
17+}
18+
19+fn main() {
20+ println!("{} {}", scale(3.0, 7.0), scale(0.1, 0.2));
21+ println!("{}", steps(5));
22+
23+ // Shadowing in a nested scope leaves the outer one alone.
24+ let x = 1u32;
25+ {
26+ let x = x + 10;
27+ println!("inner {}", x);
28+ }
29+ println!("outer {}", x);
30+}
new file mode 100644
@@ -0,0 +1,30 @@
1+// Rust lets a `let` shadow another in the same scope; Nim does not. The second
2+// binding gets a name of its own and every later use follows it — which is
3+// what shadowing means.
4+fn scale(a: f64, b: f64) -> f64 {
5+ let scale = if a > b { a } else { b };
6+ let scale = if scale > 1.0 { scale } else { 1.0 };
7+ scale * 2.0
8+}
9+
10+fn steps(n: u32) -> String {
11+ let v = n;
12+ let v = v + 1;
13+ let v = v * 2;
14+ // A shadow of a different type entirely.
15+ let v = format!("{}", v);
16+ v
17+}
18+
19+fn main() {
20+ println!("{} {}", scale(3.0, 7.0), scale(0.1, 0.2));
21+ println!("{}", steps(5));
22+
23+ // Shadowing in a nested scope leaves the outer one alone.
24+ let x = 1u32;
25+ {
26+ let x = x + 10;
27+ println!("inner {}", x);
28+ }
29+ println!("outer {}", x);
30+}