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

Read a trait's associated constants through an accessor

`almost` compiles -- the fourth of libcosmic's dependencies to go the whole
way, and the first that needed traits to work.

A default body reads `Self::DEFAULT_TOLERANCE`, and which constant that is
depends on the instantiation. Nim has no associated constants on a generic, so
each impl also emits `proc rsTrait_CONST(_: typedesc[T]): Ty`, overloaded on
the type -- the same answer as for trait methods, and resolved at the same
point Rust resolves it.

The accessors are emitted after the types they name in a `typedesc`, and trait
default bodies after the accessors but before the procs that call them. That
is the seventh ordering constraint in this pass sequence; they are all of the
form "X names Y, so Y first".

Two things found on the way:

  * Trait default bodies cannot be forward-declared with the rest. The
    definition uses `auto` where the return type is a trait parameter, and a
    forward saying anything else would not match it -- so they are emitted
    before the bodies that call them instead.
  * A `&'static str` constant was becoming `seq[char]`. `&str` is a borrowed
    view of characters everywhere else, and `owned()` on a view gives a `seq`
    -- but a constant has no buffer to borrow from, so it is a string.
    Pre-existing, and unrelated to traits.

tests/cases/074.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-20T13:20:42-07:00 Browse files
81195a6 parent: c9a5d39
modified src/lower.rs +118 -3
@@ -398,6 +398,13 @@ pub struct Lowerer {
398398 /// Nim names for locals whose Rust name is shadowed.
399399 shadow: Vec<HashMap<String, String>>,
400400 shadow_n: usize,
401+ /// Associated constants a trait declares, by `(trait, name)` -> type.
402+ /// A default body reads them through `Self`, where there is no concrete
403+ /// type to name.
404+ trait_consts: HashMap<(String, String), Nim>,
405+ /// Accessors for associated constants, emitted after the types they
406+ /// name in a `typedesc`.
407+ const_accessors: Vec<String>,
401408 /// Procs that cannot be translated, by Nim name, with the reason.
402409 ///
403410 /// Not an error on its own. A dependency graph carries far more code than
@@ -528,6 +535,8 @@ impl Lowerer {
528535 cur_sig_generics: std::cell::RefCell::new(Vec::new()),
529536 shadow: vec![HashMap::new()],
530537 shadow_n: 0,
538+ trait_consts: HashMap::new(),
539+ const_accessors: Vec::new(),
531540 untranslatable: HashMap::new(),
532541 enum_module: HashMap::new(),
533542 report: false,
@@ -791,6 +800,27 @@ impl Lowerer {
791800 }
792801 }
793802
803+ // The associated-constant accessors, now that their types exist.
804+ if !self.const_accessors.is_empty() {
805+ for line in std::mem::take(&mut self.const_accessors) {
806+ self.line(&line);
807+ }
808+ self.blank();
809+ }
810+
811+ // Pass 3.7: trait default bodies. They are generic procs and cannot be
812+ // forward-declared alongside the rest -- the definition uses `auto`
813+ // where the return type is a trait parameter, and a forward saying
814+ // anything else would not match it. Emitted before the bodies that
815+ // call them instead.
816+ for (m, f) in files {
817+ self.cur_mod = m.clone();
818+ for item in &f.items {
819+ let r = self.emit_trait_defaults(item);
820+ self.note(r)?;
821+ }
822+ }
823+
794824 self.body_start = self.out.len();
795825
796826 // Pass 4: bodies.
@@ -1086,6 +1116,12 @@ impl Lowerer {
10861116 let outer_self =
10871117 self.self_ty.replace(Nim::Named("Self".into(), Vec::new()));
10881118 for it in &t.items {
1119+ if let syn::TraitItem::Const(c) = it {
1120+ if let Ok(ty) = self.map_ty(&c.ty) {
1121+ self.trait_consts
1122+ .insert((tname.clone(), c.ident.to_string()), ty.owned());
1123+ }
1124+ }
10891125 if let syn::TraitItem::Fn(f) = it {
10901126 self.note_bounds(&f.sig.generics);
10911127 if let Ok((mut params, ret)) = self.signature(&f.sig) {
@@ -2847,6 +2883,33 @@ impl Lowerer {
28472883 // --------------------------------------------------------------- items
28482884
28492885 /// Emit the type definitions only: they must precede every signature.
2886+ /// Write out a trait's default bodies, ahead of the procs that call them.
2887+ fn emit_trait_defaults(&mut self, item: &Item) -> Result<(), String> {
2888+ if !self.cfg_keeps(item_attrs(item))? {
2889+ return Ok(());
2890+ }
2891+ match item {
2892+ Item::Trait(_) => self.item_inner(item),
2893+ Item::Mod(m) => {
2894+ if let Some((_, items)) = &m.content {
2895+ for i in items {
2896+ self.emit_trait_defaults(i)?;
2897+ }
2898+ }
2899+ Ok(())
2900+ }
2901+ Item::Const(c) => {
2902+ if let Some(items) = derive_item_block(c) {
2903+ for i in &items {
2904+ self.emit_trait_defaults(i)?;
2905+ }
2906+ }
2907+ Ok(())
2908+ }
2909+ _ => Ok(()),
2910+ }
2911+ }
2912+
28502913 /// Write out the module-level constants, ahead of everything that can
28512914 /// name one.
28522915 fn emit_consts(&mut self, item: &Item) -> Result<(), String> {
@@ -3017,10 +3080,14 @@ impl Lowerer {
30173080 return Ok(());
30183081 }
30193082 }
3020- // Types and statics were emitted in their own passes.
3083+ // Types, statics and trait defaults were emitted in their own passes.
30213084 if matches!(
30223085 item,
3023- Item::Struct(_) | Item::Enum(_) | Item::Const(_) | Item::Static(_)
3086+ Item::Struct(_)
3087+ | Item::Enum(_)
3088+ | Item::Const(_)
3089+ | Item::Static(_)
3090+ | Item::Trait(_)
30243091 ) {
30253092 return Ok(());
30263093 }
@@ -3411,10 +3478,37 @@ impl Lowerer {
34113478 {
34123479 return Ok(());
34133480 }
3414- let t = self.map_ty(&c.ty)?.owned();
3481+ // A `&'static str` constant is an owned string, not a borrowed view of
3482+ // one: there is no buffer for it to borrow from, and `owned()` on a
3483+ // view of characters gives `seq[char]`, which is not what a string
3484+ // literal is.
3485+ let t = match self.map_ty(&c.ty)? {
3486+ Nim::OpenArray(e) if matches!(&*e, Nim::Prim(p) if p == "char") => {
3487+ Nim::Prim("string".into())
3488+ }
3489+ other => other.owned(),
3490+ };
34153491 let v = self.expr_at(&c.expr, Some(&t))?;
34163492 let name = format!("{}_{}", tyname, c.ident);
34173493 self.line(&format!("const {}*: {} = {}", ident(&name), t.render(), v.code));
3494+ // A trait's default body reads this through `Self`, where there is no
3495+ // concrete type to name. An accessor overloaded on the type is how
3496+ // Nim answers that, the same way it answers a trait method.
3497+ // Deferred: it names the type in a `typedesc`, which has to be
3498+ // declared first, and constants are written before the types are.
3499+ let trs = self.type_traits.get(tyname).cloned().unwrap_or_default();
3500+ for (tr, _) in trs {
3501+ if self.trait_consts.contains_key(&(tr.clone(), c.ident.to_string())) {
3502+ self.const_accessors.push(format!(
3503+ "proc rs{}_{}*(_: typedesc[{}]): {} = {}",
3504+ tr,
3505+ c.ident,
3506+ ident(tyname),
3507+ t.render(),
3508+ ident(&name)
3509+ ));
3510+ }
3511+ }
34183512 self.blank();
34193513 self.assoc_consts
34203514 .insert((tyname.to_string(), c.ident.to_string()), (ident(&name), t));
@@ -6176,6 +6270,27 @@ impl Lowerer {
61766270 }
61776271 }
61786272
6273+ // `Self::CONST` where `Self` is a type parameter: which
6274+ // constant that is depends on the instantiation, so it is read
6275+ // through the accessor rather than named.
6276+ if let Some(q) = p.path.segments.iter().rev().nth(1) {
6277+ if q.ident == "Self"
6278+ && self.self_ty.as_ref().map(type_name).as_deref() == Some("Self")
6279+ {
6280+ let hit = self
6281+ .trait_consts
6282+ .iter()
6283+ .find(|((_, n), _)| *n == name)
6284+ .map(|((tr, _), ty)| (tr.clone(), ty.clone()));
6285+ if let Some((tr, ty)) = hit {
6286+ return Ok(Val::new(
6287+ format!("rs{}_{}(Self)", tr, name),
6288+ Some(ty),
6289+ ));
6290+ }
6291+ }
6292+ }
6293+
61796294 // `i32::MAX` and friends: an associated const on a primitive.
61806295 // The float constants, written out. Nim has its own spellings
61816296 // for some and none for others, and these are the values Rust
@@ -398,6 +398,13 @@ pub struct Lowerer {
398 /// Nim names for locals whose Rust name is shadowed.398 /// Nim names for locals whose Rust name is shadowed.
399 shadow: Vec<HashMap<String, String>>,399 shadow: Vec<HashMap<String, String>>,
400 shadow_n: usize,400 shadow_n: usize,
401+ /// Associated constants a trait declares, by `(trait, name)` -> type.
402+ /// A default body reads them through `Self`, where there is no concrete
403+ /// type to name.
404+ trait_consts: HashMap<(String, String), Nim>,
405+ /// Accessors for associated constants, emitted after the types they
406+ /// name in a `typedesc`.
407+ const_accessors: Vec<String>,
401 /// Procs that cannot be translated, by Nim name, with the reason.408 /// Procs that cannot be translated, by Nim name, with the reason.
402 ///409 ///
403 /// Not an error on its own. A dependency graph carries far more code than410 /// Not an error on its own. A dependency graph carries far more code than
@@ -528,6 +535,8 @@ impl Lowerer {
528 cur_sig_generics: std::cell::RefCell::new(Vec::new()),535 cur_sig_generics: std::cell::RefCell::new(Vec::new()),
529 shadow: vec![HashMap::new()],536 shadow: vec![HashMap::new()],
530 shadow_n: 0,537 shadow_n: 0,
538+ trait_consts: HashMap::new(),
539+ const_accessors: Vec::new(),
531 untranslatable: HashMap::new(),540 untranslatable: HashMap::new(),
532 enum_module: HashMap::new(),541 enum_module: HashMap::new(),
533 report: false,542 report: false,
@@ -791,6 +800,27 @@ impl Lowerer {
791 }800 }
792 }801 }
793 802
803+ // The associated-constant accessors, now that their types exist.
804+ if !self.const_accessors.is_empty() {
805+ for line in std::mem::take(&mut self.const_accessors) {
806+ self.line(&line);
807+ }
808+ self.blank();
809+ }
810+
811+ // Pass 3.7: trait default bodies. They are generic procs and cannot be
812+ // forward-declared alongside the rest -- the definition uses `auto`
813+ // where the return type is a trait parameter, and a forward saying
814+ // anything else would not match it. Emitted before the bodies that
815+ // call them instead.
816+ for (m, f) in files {
817+ self.cur_mod = m.clone();
818+ for item in &f.items {
819+ let r = self.emit_trait_defaults(item);
820+ self.note(r)?;
821+ }
822+ }
823+
794 self.body_start = self.out.len();824 self.body_start = self.out.len();
795 825
796 // Pass 4: bodies.826 // Pass 4: bodies.
@@ -1086,6 +1116,12 @@ impl Lowerer {
1086 let outer_self =1116 let outer_self =
1087 self.self_ty.replace(Nim::Named("Self".into(), Vec::new()));1117 self.self_ty.replace(Nim::Named("Self".into(), Vec::new()));
1088 for it in &t.items {1118 for it in &t.items {
1119+ if let syn::TraitItem::Const(c) = it {
1120+ if let Ok(ty) = self.map_ty(&c.ty) {
1121+ self.trait_consts
1122+ .insert((tname.clone(), c.ident.to_string()), ty.owned());
1123+ }
1124+ }
1089 if let syn::TraitItem::Fn(f) = it {1125 if let syn::TraitItem::Fn(f) = it {
1090 self.note_bounds(&f.sig.generics);1126 self.note_bounds(&f.sig.generics);
1091 if let Ok((mut params, ret)) = self.signature(&f.sig) {1127 if let Ok((mut params, ret)) = self.signature(&f.sig) {
@@ -2847,6 +2883,33 @@ impl Lowerer {
2847 // --------------------------------------------------------------- items2883 // --------------------------------------------------------------- items
2848 2884
2849 /// Emit the type definitions only: they must precede every signature.2885 /// Emit the type definitions only: they must precede every signature.
2886+ /// Write out a trait's default bodies, ahead of the procs that call them.
2887+ fn emit_trait_defaults(&mut self, item: &Item) -> Result<(), String> {
2888+ if !self.cfg_keeps(item_attrs(item))? {
2889+ return Ok(());
2890+ }
2891+ match item {
2892+ Item::Trait(_) => self.item_inner(item),
2893+ Item::Mod(m) => {
2894+ if let Some((_, items)) = &m.content {
2895+ for i in items {
2896+ self.emit_trait_defaults(i)?;
2897+ }
2898+ }
2899+ Ok(())
2900+ }
2901+ Item::Const(c) => {
2902+ if let Some(items) = derive_item_block(c) {
2903+ for i in &items {
2904+ self.emit_trait_defaults(i)?;
2905+ }
2906+ }
2907+ Ok(())
2908+ }
2909+ _ => Ok(()),
2910+ }
2911+ }
2912+
2850 /// Write out the module-level constants, ahead of everything that can2913 /// Write out the module-level constants, ahead of everything that can
2851 /// name one.2914 /// name one.
2852 fn emit_consts(&mut self, item: &Item) -> Result<(), String> {2915 fn emit_consts(&mut self, item: &Item) -> Result<(), String> {
@@ -3017,10 +3080,14 @@ impl Lowerer {
3017 return Ok(());3080 return Ok(());
3018 }3081 }
3019 }3082 }
3020- // Types and statics were emitted in their own passes.3083+ // Types, statics and trait defaults were emitted in their own passes.
3021 if matches!(3084 if matches!(
3022 item,3085 item,
3023- Item::Struct(_) | Item::Enum(_) | Item::Const(_) | Item::Static(_)3086+ Item::Struct(_)
3087+ | Item::Enum(_)
3088+ | Item::Const(_)
3089+ | Item::Static(_)
3090+ | Item::Trait(_)
3024 ) {3091 ) {
3025 return Ok(());3092 return Ok(());
3026 }3093 }
@@ -3411,10 +3478,37 @@ impl Lowerer {
3411 {3478 {
3412 return Ok(());3479 return Ok(());
3413 }3480 }
3414- let t = self.map_ty(&c.ty)?.owned();3481+ // A `&'static str` constant is an owned string, not a borrowed view of
3482+ // one: there is no buffer for it to borrow from, and `owned()` on a
3483+ // view of characters gives `seq[char]`, which is not what a string
3484+ // literal is.
3485+ let t = match self.map_ty(&c.ty)? {
3486+ Nim::OpenArray(e) if matches!(&*e, Nim::Prim(p) if p == "char") => {
3487+ Nim::Prim("string".into())
3488+ }
3489+ other => other.owned(),
3490+ };
3415 let v = self.expr_at(&c.expr, Some(&t))?;3491 let v = self.expr_at(&c.expr, Some(&t))?;
3416 let name = format!("{}_{}", tyname, c.ident);3492 let name = format!("{}_{}", tyname, c.ident);
3417 self.line(&format!("const {}*: {} = {}", ident(&name), t.render(), v.code));3493 self.line(&format!("const {}*: {} = {}", ident(&name), t.render(), v.code));
3494+ // A trait's default body reads this through `Self`, where there is no
3495+ // concrete type to name. An accessor overloaded on the type is how
3496+ // Nim answers that, the same way it answers a trait method.
3497+ // Deferred: it names the type in a `typedesc`, which has to be
3498+ // declared first, and constants are written before the types are.
3499+ let trs = self.type_traits.get(tyname).cloned().unwrap_or_default();
3500+ for (tr, _) in trs {
3501+ if self.trait_consts.contains_key(&(tr.clone(), c.ident.to_string())) {
3502+ self.const_accessors.push(format!(
3503+ "proc rs{}_{}*(_: typedesc[{}]): {} = {}",
3504+ tr,
3505+ c.ident,
3506+ ident(tyname),
3507+ t.render(),
3508+ ident(&name)
3509+ ));
3510+ }
3511+ }
3418 self.blank();3512 self.blank();
3419 self.assoc_consts3513 self.assoc_consts
3420 .insert((tyname.to_string(), c.ident.to_string()), (ident(&name), t));3514 .insert((tyname.to_string(), c.ident.to_string()), (ident(&name), t));
@@ -6176,6 +6270,27 @@ impl Lowerer {
6176 }6270 }
6177 }6271 }
6178 6272
6273+ // `Self::CONST` where `Self` is a type parameter: which
6274+ // constant that is depends on the instantiation, so it is read
6275+ // through the accessor rather than named.
6276+ if let Some(q) = p.path.segments.iter().rev().nth(1) {
6277+ if q.ident == "Self"
6278+ && self.self_ty.as_ref().map(type_name).as_deref() == Some("Self")
6279+ {
6280+ let hit = self
6281+ .trait_consts
6282+ .iter()
6283+ .find(|((_, n), _)| *n == name)
6284+ .map(|((tr, _), ty)| (tr.clone(), ty.clone()));
6285+ if let Some((tr, ty)) = hit {
6286+ return Ok(Val::new(
6287+ format!("rs{}_{}(Self)", tr, name),
6288+ Some(ty),
6289+ ));
6290+ }
6291+ }
6292+ }
6293+
6179 // `i32::MAX` and friends: an associated const on a primitive.6294 // `i32::MAX` and friends: an associated const on a primitive.
6180 // The float constants, written out. Nim has its own spellings6295 // The float constants, written out. Nim has its own spellings
6181 // for some and none for others, and these are the values Rust6296 // for some and none for others, and these are the values Rust
added tests/cases/074-trait-assoc-const.rs +37 -0
new file mode 100644
@@ -0,0 +1,37 @@
1+// A trait's associated constant, read from a default body through `Self`.
2+// Which constant that is depends on the instantiation, so it goes through an
3+// accessor overloaded on the type — the same way a trait method does.
4+trait Limits {
5+ const CEILING: u32;
6+ const NAME: &'static str;
7+
8+ fn clamp(&self, v: u32) -> u32 {
9+ if v > Self::CEILING { Self::CEILING } else { v }
10+ }
11+
12+ fn label(&self) -> String {
13+ format!("{} <= {}", Self::NAME, Self::CEILING)
14+ }
15+}
16+
17+struct Small;
18+struct Large;
19+
20+impl Limits for Small {
21+ const CEILING: u32 = 10;
22+ const NAME: &'static str = "small";
23+}
24+
25+impl Limits for Large {
26+ const CEILING: u32 = 1000;
27+ const NAME: &'static str = "large";
28+}
29+
30+fn main() {
31+ let s = Small;
32+ let l = Large;
33+ println!("{} {}", s.clamp(5), s.clamp(50));
34+ println!("{} {}", l.clamp(5), l.clamp(5000));
35+ println!("{}", s.label());
36+ println!("{}", l.label());
37+}
new file mode 100644
@@ -0,0 +1,37 @@
1+// A trait's associated constant, read from a default body through `Self`.
2+// Which constant that is depends on the instantiation, so it goes through an
3+// accessor overloaded on the type — the same way a trait method does.
4+trait Limits {
5+ const CEILING: u32;
6+ const NAME: &'static str;
7+
8+ fn clamp(&self, v: u32) -> u32 {
9+ if v > Self::CEILING { Self::CEILING } else { v }
10+ }
11+
12+ fn label(&self) -> String {
13+ format!("{} <= {}", Self::NAME, Self::CEILING)
14+ }
15+}
16+
17+struct Small;
18+struct Large;
19+
20+impl Limits for Small {
21+ const CEILING: u32 = 10;
22+ const NAME: &'static str = "small";
23+}
24+
25+impl Limits for Large {
26+ const CEILING: u32 = 1000;
27+ const NAME: &'static str = "large";
28+}
29+
30+fn main() {
31+ let s = Small;
32+ let l = Large;
33+ println!("{} {}", s.clamp(5), s.clamp(50));
34+ println!("{} {}", l.clamp(5), l.clamp(5000));
35+ println!("{}", s.label());
36+ println!("{}", l.label());
37+}