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>
8354895 parent: ff34e1b modified
DESIGN.md +51 -14 | @@ -304,7 +304,10 @@ runner) rather than a wrong answer. | ||
| 304 | 304 | ### Still open |
| 305 | 305 | |
| 306 | 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 | 311 | Lifetime parameters are *not* a rejection: they carry no runtime meaning |
| 309 | 312 | and Nim is GC'd, so `fn encode<'a>(..)` lowers fine. |
| 310 | 313 | 7. `saturating_*` and `checked_*` are implemented, detecting overflow on the |
| @@ -390,16 +393,16 @@ is a north star, not a next step. | ||
| 390 | 393 | 400 crates from the local registry (under 4,000 lines each), all their module |
| 391 | 394 | files passed together, first blocker recorded: |
| 392 | 395 | |
| 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** | | |
| 403 | 406 | |
| 404 | 407 | This has now happened twice. Evaluating the host `#[cfg]` predicates cleared |
| 405 | 408 | 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 | 415 | `cosmic-theme`'s spacing model work because their whole stack was ground |
| 413 | 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 | 452 | So the ranking above is not a roadmap — it says which feature is most often |
| 416 | 453 | *first*, which is not the same as which feature finishes a crate. The only |
| 417 | 454 | honest way to add a crate is to pick it and clear its stack, as was done |
| 418 | 455 | twice. |
| 419 | 456 | |
| 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. | |
| 423 | 460 | |
| 424 | 461 | ### `cosmic-theme`: what was reachable |
| 425 | 462 | |
| @@ -304,7 +304,10 @@ runner) rather than a wrong answer. | |||
| 304 | ### Still open | 304 | ### 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 meaning | 311 | 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 the | 313 | 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 module | 393 | 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 cleared | 407 | 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 then | 408 | 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 ground | 415 | `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 often | 452 | 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 only | 453 | *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 done | 454 | 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 types | 457 | +Generics and associated types are now done. By frequency the next are |
| 421 | -(`type Item = ..` inside an `impl`, 87), then unsupported types (60) and trait | 458 | +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 reachable | 461 | ### `cosmic-theme`: what was reachable |
| 425 | 462 | ||
modified
src/lower.rs +123 -3 | @@ -207,6 +207,12 @@ pub struct Lowerer { | ||
| 207 | 207 | fn_generics: Vec<String>, |
| 208 | 208 | /// Type parameters declared by each generic struct or enum. |
| 209 | 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 | 216 | /// `use` brings a name into scope from another module. Flattening loses |
| 211 | 217 | /// the module structure, so the mapping is recorded and consulted when a |
| 212 | 218 | /// bare call is resolved. |
| @@ -280,6 +286,8 @@ impl Lowerer { | ||
| 280 | 286 | impl_generics: Vec::new(), |
| 281 | 287 | fn_generics: Vec::new(), |
| 282 | 288 | type_generics: HashMap::new(), |
| 289 | + assoc: HashMap::new(), | |
| 290 | + assoc_consts: HashMap::new(), | |
| 283 | 291 | use_map: HashMap::new(), |
| 284 | 292 | structs: HashMap::new(), |
| 285 | 293 | enums: HashMap::new(), |
| @@ -630,6 +638,14 @@ impl Lowerer { | ||
| 630 | 638 | { |
| 631 | 639 | let self_ty = self_ty.clone(); |
| 632 | 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 | 649 | if let Some((path, _)) = &im.trait_ { |
| 634 | 650 | let tr = path_name(path); |
| 635 | 651 | if im.items.is_empty() { |
| @@ -672,8 +688,16 @@ impl Lowerer { | ||
| 672 | 688 | self.op_impls.insert((tyname.clone(), op.to_string()), ()); |
| 673 | 689 | } |
| 674 | 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 | 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 | 702 | let mname = m.sig.ident.to_string(); |
| 679 | 703 | let (mut params, ret) = self.signature(&m.sig)?; |
| @@ -821,6 +845,23 @@ impl Lowerer { | ||
| 821 | 845 | /// lowering goes through here rather than calling `ty::map` directly, so |
| 822 | 846 | /// an alias cannot be missed in one position and honoured in another. |
| 823 | 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 | 865 | let n = ty::map(&self.expand(t, 0)?)?; |
| 825 | 866 | Ok(self.subst_self(n)) |
| 826 | 867 | } |
| @@ -1185,6 +1226,26 @@ impl Lowerer { | ||
| 1185 | 1226 | Ok(()) |
| 1186 | 1227 | } |
| 1187 | 1228 | 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 | 1249 | Item::Enum(e) => { |
| 1189 | 1250 | let def = self.enums[&e.ident.to_string()].clone(); |
| 1190 | 1251 | self.emit_enum(&def); |
| @@ -1285,8 +1346,18 @@ impl Lowerer { | ||
| 1285 | 1346 | } |
| 1286 | 1347 | let tyname = type_name(self_ty); |
| 1287 | 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 | 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 | 1362 | let recv = if takes_self(&m.sig) { Some(self_ty.clone()) } else { None }; |
| 1292 | 1363 | let nim = trait_method_name(&tyname, &tr, &m.sig.ident.to_string()); |
| @@ -1294,6 +1365,7 @@ impl Lowerer { | ||
| 1294 | 1365 | } |
| 1295 | 1366 | return Ok(()); |
| 1296 | 1367 | } |
| 1368 | + let tyname = type_name(self_ty); | |
| 1297 | 1369 | for it in &im.items { |
| 1298 | 1370 | match it { |
| 1299 | 1371 | syn::ImplItem::Fn(m) => { |
| @@ -1301,12 +1373,31 @@ impl Lowerer { | ||
| 1301 | 1373 | let nim = self.fn_name(&self.cur_mod, &m.sig.ident.to_string()); |
| 1302 | 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 | 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 | 1401 | /// The type an operator impl declares for its right-hand operand. |
| 1311 | 1402 | fn op_param(&self, t: &Option<Nim>, op: &str) -> Option<Nim> { |
| 1312 | 1403 | let n = type_name(t.as_ref()?); |
| @@ -2769,6 +2860,18 @@ impl Lowerer { | ||
| 2769 | 2860 | if name == "None" { |
| 2770 | 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 | 2875 | // `i32::MAX` and friends: an associated const on a primitive. |
| 2773 | 2876 | if matches!(name.as_str(), "MAX" | "MIN") { |
| 2774 | 2877 | if let Some(q) = p.path.segments.iter().rev().nth(1) { |
| @@ -4192,6 +4295,23 @@ impl Lowerer { | ||
| 4192 | 4295 | // A method defined in this file via `impl`, found by the |
| 4193 | 4296 | // receiver's type rather than by name alone. |
| 4194 | 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 | 4315 | let mut arg_tys: Vec<Option<Nim>> = vec![recv.ty.clone()]; |
| 4196 | 4316 | arg_tys.extend(args.iter().map(|a| a.ty.clone())); |
| 4197 | 4317 | 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 loses | 216 | /// `use` brings a name into scope from another module. Flattening loses |
| 211 | /// the module structure, so the mapping is recorded and consulted when a | 217 | /// 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, so | 845 | /// 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 site | 1228 | 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 the | 4295 | // 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 = key | 4317 | 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 | +} | ||