Add `cmp`/`partial_cmp`, `Default::default`, `recip` and `to_bits`
`Ordering` becomes `RsOrdering` in the prelude: Nim's `cmp` returns an `int` whose sign carries the same information, and naming the three cases lets a `match` on the result read as it does in Rust. Both spellings are lowered -- the derived impls call `PartialOrd::partial_cmp(&a, &b)`, hand-written code writes `a.cmp(&b)`. A derived `PartialOrd` never answers `None`, so the partial form is the total one in `Some`. `Default::default()` is the zero value, which the expected type decides; without one there is nothing to build and it stays refused. `to_bits` converts the receiver to its declared width before the `cast`. A `cast` reinterprets whatever bytes are there, and an unsuffixed literal is `float64` until something says otherwise -- so `1.0f32.to_bits()` was reading four of the wrong eight bytes and answering 0 where rustc says 1065353216. Caught by the differential runner, which is the point of it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9ee9823 parent: 27c690d modified
src/lower.rs +65 -0 | @@ -6811,6 +6811,32 @@ impl Lowerer { | ||
| 6811 | 6811 | } |
| 6812 | 6812 | } |
| 6813 | 6813 | } |
| 6814 | + // `Ord::cmp` / `PartialOrd::partial_cmp`. Nim's `cmp` returns an | |
| 6815 | + // `int` whose sign carries the same information, and `rsOrd` names | |
| 6816 | + // the three cases. `partial_cmp` is the total one wrapped in | |
| 6817 | + // `Some`: these are the derived impls, and a derived `PartialOrd` | |
| 6818 | + // never answers `None`. | |
| 6819 | + "cmp" | "partial_cmp" if codes.len() == 2 => { | |
| 6820 | + let o = Nim::Named("RsOrdering".into(), Vec::new()); | |
| 6821 | + let code = format!("rsOrd(cmp({}, {}))", codes[0], codes[1]); | |
| 6822 | + return Ok(if name == "cmp" { | |
| 6823 | + Val::new(code, Some(o)) | |
| 6824 | + } else { | |
| 6825 | + Val::new( | |
| 6826 | + format!("rsSome[RsOrdering]({code})"), | |
| 6827 | + Some(Nim::Named("Option".into(), vec![o])), | |
| 6828 | + ) | |
| 6829 | + }); | |
| 6830 | + } | |
| 6831 | + // `Default::default()` is the zero value, which the expected type | |
| 6832 | + // decides. Without one there is nothing to build. | |
| 6833 | + "default" if codes.is_empty() => { | |
| 6834 | + if let Some(t) = expect { | |
| 6835 | + if let Some(d) = default_of(t) { | |
| 6836 | + return Ok(Val::new(d, Some(t.clone()))); | |
| 6837 | + } | |
| 6838 | + } | |
| 6839 | + } | |
| 6814 | 6840 | // `vec![v; n]`, after expansion. |
| 6815 | 6841 | "from_elem" if codes.len() == 2 => { |
| 6816 | 6842 | let t = args[0].ty.clone().map(|t| Nim::Seq(Box::new(t.owned()))); |
| @@ -7244,6 +7270,45 @@ impl Lowerer { | ||
| 7244 | 7270 | "push" => (format!("{}.add({})", recv.code, a0.unwrap_or_default()), Some(Nim::Unit)), |
| 7245 | 7271 | // `clear` drops every element and keeps the allocation, which is |
| 7246 | 7272 | // what setting the length to zero does in Nim too. |
| 7273 | + // `f.recip()` is `1/f`, and `to_bits`/`from_bits` reinterpret the | |
| 7274 | + // float's bytes -- which is what Nim's `cast` means. | |
| 7275 | + // The method spelling of `Ord::cmp`; the derived impls use the | |
| 7276 | + // free-function one, hand-written code this. | |
| 7277 | + "cmp" | "partial_cmp" if m.args.len() == 1 => { | |
| 7278 | + let o = Nim::Named("RsOrdering".into(), Vec::new()); | |
| 7279 | + let code = format!( | |
| 7280 | + "rsOrd(cmp({}, {}))", | |
| 7281 | + recv.code, | |
| 7282 | + a0.clone().unwrap_or_default() | |
| 7283 | + ); | |
| 7284 | + if name == "cmp" { | |
| 7285 | + (code, Some(o)) | |
| 7286 | + } else { | |
| 7287 | + ( | |
| 7288 | + format!("rsSome[RsOrdering]({code})"), | |
| 7289 | + Some(Nim::Named("Option".into(), vec![o])), | |
| 7290 | + ) | |
| 7291 | + } | |
| 7292 | + } | |
| 7293 | + "recip" if matches!(&rt, Some(Nim::Prim(p)) if p.starts_with("float")) => { | |
| 7294 | + let t = rt.clone().unwrap(); | |
| 7295 | + (format!("({}(1.0) / {})", t.render(), recv.code), Some(t)) | |
| 7296 | + } | |
| 7297 | + "to_bits" if matches!(&rt, Some(Nim::Prim(p)) if p.starts_with("float")) => { | |
| 7298 | + let from = match &rt { | |
| 7299 | + Some(Nim::Prim(p)) => p.clone(), | |
| 7300 | + _ => unreachable!(), | |
| 7301 | + }; | |
| 7302 | + let to = if from == "float32" { "uint32" } else { "uint64" }; | |
| 7303 | + // The receiver is converted to its declared width first: a | |
| 7304 | + // `cast` reinterprets whatever bytes are there, and an | |
| 7305 | + // unsuffixed literal is `float64` until something says | |
| 7306 | + // otherwise -- which would take the wrong four of eight. | |
| 7307 | + ( | |
| 7308 | + format!("cast[{}]({}({}))", to, from, recv.code), | |
| 7309 | + Some(Nim::Prim(to.into())), | |
| 7310 | + ) | |
| 7311 | + } | |
| 7247 | 7312 | "clear" if matches!(rt, Some(Nim::Seq(_)) | Some(Nim::Prim(_))) => { |
| 7248 | 7313 | (format!("{}.setLen(0)", recv.code), Some(Nim::Unit)) |
| 7249 | 7314 | } |
| @@ -6811,6 +6811,32 @@ impl Lowerer { | |||
| 6811 | } | 6811 | } |
| 6812 | } | 6812 | } |
| 6813 | } | 6813 | } |
| 6814 | + // `Ord::cmp` / `PartialOrd::partial_cmp`. Nim's `cmp` returns an | ||
| 6815 | + // `int` whose sign carries the same information, and `rsOrd` names | ||
| 6816 | + // the three cases. `partial_cmp` is the total one wrapped in | ||
| 6817 | + // `Some`: these are the derived impls, and a derived `PartialOrd` | ||
| 6818 | + // never answers `None`. | ||
| 6819 | + "cmp" | "partial_cmp" if codes.len() == 2 => { | ||
| 6820 | + let o = Nim::Named("RsOrdering".into(), Vec::new()); | ||
| 6821 | + let code = format!("rsOrd(cmp({}, {}))", codes[0], codes[1]); | ||
| 6822 | + return Ok(if name == "cmp" { | ||
| 6823 | + Val::new(code, Some(o)) | ||
| 6824 | + } else { | ||
| 6825 | + Val::new( | ||
| 6826 | + format!("rsSome[RsOrdering]({code})"), | ||
| 6827 | + Some(Nim::Named("Option".into(), vec![o])), | ||
| 6828 | + ) | ||
| 6829 | + }); | ||
| 6830 | + } | ||
| 6831 | + // `Default::default()` is the zero value, which the expected type | ||
| 6832 | + // decides. Without one there is nothing to build. | ||
| 6833 | + "default" if codes.is_empty() => { | ||
| 6834 | + if let Some(t) = expect { | ||
| 6835 | + if let Some(d) = default_of(t) { | ||
| 6836 | + return Ok(Val::new(d, Some(t.clone()))); | ||
| 6837 | + } | ||
| 6838 | + } | ||
| 6839 | + } | ||
| 6814 | // `vec![v; n]`, after expansion. | 6840 | // `vec![v; n]`, after expansion. |
| 6815 | "from_elem" if codes.len() == 2 => { | 6841 | "from_elem" if codes.len() == 2 => { |
| 6816 | let t = args[0].ty.clone().map(|t| Nim::Seq(Box::new(t.owned()))); | 6842 | let t = args[0].ty.clone().map(|t| Nim::Seq(Box::new(t.owned()))); |
| @@ -7244,6 +7270,45 @@ impl Lowerer { | |||
| 7244 | "push" => (format!("{}.add({})", recv.code, a0.unwrap_or_default()), Some(Nim::Unit)), | 7270 | "push" => (format!("{}.add({})", recv.code, a0.unwrap_or_default()), Some(Nim::Unit)), |
| 7245 | // `clear` drops every element and keeps the allocation, which is | 7271 | // `clear` drops every element and keeps the allocation, which is |
| 7246 | // what setting the length to zero does in Nim too. | 7272 | // what setting the length to zero does in Nim too. |
| 7273 | + // `f.recip()` is `1/f`, and `to_bits`/`from_bits` reinterpret the | ||
| 7274 | + // float's bytes -- which is what Nim's `cast` means. | ||
| 7275 | + // The method spelling of `Ord::cmp`; the derived impls use the | ||
| 7276 | + // free-function one, hand-written code this. | ||
| 7277 | + "cmp" | "partial_cmp" if m.args.len() == 1 => { | ||
| 7278 | + let o = Nim::Named("RsOrdering".into(), Vec::new()); | ||
| 7279 | + let code = format!( | ||
| 7280 | + "rsOrd(cmp({}, {}))", | ||
| 7281 | + recv.code, | ||
| 7282 | + a0.clone().unwrap_or_default() | ||
| 7283 | + ); | ||
| 7284 | + if name == "cmp" { | ||
| 7285 | + (code, Some(o)) | ||
| 7286 | + } else { | ||
| 7287 | + ( | ||
| 7288 | + format!("rsSome[RsOrdering]({code})"), | ||
| 7289 | + Some(Nim::Named("Option".into(), vec![o])), | ||
| 7290 | + ) | ||
| 7291 | + } | ||
| 7292 | + } | ||
| 7293 | + "recip" if matches!(&rt, Some(Nim::Prim(p)) if p.starts_with("float")) => { | ||
| 7294 | + let t = rt.clone().unwrap(); | ||
| 7295 | + (format!("({}(1.0) / {})", t.render(), recv.code), Some(t)) | ||
| 7296 | + } | ||
| 7297 | + "to_bits" if matches!(&rt, Some(Nim::Prim(p)) if p.starts_with("float")) => { | ||
| 7298 | + let from = match &rt { | ||
| 7299 | + Some(Nim::Prim(p)) => p.clone(), | ||
| 7300 | + _ => unreachable!(), | ||
| 7301 | + }; | ||
| 7302 | + let to = if from == "float32" { "uint32" } else { "uint64" }; | ||
| 7303 | + // The receiver is converted to its declared width first: a | ||
| 7304 | + // `cast` reinterprets whatever bytes are there, and an | ||
| 7305 | + // unsuffixed literal is `float64` until something says | ||
| 7306 | + // otherwise -- which would take the wrong four of eight. | ||
| 7307 | + ( | ||
| 7308 | + format!("cast[{}]({}({}))", to, from, recv.code), | ||
| 7309 | + Some(Nim::Prim(to.into())), | ||
| 7310 | + ) | ||
| 7311 | + } | ||
| 7247 | "clear" if matches!(rt, Some(Nim::Seq(_)) | Some(Nim::Prim(_))) => { | 7312 | "clear" if matches!(rt, Some(Nim::Seq(_)) | Some(Nim::Prim(_))) => { |
| 7248 | (format!("{}.setLen(0)", recv.code), Some(Nim::Unit)) | 7313 | (format!("{}.setLen(0)", recv.code), Some(Nim::Unit)) |
| 7249 | } | 7314 | } |
modified
src/prelude.nim +17 -0 | @@ -327,3 +327,20 @@ template rsArrayFill*(n: static int, T: typedesc, v: untyped): untyped = | ||
| 327 | 327 | ## like `Result<T, !>` has something to name. No translated program can |
| 328 | 328 | ## construct one, because no Rust program can. |
| 329 | 329 | type RsNever* = object |
| 330 | + | |
| 331 | +## `core::cmp::Ordering`. Nim's `cmp` returns an `int` whose sign carries the | |
| 332 | +## same information; this names the three cases so a `match` on them reads as | |
| 333 | +## it does in Rust. | |
| 334 | +type RsOrdering* = enum | |
| 335 | + rsLess, rsEqual, rsGreater | |
| 336 | + | |
| 337 | +proc rsOrd*(n: int): RsOrdering = | |
| 338 | + if n < 0: rsLess elif n > 0: rsGreater else: rsEqual | |
| 339 | + | |
| 340 | +## Rust prints these as `Less` / `Equal` / `Greater`. | |
| 341 | +proc rsDebug*(o: RsOrdering): string = | |
| 342 | + case o | |
| 343 | + of rsLess: "Less" | |
| 344 | + of rsEqual: "Equal" | |
| 345 | + of rsGreater: "Greater" | |
| 346 | + | |
| @@ -327,3 +327,20 @@ template rsArrayFill*(n: static int, T: typedesc, v: untyped): untyped = | |||
| 327 | ## like `Result<T, !>` has something to name. No translated program can | 327 | ## like `Result<T, !>` has something to name. No translated program can |
| 328 | ## construct one, because no Rust program can. | 328 | ## construct one, because no Rust program can. |
| 329 | type RsNever* = object | 329 | type RsNever* = object |
| 330 | + | ||
| 331 | +## `core::cmp::Ordering`. Nim's `cmp` returns an `int` whose sign carries the | ||
| 332 | +## same information; this names the three cases so a `match` on them reads as | ||
| 333 | +## it does in Rust. | ||
| 334 | +type RsOrdering* = enum | ||
| 335 | + rsLess, rsEqual, rsGreater | ||
| 336 | + | ||
| 337 | +proc rsOrd*(n: int): RsOrdering = | ||
| 338 | + if n < 0: rsLess elif n > 0: rsGreater else: rsEqual | ||
| 339 | + | ||
| 340 | +## Rust prints these as `Less` / `Equal` / `Greater`. | ||
| 341 | +proc rsDebug*(o: RsOrdering): string = | ||
| 342 | + case o | ||
| 343 | + of rsLess: "Less" | ||
| 344 | + of rsEqual: "Equal" | ||
| 345 | + of rsGreater: "Greater" | ||
| 346 | + | ||
modified
tests/cases/066-std-subset.rs +7 -0 | @@ -39,6 +39,13 @@ fn main() { | ||
| 39 | 39 | t.clear(); |
| 40 | 40 | println!("[{}] {}", t, t.len()); |
| 41 | 41 | |
| 42 | + // `cmp` and `partial_cmp`; `recip` and `to_bits`. | |
| 43 | + println!("{:?} {:?}", 3u32.cmp(&5), 5u32.cmp(&3)); | |
| 44 | + println!("{:?}", 4u32.cmp(&4)); | |
| 45 | + println!("{:?}", 3u32.partial_cmp(&5)); | |
| 46 | + println!("{} {}", 4.0f64.recip(), 0.5f32.recip()); | |
| 47 | + println!("{} {}", 1.0f32.to_bits(), 2.0f64.to_bits()); | |
| 48 | + | |
| 42 | 49 | // `#[derive(PartialOrd)]` compares discriminants. |
| 43 | 50 | println!("{} {}", Rank::Low < Rank::High, Rank::Mid > Rank::High); |
| 44 | 51 | } |
| @@ -39,6 +39,13 @@ fn main() { | |||
| 39 | t.clear(); | 39 | t.clear(); |
| 40 | println!("[{}] {}", t, t.len()); | 40 | println!("[{}] {}", t, t.len()); |
| 41 | 41 | ||
| 42 | + // `cmp` and `partial_cmp`; `recip` and `to_bits`. | ||
| 43 | + println!("{:?} {:?}", 3u32.cmp(&5), 5u32.cmp(&3)); | ||
| 44 | + println!("{:?}", 4u32.cmp(&4)); | ||
| 45 | + println!("{:?}", 3u32.partial_cmp(&5)); | ||
| 46 | + println!("{} {}", 4.0f64.recip(), 0.5f32.recip()); | ||
| 47 | + println!("{} {}", 1.0f32.to_bits(), 2.0f64.to_bits()); | ||
| 48 | + | ||
| 42 | // `#[derive(PartialOrd)]` compares discriminants. | 49 | // `#[derive(PartialOrd)]` compares discriminants. |
| 43 | println!("{} {}", Rank::Low < Rank::High, Rank::Mid > Rank::High); | 50 | println!("{} {}", Rank::Low < Rank::High, Rank::Mid > Rank::High); |
| 44 | } | 51 | } |