nandi/rustnimpublic Fork 0
8354895
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.

Add associated types and consts, and test whether the blocker stack bottoms out

`type Item = ..;` inside an `impl` now binds a name the same block's
signatures resolve as `Self::Item`, recorded in a pass before the signatures
are mapped so it is available when they need it. `const` in an `impl` becomes
a module-level constant named for both type and item, since Nim has no
per-type constant namespace. A `trait` declaration lowers to nothing --
trait resolution is not modelled anywhere -- but one that gives a method a
default body is rejected, because that body is code and there is no impl for
it to be emitted into.

Method arguments are now typed from the method's own signature rather than
from the receiver, which is what a method taking `usize` needs in order to
accept a bare literal.

This cleared 85 of 87 blockers across the 400-crate sample and moved the
fully-transpiling count from 2 to 3 -- the first time a feature has moved it
at all.

That prompted the question of whether the stacks bottom out: if they are
drawn from one finite pool of language features, clearing the pool clears
every stack, and the flat counts so far are just what precedes a step change.
Sampling every file rather than each crate's first blocker, 1,766
observations, says the answer is both, split by class. Distinct normalised
blocker kinds: 63 -- finite, and each one cleared is cleared for everyone.
Distinct unsupported std methods: 80, from small crates alone, against a
std surface of thousands. Distinct unknown functions: 75, which are calls
into dependencies. And 315 of the 400 crates have dependencies at all, which
is not a pool to drain but a closure to transpile recursively until it ends
at libc, proc macros or SIMD.

Among the 85 dependency-free crates, 1 of 84 transpiles in full, so features
are not the only remaining gate even there. DESIGN.md records all of it.

