diff --git a/bootstrap/src/compiler.rs b/bootstrap/src/compiler.rs index a71dbbcaac..f1a9b50035 100644 --- a/bootstrap/src/compiler.rs +++ b/bootstrap/src/compiler.rs @@ -19143,6 +19143,18 @@ impl CCodegen { format!("{}*", base) }; } + // Prefix pointer: t27 spells it `*T`, C spells it `T*`. There was no + // arm for this, so the text passed through verbatim and the header read + // + // uint64_t set(*Bitmap bitmap); + // + // where C parses `Bitmap` as the declared NAME with an implicit-int + // type, then finds a second identifier: "type specifier missing" and + // "expected ')'". 52 generated files carried it. + if let Some(inner) = ty.strip_prefix('*') { + let base = Self::param_type_to_c(inner.trim()); + return format!("{}*", base); + } // Slice types: []Type → Type*, including the `[]const T` spelling -- // without stripping `const` the inner type was the literal string // "const u8" and C received `[]const u8* positional;`. diff --git a/bootstrap/stage0/FROZEN_HASH b/bootstrap/stage0/FROZEN_HASH index ebb719c418..96cbec4b56 100644 --- a/bootstrap/stage0/FROZEN_HASH +++ b/bootstrap/stage0/FROZEN_HASH @@ -1 +1 @@ -82e020cf95b211d048f71ee03840e1c5b3acbdc4192ebabeab8d951f4a26eeda +f54e190f098bb7663bfdea7fdbde6f2450fd38174bf407073fc6f6b17acb4275 diff --git a/bootstrap/tests/pointer_param_c.rs b/bootstrap/tests/pointer_param_c.rs new file mode 100644 index 0000000000..a05bb0b507 --- /dev/null +++ b/bootstrap/tests/pointer_param_c.rs @@ -0,0 +1,66 @@ +//! A t27 `*T` parameter must reach C as `T*`. +//! +//! `param_type_to_c` has arms for `?T`, `[]T`, `[T; N]` and `[N]T`, and had none +//! for the prefix star, so the text passed through verbatim: +//! +//! uint64_t set(*Bitmap bitmap); +//! +//! C parses `Bitmap` there as the declared NAME carrying an implicit-int type, +//! then finds a second identifier -- "type specifier missing, defaults to 'int'" +//! followed by "expected ')'". Fifty-two generated headers carried it. +//! +//! Removing the family moved `cc accepts` by zero, which is the measurement that +//! matters about this backlog rather than a disappointment: of 404 rejected +//! files only 20 are blocked by a single family, so no one family is a lever. +//! What it does move is what comes NEXT -- with pointers typed correctly, the +//! member-access defect (`.` where C needs `->`) becomes visible as its own +//! single-family file. + +use std::io::Write; +use std::process::Command; + +fn gen_c(src: &str) -> String { + let dir = std::env::temp_dir().join(format!("t27-ptrparam-{}-{}", std::process::id(), src.len())); + std::fs::create_dir_all(&dir).expect("temp dir"); + let path = dir.join("m.t27"); + let mut f = std::fs::File::create(&path).expect("write spec"); + f.write_all(src.as_bytes()).expect("write spec"); + let out = Command::new(env!("CARGO_BIN_EXE_t27c")) + .arg("gen-c") + .arg(&path) + .output() + .expect("run t27c"); + let _ = std::fs::remove_dir_all(&dir); + String::from_utf8_lossy(&out.stdout).to_string() +} + +const SPEC: &str = "module ptr\n\nstruct Bitmap {\n bits: u64,\n}\n\nfn set(bitmap: *Bitmap) -> u64 {\n return 1\n}\n"; + +#[test] +fn a_prefix_star_parameter_becomes_a_trailing_star_in_c() { + let c = gen_c(SPEC); + assert!( + c.contains("uint64_t set(Bitmap* bitmap)"), + "expected `Bitmap* bitmap`, got:\n{}", + c.lines().filter(|l| l.contains("set(")).collect::>().join("\n") + ); + assert!( + !c.contains("(*Bitmap bitmap"), + "the prefix star must not survive into C" + ); +} + +/// A pointer to a slice keeps both, and in the right order. +#[test] +fn the_star_arm_recurses_rather_than_prepending_to_raw_text() { + let c = gen_c("module p2\n\nfn f(xs: *[]u8) -> u64 {\n return 1\n}\n"); + let line = c + .lines() + .find(|l| l.contains("f(") && l.contains("xs")) + .unwrap_or_default() + .to_string(); + assert!( + line.contains("uint8_t** xs"), + "a pointer to a slice is `uint8_t**`, got: {line}" + ); +} diff --git a/docs/now/2026-08-30-a-family-removed-and-the-headline-unmoved.md b/docs/now/2026-08-30-a-family-removed-and-the-headline-unmoved.md new file mode 100644 index 0000000000..26a3178c26 --- /dev/null +++ b/docs/now/2026-08-30-a-family-removed-and-the-headline-unmoved.md @@ -0,0 +1,10 @@ +# NOW -- A family removed and the headline unmoved (2026-08-30) + +## A family removed and the headline unmoved (Closes #2929) + +- `param_type_to_c` had arms for `?T`, `[]T`, `[T; N]` and `[N]T` and none for `*T`, so 52 headers carried `uint64_t set(*Bitmap bitmap);` +- in C the star belongs to the declarator: written first, `Bitmap` parses as the NAME with an implicit-int type and the next identifier is unexpected +- files with the family **52 -> 0**; `cc accepts` **174 -> 174** +- that is the measurement, not a disappointment: only 20 of 404 rejected files are blocked by a single family, so no one family is a lever +- what it moves is what comes NEXT -- `member reference type 'Bitmap *' is a pointer; did you mean to use '->'?` is now its own single-family file, invisible while the parameter never parsed +- the arm recurses: `*[]u8` is `uint8_t**`, and prepending the star to raw text gives `[]u8*`