39 differential cases, all green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-18T22:04:06-07:00 Browse files
8354895 parent: ff34e1b
modified DESIGN.md +51 -14
@@ -304,7 +304,10 @@ runner) rather than a wrong answer.
304304 ### Still open
305305
306306 6. Const generic parameters, `move` closures, closure bodies with statements,
307- associated types in an `impl`, and trait objects are rejected with a reason.
307+ trait objects, `macro_rules!` definitions and raw pointers are rejected
308+ with a reason. A `trait` declaration lowers to nothing — trait resolution
309+ is not modelled — but one giving a method a *default body* is rejected,
310+ since that body is code with no impl to be emitted into.
308311 Lifetime parameters are *not* a rejection: they carry no runtime meaning
309312 and Nim is GC'd, so `fn encode<'a>(..)` lowers fine.
310313 7. `saturating_*` and `checked_*` are implemented, detecting overflow on the
@@ -390,16 +393,16 @@ is a north star, not a next step.
390393 400 crates from the local registry (under 4,000 lines each), all their module
391394 files passed together, first blocker recorded:
392395
393-| blocker | start | after host-`cfg` | after generics |
394-|---|---|---|---|
395-| unevaluable `#[cfg]` | 124 | 34 | 34 |
396-| generic type parameter | 69 | 91 | **1** |
397-| unsupported item in an `impl` (associated types) | 54 | 63 | 87 |
398-| unsupported type | 20 | 29 | 60 |
399-| trait object | 17 | 25 | 30 |
400-| macro definition | 21 | 24 | 25 |
401-| raw pointer | 12 | 22 | 24 |
402-| **crates fully transpiled** | **2** | **2** | **2** |
396+| blocker | start | +host-`cfg` | +generics | +assoc types |
397+|---|---|---|---|---|
398+| unevaluable `#[cfg]` | 124 | 34 | 34 | 34 |
399+| generic type parameter | 69 | 91 | **1** | 1 |
400+| associated types in an `impl` | 54 | 63 | 87 | **2** |
401+| unsupported type | 20 | 29 | 60 | 71 |
402+| trait object | 17 | 25 | 30 | 35 |
403+| macro definition | 21 | 24 | 25 | 32 |
404+| raw pointer | 12 | 22 | 24 | 28 |
405+| **crates fully transpiled** | **2** | **2** | **2** | **3** |
403406
404407 This has now happened twice. Evaluating the host `#[cfg]` predicates cleared
405408 90 of 124 blockers and moved the fully-working count by zero. Generics then
@@ -412,14 +415,48 @@ often first, not which one finishes a crate. `base16ct`, `adler2` and
412415 `cosmic-theme`'s spacing model work because their whole stack was ground
413416 through, one blocker at a time.
414417
418+### Does the stack have a bottom?
419+
420+If the stacks are drawn from one shared finite pool of language features, then
421+clearing the pool clears every stack at once, and the flat counts above are
422+just what progress looks like before a step change. That is a real possibility
423+and it is testable, so it was tested. Sampling every *file* of the 400 crates
424+rather than only each crate's first blocker — 1,766 blocker observations:
425+
426+| | count | bounded? |
427+|---|---|---|
428+| distinct normalised blocker kinds | **63** | **yes — this is the language-feature pool** |
429+| distinct unsupported std methods | 80 | no — this is `std`'s surface |
430+| distinct unknown functions | 75 | no — these are calls into dependencies |
431+| distinct unsupported macros | 13 | no |
432+
433+So the answer is *both*, split by class:
434+
435+- **Language features do bottom out.** 63 distinct kinds, and each one cleared
436+ is cleared for every crate forever. This part is finite and the step-change
437+ intuition is correct for it. Associated types produced the first net gain
438+ (2 → 3), which is what that dynamic looks like starting.
439+- **The API surface does not.** 80 distinct `std` methods appeared in 1,766
440+ samples of *small* crates; `std` has thousands of items, and each needs a
441+ verified Nim equivalent rather than a guess. That is enumerable but it has
442+ no bottom you reach by clearing features.
443+- **Dependencies are not a pool at all.** 315 of the 400 crates depend on
444+ other crates, which have to be transpiled too, recursively, until the graph
445+ ends at `libc`, proc macros or SIMD intrinsics — which it does, and those do
446+ not translate.
447+
448+And among the 85 dependency-free crates, 1 of 84 currently transpiles in full.
449+So even with dependencies removed from the picture, features alone are not the
450+only remaining gate.
451+
415452 So the ranking above is not a roadmap — it says which feature is most often
416453 *first*, which is not the same as which feature finishes a crate. The only
417454 honest way to add a crate is to pick it and clear its stack, as was done
418455 twice.
419456
420-Generics are now done. The next blocker by frequency is associated types
421-(`type Item = ..` inside an `impl`, 87), then unsupported types (60) and trait
422-objects (30) — but see the paragraph above before treating that as a plan.
457+Generics and associated types are now done. By frequency the next are
458+unsupported types (71), trait objects (35) and `macro_rules!` definitions (32)
459+— but see above before treating that as a plan.
423460
424461 ### `cosmic-theme`: what was reachable
425462
@@ -304,7 +304,10 @@ runner) rather than a wrong answer.
304 ### Still open304 ### Still open
305 305
306 6. Const generic parameters, `move` closures, closure bodies with statements,306 6. Const generic parameters, `move` closures, closure bodies with statements,
307- associated types in an `impl`, and trait objects are rejected with a reason.307+ trait objects, `macro_rules!` definitions and raw pointers are rejected
308+ with a reason. A `trait` declaration lowers to nothing — trait resolution
309+ is not modelled — but one giving a method a *default body* is rejected,
310+ since that body is code with no impl to be emitted into.
308 Lifetime parameters are *not* a rejection: they carry no runtime meaning311 Lifetime parameters are *not* a rejection: they carry no runtime meaning
309 and Nim is GC'd, so `fn encode<'a>(..)` lowers fine.312 and Nim is GC'd, so `fn encode<'a>(..)` lowers fine.
310 7. `saturating_*` and `checked_*` are implemented, detecting overflow on the313 7. `saturating_*` and `checked_*` are implemented, detecting overflow on the
@@ -390,16 +393,16 @@ is a north star, not a next step.
390 400 crates from the local registry (under 4,000 lines each), all their module393 400 crates from the local registry (under 4,000 lines each), all their module
391 files passed together, first blocker recorded:394 files passed together, first blocker recorded:
392 395
393-| blocker | start | after host-`cfg` | after generics |396+| blocker | start | +host-`cfg` | +generics | +assoc types |
394-|---|---|---|---|397+|---|---|---|---|---|
395-| unevaluable `#[cfg]` | 124 | 34 | 34 |398+| unevaluable `#[cfg]` | 124 | 34 | 34 | 34 |
396-| generic type parameter | 69 | 91 | **1** |399+| generic type parameter | 69 | 91 | **1** | 1 |
397-| unsupported item in an `impl` (associated types) | 54 | 63 | 87 |400+| associated types in an `impl` | 54 | 63 | 87 | **2** |
398-| unsupported type | 20 | 29 | 60 |401+| unsupported type | 20 | 29 | 60 | 71 |
399-| trait object | 17 | 25 | 30 |402+| trait object | 17 | 25 | 30 | 35 |
400-| macro definition | 21 | 24 | 25 |403+| macro definition | 21 | 24 | 25 | 32 |
401-| raw pointer | 12 | 22 | 24 |404+| raw pointer | 12 | 22 | 24 | 28 |
402-| **crates fully transpiled** | **2** | **2** | **2** |405+| **crates fully transpiled** | **2** | **2** | **2** | **3** |
403 406
404 This has now happened twice. Evaluating the host `#[cfg]` predicates cleared407 This has now happened twice. Evaluating the host `#[cfg]` predicates cleared
405 90 of 124 blockers and moved the fully-working count by zero. Generics then408 90 of 124 blockers and moved the fully-working count by zero. Generics then
@@ -412,14 +415,48 @@ often first, not which one finishes a crate. `base16ct`, `adler2` and
412 `cosmic-theme`'s spacing model work because their whole stack was ground415 `cosmic-theme`'s spacing model work because their whole stack was ground
413 through, one blocker at a time.416 through, one blocker at a time.
414 417
418+### Does the stack have a bottom?
419+
420+If the stacks are drawn from one shared finite pool of language features, then
421+clearing the pool clears every stack at once, and the flat counts above are
422+just what progress looks like before a step change. That is a real possibility
423+and it is testable, so it was tested. Sampling every *file* of the 400 crates
424+rather than only each crate's first blocker — 1,766 blocker observations:
425+
426+| | count | bounded? |
427+|---|---|---|
428+| distinct normalised blocker kinds | **63** | **yes — this is the language-feature pool** |
429+| distinct unsupported std methods | 80 | no — this is `std`'s surface |
430+| distinct unknown functions | 75 | no — these are calls into dependencies |
431+| distinct unsupported macros | 13 | no |
432+
433+So the answer is *both*, split by class:
434+
435+- **Language features do bottom out.** 63 distinct kinds, and each one cleared
436+ is cleared for every crate forever. This part is finite and the step-change
437+ intuition is correct for it. Associated types produced the first net gain
438+ (2 → 3), which is what that dynamic looks like starting.
439+- **The API surface does not.** 80 distinct `std` methods appeared in 1,766
440+ samples of *small* crates; `std` has thousands of items, and each needs a
441+ verified Nim equivalent rather than a guess. That is enumerable but it has
442+ no bottom you reach by clearing features.
443+- **Dependencies are not a pool at all.** 315 of the 400 crates depend on
444+ other crates, which have to be transpiled too, recursively, until the graph
445+ ends at `libc`, proc macros or SIMD intrinsics — which it does, and those do
446+ not translate.
447+
448+And among the 85 dependency-free crates, 1 of 84 currently transpiles in full.
449+So even with dependencies removed from the picture, features alone are not the
450+only remaining gate.
451+
415 So the ranking above is not a roadmap — it says which feature is most often452 So the ranking above is not a roadmap — it says which feature is most often
416 *first*, which is not the same as which feature finishes a crate. The only453 *first*, which is not the same as which feature finishes a crate. The only
417 honest way to add a crate is to pick it and clear its stack, as was done454 honest way to add a crate is to pick it and clear its stack, as was done
418 twice.455 twice.
419 456
420-Generics are now done. The next blocker by frequency is associated types457+Generics and associated types are now done. By frequency the next are
421-(`type Item = ..` inside an `impl`, 87), then unsupported types (60) and trait458+unsupported types (71), trait objects (35) and `macro_rules!` definitions (32)
422-objects (30) — but see the paragraph above before treating that as a plan.459+— but see above before treating that as a plan.
423 460
424 ### `cosmic-theme`: what was reachable461 ### `cosmic-theme`: what was reachable
425 462
modified src/lower.rs +123 -3
@@ -207,6 +207,12 @@ pub struct Lowerer {
207207 fn_generics: Vec<String>,
208208 /// Type parameters declared by each generic struct or enum.
209209 type_generics: HashMap<String, Vec<String>>,
210+ /// `(type, name) -> type` for `type Item = ..;` inside an `impl`. Rust
211+ /// writes those as `Self::Item`, which has to resolve before any
212+ /// signature mentioning it is mapped.
213+ assoc: HashMap<(String, String), Nim>,
214+ /// `(type, name) -> (nim name, type)` for `const` items inside an `impl`.
215+ assoc_consts: HashMap<(String, String), (String, Nim)>,
210216 /// `use` brings a name into scope from another module. Flattening loses
211217 /// the module structure, so the mapping is recorded and consulted when a
212218 /// bare call is resolved.
@@ -280,6 +286,8 @@ impl Lowerer {
280286 impl_generics: Vec::new(),
281287 fn_generics: Vec::new(),
282288 type_generics: HashMap::new(),
289+ assoc: HashMap::new(),
290+ assoc_consts: HashMap::new(),
283291 use_map: HashMap::new(),
284292 structs: HashMap::new(),
285293 enums: HashMap::new(),
@@ -630,6 +638,14 @@ impl Lowerer {
630638 {
631639 let self_ty = self_ty.clone();
632640 let tyname = type_name(&self_ty);
641+ // Associated types first: a signature in the same block may name
642+ // one, and it has to resolve by the time that signature is mapped.
643+ for it in &im.items {
644+ if let syn::ImplItem::Type(t) = it {
645+ let v = self.map_ty(&t.ty)?;
646+ self.assoc.insert((tyname.clone(), t.ident.to_string()), v);
647+ }
648+ }
633649 if let Some((path, _)) = &im.trait_ {
634650 let tr = path_name(path);
635651 if im.items.is_empty() {
@@ -672,8 +688,16 @@ impl Lowerer {
672688 self.op_impls.insert((tyname.clone(), op.to_string()), ());
673689 }
674690 for it in &im.items {
691+ // Already recorded above; a const is emitted with the
692+ // bodies.
693+ if matches!(it, syn::ImplItem::Type(_) | syn::ImplItem::Const(_)) {
694+ continue;
695+ }
675696 let syn::ImplItem::Fn(m) = it else {
676- return Err(format!("unsupported item in `impl {tr}`"));
697+ return Err(format!(
698+ "unsupported item in `impl {tr}`: only `fn`, \
699+ `type` and `const` are implemented"
700+ ));
677701 };
678702 let mname = m.sig.ident.to_string();
679703 let (mut params, ret) = self.signature(&m.sig)?;
@@ -821,6 +845,23 @@ impl Lowerer {
821845 /// lowering goes through here rather than calling `ty::map` directly, so
822846 /// an alias cannot be missed in one position and honoured in another.
823847 fn map_ty(&self, t: &syn::Type) -> Result<Nim, String> {
848+ // `Self::Item` names an associated type of the enclosing `impl`.
849+ if let syn::Type::Path(p) = t {
850+ let segs: Vec<String> =
851+ p.path.segments.iter().map(|s| s.ident.to_string()).collect();
852+ if segs.len() == 2 {
853+ let owner = if segs[0] == "Self" {
854+ self.self_ty.as_ref().map(type_name)
855+ } else {
856+ Some(segs[0].clone())
857+ };
858+ if let Some(o) = owner {
859+ if let Some(a) = self.assoc.get(&(o, segs[1].clone())) {
860+ return Ok(a.clone());
861+ }
862+ }
863+ }
864+ }
824865 let n = ty::map(&self.expand(t, 0)?)?;
825866 Ok(self.subst_self(n))
826867 }
@@ -1185,6 +1226,26 @@ impl Lowerer {
11851226 Ok(())
11861227 }
11871228 Item::Type(_) => Ok(()), // expanded at every use site
1229+ Item::Trait(t) => {
1230+ // We do not model trait resolution, so a declaration generates
1231+ // nothing and a use that needed it is rejected where it
1232+ // appears. A *default body*, though, is code: dropping it
1233+ // would silently remove a method the impls inherit.
1234+ for it in &t.items {
1235+ if let syn::TraitItem::Fn(f) = it {
1236+ if f.default.is_some() {
1237+ return Err(format!(
1238+ "`trait {}` gives `{}` a default body; trait \
1239+ resolution is not modelled, so that body has no \
1240+ impl to be emitted into and dropping it would \
1241+ remove code",
1242+ t.ident, f.sig.ident
1243+ ));
1244+ }
1245+ }
1246+ }
1247+ Ok(())
1248+ }
11881249 Item::Enum(e) => {
11891250 let def = self.enums[&e.ident.to_string()].clone();
11901251 self.emit_enum(&def);
@@ -1285,8 +1346,18 @@ impl Lowerer {
12851346 }
12861347 let tyname = type_name(self_ty);
12871348 for it in &im.items {
1349+ if let syn::ImplItem::Const(c) = it {
1350+ self.assoc_const(&tyname, c)?;
1351+ continue;
1352+ }
1353+ if matches!(it, syn::ImplItem::Type(_)) {
1354+ continue; // a type binding emits nothing
1355+ }
12881356 let syn::ImplItem::Fn(m) = it else {
1289- return Err(format!("unsupported item in `impl {tr}`"));
1357+ return Err(format!(
1358+ "unsupported item in `impl {tr}`: only `fn`, `type` and \
1359+ `const` are implemented"
1360+ ));
12901361 };
12911362 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
12921363 let nim = trait_method_name(&tyname, &tr, &m.sig.ident.to_string());
@@ -1294,6 +1365,7 @@ impl Lowerer {
12941365 }
12951366 return Ok(());
12961367 }
1368+ let tyname = type_name(self_ty);
12971369 for it in &im.items {
12981370 match it {
12991371 syn::ImplItem::Fn(m) => {
@@ -1301,12 +1373,31 @@ impl Lowerer {
13011373 let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string());
13021374 self.func_named(&nim, &m.sig, &m.block, recv)?;
13031375 }
1304- _ => return Err("only `fn` items are supported inside `impl`".into()),
1376+ syn::ImplItem::Type(_) => {}
1377+ syn::ImplItem::Const(c) => self.assoc_const(&tyname, c)?,
1378+ _ => {
1379+ return Err("only `fn`, `type` and `const` items are supported \
1380+ inside `impl`"
1381+ .into())
1382+ }
13051383 }
13061384 }
13071385 Ok(())
13081386 }
13091387
1388+ /// `const N: usize = 4;` inside an `impl`. Nim has no per-type constant
1389+ /// namespace, so it becomes a module-level const named for both.
1390+ fn assoc_const(&mut self, tyname: &str, c: &syn::ImplItemConst) -> Result<(), String> {
1391+ let t = self.map_ty(&c.ty)?.owned();
1392+ let v = self.expr_at(&c.expr, Some(&t))?;
1393+ let name = format!("{}_{}", tyname, c.ident);
1394+ self.line(&format!("const {}*: {} = {}", ident(&name), t.render(), v.code));
1395+ self.blank();
1396+ self.assoc_consts
1397+ .insert((tyname.to_string(), c.ident.to_string()), (ident(&name), t));
1398+ Ok(())
1399+ }
1400+
13101401 /// The type an operator impl declares for its right-hand operand.
13111402 fn op_param(&self, t: &Option<Nim>, op: &str) -> Option<Nim> {
13121403 let n = type_name(t.as_ref()?);
@@ -2769,6 +2860,18 @@ impl Lowerer {
27692860 if name == "None" {
27702861 return Ok(Val::new(self.none_of(expect), expect.cloned()));
27712862 }
2863+ // `Grid::BORDER`: a `const` declared inside an `impl`.
2864+ if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
2865+ let q = if q == "Self" {
2866+ self.self_ty.as_ref().map(type_name).unwrap_or(q)
2867+ } else {
2868+ q
2869+ };
2870+ if let Some((nim, t)) = self.assoc_consts.get(&(q, name.clone())) {
2871+ return Ok(Val::new(nim.clone(), Some(t.clone())));
2872+ }
2873+ }
2874+
27722875 // `i32::MAX` and friends: an associated const on a primitive.
27732876 if matches!(name.as_str(), "MAX" | "MIN") {
27742877 if let Some(q) = p.path.segments.iter().rev().nth(1) {
@@ -4192,6 +4295,23 @@ impl Lowerer {
41924295 // A method defined in this file via `impl`, found by the
41934296 // receiver's type rather than by name alone.
41944297 let key = rt.as_ref().map(|t| (type_name(t), name.clone()));
4298+ // Re-lower the arguments with the declared parameter types:
4299+ // a method's own signature says what width its literals are,
4300+ // which the receiver's type does not.
4301+ let declared: Option<Vec<Nim>> = key
4302+ .as_ref()
4303+ .and_then(|k| self.methods.get(k))
4304+ .map(|s| s.params.clone());
4305+ if let Some(d) = &declared {
4306+ // params[0] is the receiver for a method with `self`.
4307+ let skip = usize::from(d.len() == m.args.len() + 1);
4308+ for (i, a) in m.args.iter().enumerate() {
4309+ if let Some(want) = d.get(i + skip) {
4310+ let want = want.clone().unvar();
4311+ args[i] = self.expr_at(a, Some(&want))?;
4312+ }
4313+ }
4314+ }
41954315 let mut arg_tys: Vec<Option<Nim>> = vec![recv.ty.clone()];
41964316 arg_tys.extend(args.iter().map(|a| a.ty.clone()));
41974317 let sig = key
@@ -207,6 +207,12 @@ pub struct Lowerer {
207 fn_generics: Vec<String>,207 fn_generics: Vec<String>,
208 /// Type parameters declared by each generic struct or enum.208 /// Type parameters declared by each generic struct or enum.
209 type_generics: HashMap<String, Vec<String>>,209 type_generics: HashMap<String, Vec<String>>,
210+ /// `(type, name) -> type` for `type Item = ..;` inside an `impl`. Rust
211+ /// writes those as `Self::Item`, which has to resolve before any
212+ /// signature mentioning it is mapped.
213+ assoc: HashMap<(String, String), Nim>,
214+ /// `(type, name) -> (nim name, type)` for `const` items inside an `impl`.
215+ assoc_consts: HashMap<(String, String), (String, Nim)>,
210 /// `use` brings a name into scope from another module. Flattening loses216 /// `use` brings a name into scope from another module. Flattening loses
211 /// the module structure, so the mapping is recorded and consulted when a217 /// the module structure, so the mapping is recorded and consulted when a
212 /// bare call is resolved.218 /// bare call is resolved.
@@ -280,6 +286,8 @@ impl Lowerer {
280 impl_generics: Vec::new(),286 impl_generics: Vec::new(),
281 fn_generics: Vec::new(),287 fn_generics: Vec::new(),
282 type_generics: HashMap::new(),288 type_generics: HashMap::new(),
289+ assoc: HashMap::new(),
290+ assoc_consts: HashMap::new(),
283 use_map: HashMap::new(),291 use_map: HashMap::new(),
284 structs: HashMap::new(),292 structs: HashMap::new(),
285 enums: HashMap::new(),293 enums: HashMap::new(),
@@ -630,6 +638,14 @@ impl Lowerer {
630 {638 {
631 let self_ty = self_ty.clone();639 let self_ty = self_ty.clone();
632 let tyname = type_name(&self_ty);640 let tyname = type_name(&self_ty);
641+ // Associated types first: a signature in the same block may name
642+ // one, and it has to resolve by the time that signature is mapped.
643+ for it in &im.items {
644+ if let syn::ImplItem::Type(t) = it {
645+ let v = self.map_ty(&t.ty)?;
646+ self.assoc.insert((tyname.clone(), t.ident.to_string()), v);
647+ }
648+ }
633 if let Some((path, _)) = &im.trait_ {649 if let Some((path, _)) = &im.trait_ {
634 let tr = path_name(path);650 let tr = path_name(path);
635 if im.items.is_empty() {651 if im.items.is_empty() {
@@ -672,8 +688,16 @@ impl Lowerer {
672 self.op_impls.insert((tyname.clone(), op.to_string()), ());688 self.op_impls.insert((tyname.clone(), op.to_string()), ());
673 }689 }
674 for it in &im.items {690 for it in &im.items {
691+ // Already recorded above; a const is emitted with the
692+ // bodies.
693+ if matches!(it, syn::ImplItem::Type(_) | syn::ImplItem::Const(_)) {
694+ continue;
695+ }
675 let syn::ImplItem::Fn(m) = it else {696 let syn::ImplItem::Fn(m) = it else {
676- return Err(format!("unsupported item in `impl {tr}`"));697+ return Err(format!(
698+ "unsupported item in `impl {tr}`: only `fn`, \
699+ `type` and `const` are implemented"
700+ ));
677 };701 };
678 let mname = m.sig.ident.to_string();702 let mname = m.sig.ident.to_string();
679 let (mut params, ret) = self.signature(&m.sig)?;703 let (mut params, ret) = self.signature(&m.sig)?;
@@ -821,6 +845,23 @@ impl Lowerer {
821 /// lowering goes through here rather than calling `ty::map` directly, so845 /// lowering goes through here rather than calling `ty::map` directly, so
822 /// an alias cannot be missed in one position and honoured in another.846 /// an alias cannot be missed in one position and honoured in another.
823 fn map_ty(&self, t: &syn::Type) -> Result<Nim, String> {847 fn map_ty(&self, t: &syn::Type) -> Result<Nim, String> {
848+ // `Self::Item` names an associated type of the enclosing `impl`.
849+ if let syn::Type::Path(p) = t {
850+ let segs: Vec<String> =
851+ p.path.segments.iter().map(|s| s.ident.to_string()).collect();
852+ if segs.len() == 2 {
853+ let owner = if segs[0] == "Self" {
854+ self.self_ty.as_ref().map(type_name)
855+ } else {
856+ Some(segs[0].clone())
857+ };
858+ if let Some(o) = owner {
859+ if let Some(a) = self.assoc.get(&(o, segs[1].clone())) {
860+ return Ok(a.clone());
861+ }
862+ }
863+ }
864+ }
824 let n = ty::map(&self.expand(t, 0)?)?;865 let n = ty::map(&self.expand(t, 0)?)?;
825 Ok(self.subst_self(n))866 Ok(self.subst_self(n))
826 }867 }
@@ -1185,6 +1226,26 @@ impl Lowerer {
1185 Ok(())1226 Ok(())
1186 }1227 }
1187 Item::Type(_) => Ok(()), // expanded at every use site1228 Item::Type(_) => Ok(()), // expanded at every use site
1229+ Item::Trait(t) => {
1230+ // We do not model trait resolution, so a declaration generates
1231+ // nothing and a use that needed it is rejected where it
1232+ // appears. A *default body*, though, is code: dropping it
1233+ // would silently remove a method the impls inherit.
1234+ for it in &t.items {
1235+ if let syn::TraitItem::Fn(f) = it {
1236+ if f.default.is_some() {
1237+ return Err(format!(
1238+ "`trait {}` gives `{}` a default body; trait \
1239+ resolution is not modelled, so that body has no \
1240+ impl to be emitted into and dropping it would \
1241+ remove code",
1242+ t.ident, f.sig.ident
1243+ ));
1244+ }
1245+ }
1246+ }
1247+ Ok(())
1248+ }
1188 Item::Enum(e) => {1249 Item::Enum(e) => {
1189 let def = self.enums[&e.ident.to_string()].clone();1250 let def = self.enums[&e.ident.to_string()].clone();
1190 self.emit_enum(&def);1251 self.emit_enum(&def);
@@ -1285,8 +1346,18 @@ impl Lowerer {
1285 }1346 }
1286 let tyname = type_name(self_ty);1347 let tyname = type_name(self_ty);
1287 for it in &im.items {1348 for it in &im.items {
1349+ if let syn::ImplItem::Const(c) = it {
1350+ self.assoc_const(&tyname, c)?;
1351+ continue;
1352+ }
1353+ if matches!(it, syn::ImplItem::Type(_)) {
1354+ continue; // a type binding emits nothing
1355+ }
1288 let syn::ImplItem::Fn(m) = it else {1356 let syn::ImplItem::Fn(m) = it else {
1289- return Err(format!("unsupported item in `impl {tr}`"));1357+ return Err(format!(
1358+ "unsupported item in `impl {tr}`: only `fn`, `type` and \
1359+ `const` are implemented"
1360+ ));
1290 };1361 };
1291 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };1362 let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None };
1292 let nim = trait_method_name(&tyname, &tr, &m.sig.ident.to_string());1363 let nim = trait_method_name(&tyname, &tr, &m.sig.ident.to_string());
@@ -1294,6 +1365,7 @@ impl Lowerer {
1294 }1365 }
1295 return Ok(());1366 return Ok(());
1296 }1367 }
1368+ let tyname = type_name(self_ty);
1297 for it in &im.items {1369 for it in &im.items {
1298 match it {1370 match it {
1299 syn::ImplItem::Fn(m) => {1371 syn::ImplItem::Fn(m) => {
@@ -1301,12 +1373,31 @@ impl Lowerer {
1301 let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string());1373 let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string());
1302 self.func_named(&nim, &m.sig, &m.block, recv)?;1374 self.func_named(&nim, &m.sig, &m.block, recv)?;
1303 }1375 }
1304- _ => return Err("only `fn` items are supported inside `impl`".into()),1376+ syn::ImplItem::Type(_) => {}
1377+ syn::ImplItem::Const(c) => self.assoc_const(&tyname, c)?,
1378+ _ => {
1379+ return Err("only `fn`, `type` and `const` items are supported \
1380+ inside `impl`"
1381+ .into())
1382+ }
1305 }1383 }
1306 }1384 }
1307 Ok(())1385 Ok(())
1308 }1386 }
1309 1387
1388+ /// `const N: usize = 4;` inside an `impl`. Nim has no per-type constant
1389+ /// namespace, so it becomes a module-level const named for both.
1390+ fn assoc_const(&mut self, tyname: &str, c: &syn::ImplItemConst) -> Result<(), String> {
1391+ let t = self.map_ty(&c.ty)?.owned();
1392+ let v = self.expr_at(&c.expr, Some(&t))?;
1393+ let name = format!("{}_{}", tyname, c.ident);
1394+ self.line(&format!("const {}*: {} = {}", ident(&name), t.render(), v.code));
1395+ self.blank();
1396+ self.assoc_consts
1397+ .insert((tyname.to_string(), c.ident.to_string()), (ident(&name), t));
1398+ Ok(())
1399+ }
1400+
1310 /// The type an operator impl declares for its right-hand operand.1401 /// The type an operator impl declares for its right-hand operand.
1311 fn op_param(&self, t: &Option<Nim>, op: &str) -> Option<Nim> {1402 fn op_param(&self, t: &Option<Nim>, op: &str) -> Option<Nim> {
1312 let n = type_name(t.as_ref()?);1403 let n = type_name(t.as_ref()?);
@@ -2769,6 +2860,18 @@ impl Lowerer {
2769 if name == "None" {2860 if name == "None" {
2770 return Ok(Val::new(self.none_of(expect), expect.cloned()));2861 return Ok(Val::new(self.none_of(expect), expect.cloned()));
2771 }2862 }
2863+ // `Grid::BORDER`: a `const` declared inside an `impl`.
2864+ if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
2865+ let q = if q == "Self" {
2866+ self.self_ty.as_ref().map(type_name).unwrap_or(q)
2867+ } else {
2868+ q
2869+ };
2870+ if let Some((nim, t)) = self.assoc_consts.get(&(q, name.clone())) {
2871+ return Ok(Val::new(nim.clone(), Some(t.clone())));
2872+ }
2873+ }
2874+
2772 // `i32::MAX` and friends: an associated const on a primitive.2875 // `i32::MAX` and friends: an associated const on a primitive.
2773 if matches!(name.as_str(), "MAX" | "MIN") {2876 if matches!(name.as_str(), "MAX" | "MIN") {
2774 if let Some(q) = p.path.segments.iter().rev().nth(1) {2877 if let Some(q) = p.path.segments.iter().rev().nth(1) {
@@ -4192,6 +4295,23 @@ impl Lowerer {
4192 // A method defined in this file via `impl`, found by the4295 // A method defined in this file via `impl`, found by the
4193 // receiver's type rather than by name alone.4296 // receiver's type rather than by name alone.
4194 let key = rt.as_ref().map(|t| (type_name(t), name.clone()));4297 let key = rt.as_ref().map(|t| (type_name(t), name.clone()));
4298+ // Re-lower the arguments with the declared parameter types:
4299+ // a method's own signature says what width its literals are,
4300+ // which the receiver's type does not.
4301+ let declared: Option<Vec<Nim>> = key
4302+ .as_ref()
4303+ .and_then(|k| self.methods.get(k))
4304+ .map(|s| s.params.clone());
4305+ if let Some(d) = &declared {
4306+ // params[0] is the receiver for a method with `self`.
4307+ let skip = usize::from(d.len() == m.args.len() + 1);
4308+ for (i, a) in m.args.iter().enumerate() {
4309+ if let Some(want) = d.get(i + skip) {
4310+ let want = want.clone().unvar();
4311+ args[i] = self.expr_at(a, Some(&want))?;
4312+ }
4313+ }
4314+ }
4195 let mut arg_tys: Vec<Option<Nim>> = vec![recv.ty.clone()];4315 let mut arg_tys: Vec<Option<Nim>> = vec![recv.ty.clone()];
4196 arg_tys.extend(args.iter().map(|a| a.ty.clone()));4316 arg_tys.extend(args.iter().map(|a| a.ty.clone()));
4197 let sig = key4317 let sig = key
added tests/cases/033-associated-types.rs +49 -0
new file mode 100644
@@ -0,0 +1,49 @@
1+// `type Item = ..;` inside an `impl` binds a name the same block's signatures
2+// use as `Self::Item`. A `const` in an `impl` becomes a module-level constant
3+// named for both, since Nim has no per-type constant namespace.
4+
5+struct Grid {
6+ cells: Vec<i32>,
7+ width: usize,
8+}
9+
10+trait Cells {
11+ type Out;
12+ fn get_at(&self, i: usize) -> Self::Out;
13+ fn total(&self) -> Self::Out;
14+}
15+
16+impl Cells for Grid {
17+ type Out = i32;
18+
19+ fn get_at(&self, i: usize) -> Self::Out {
20+ self.cells[i]
21+ }
22+
23+ fn total(&self) -> Self::Out {
24+ let mut t: i32 = 0;
25+ for c in self.cells.iter() {
26+ t += c;
27+ }
28+ t
29+ }
30+}
31+
32+impl Grid {
33+ const BORDER: i32 = 7;
34+
35+ fn at(&self, r: usize, c: usize) -> i32 {
36+ self.cells[r * self.width + c]
37+ }
38+}
39+
40+fn main() {
41+ let g = Grid {
42+ cells: vec![1, 2, 3, 4, 5, 6],
43+ width: 3,
44+ };
45+ println!("{} {}", g.get_at(0), g.get_at(5));
46+ println!("{}", g.total());
47+ println!("{} {}", g.at(0, 2), g.at(1, 0));
48+ println!("{}", Grid::BORDER);
49+}
new file mode 100644
@@ -0,0 +1,49 @@
1+// `type Item = ..;` inside an `impl` binds a name the same block's signatures
2+// use as `Self::Item`. A `const` in an `impl` becomes a module-level constant
3+// named for both, since Nim has no per-type constant namespace.
4+
5+struct Grid {
6+ cells: Vec<i32>,
7+ width: usize,
8+}
9+
10+trait Cells {
11+ type Out;
12+ fn get_at(&self, i: usize) -> Self::Out;
13+ fn total(&self) -> Self::Out;
14+}
15+
16+impl Cells for Grid {
17+ type Out = i32;
18+
19+ fn get_at(&self, i: usize) -> Self::Out {
20+ self.cells[i]
21+ }
22+
23+ fn total(&self) -> Self::Out {
24+ let mut t: i32 = 0;
25+ for c in self.cells.iter() {
26+ t += c;
27+ }
28+ t
29+ }
30+}
31+
32+impl Grid {
33+ const BORDER: i32 = 7;
34+
35+ fn at(&self, r: usize, c: usize) -> i32 {
36+ self.cells[r * self.width + c]
37+ }
38+}
39+
40+fn main() {
41+ let g = Grid {
42+ cells: vec![1, 2, 3, 4, 5, 6],
43+ width: 3,
44+ };
45+ println!("{} {}", g.get_at(0), g.get_at(5));
46+ println!("{}", g.total());
47+ println!("{} {}", g.at(0, 2), g.at(1, 0));
48+ println!("{}", Grid::BORDER);
49+}