5.6 KiB
Relax: known gaps
Open bugs with a minimal repro, ready for a contributor to take. Each one names what rustc does, what Relax does, and where to start. Fix one, add its repro as a fixture (see CONTRIBUTING.md), and delete its entry here in the same PR.
Every repro is a standalone cargo package ([workspace] in its Cargo.toml). Compare
cargo run --release with relax run --quiet <package> from the package directory.
1. A struct whose last field may be unsized reorders that field when sized
trait Tr { fn get(&self) -> u32; }
impl Tr for u32 { fn get(&self) -> u32 { *self } }
struct W<T: ?Sized> { n: u8, t: T }
fn main() {
let w: W<u32> = W { n: 7, t: 0xdead_beef };
let d: &W<dyn Tr> = &w;
println!("n={} t={:#x} off={}", d.n, d.t.get(), (&w.t as *const u32 as usize) - (&w as *const W<u32> as usize));
let b: Box<W<dyn Tr>> = Box::new(W { n: 1, t: 42u32 });
println!("boxed t={}", b.t.get());
}
- rustc:
n=7 t=0xdeadbeef off=4/boxed t=42. - Relax (x86-64, 2026-10-24):
n=239 t=0x7 off=0/boxed t=32257.
W<u32> gets t at offset 0 and n at 4, while the W<dyn Tr> view reads n at 0 and the tail
at 4. rustc never reorders a field that may be unsized: StructKind::MaybeUnsized keeps the last
field last (rustc_abi/src/layout.rs, univariant, end = fields.len() - 1), so the sized and
unsized views agree; the dyn tail's offset is the sized prefix rounded up to the alignment the
vtable gives (rustc_codegen_ssa/src/size_of_val.rs). Relax has the same rule
(libs/relax/types/src/layout.rs, decl_maybe_unsized, Kind::MaybeUnsized), so find the path
that lays out W<u32> here without it. Fixture: tools/run/fixture/diff/.
2. Derive and bang macros resolve by bare name across the whole graph
Workspace with four members:
-
a_macandb_mac: proc-macro crates, each exporting#[proc_macro_derive(Who)], which emitsimpl <Name> { pub fn who() -> &'static str { "a" } }("b"inb_mac). -
lib_b: depends onb_mac,#[derive(b_mac::Who)] pub struct InB;. -
app: depends ona_macandlib_b,use a_mac::Who; #[derive(Who)] struct InApp;, printsInApp::who()andlib_b::InB::who(). -
rustc:
app=a b=b. -
Relax:
app=a b=a.lib_b's derive, written with the full pathb_mac::Who, expandsa_mac's macro.
The registry (libs/relax/front/src/macro_reg.rs, derives / macros) is keyed by the macro's
bare name and the first registered entry wins silently. Real collisions: stable (instability and
rustversion), Error (thiserror and derive_more). Attribute macros already require the macro
crate in the invoking crate's dependency closure (attr_macro_at). Derives and bang macros need
rustc's rule: resolve the path at the invocation (the use that brought it in, or the crate the
path names) among the crates the invoking crate depends on.
3. type Error = std::fmt::Error; in a serde::Serializer impl: E0277 (not reproduced)
Seen in a large graph (PR #1326): "E0277: item Error has an incompatible type for trait" on a
Serializer impl with type Error = std::fmt::Error;. rustc accepts it (serde has
impl ser::Error for fmt::Error). A standalone serde 1.0.229 package with such a Serializer
(spelled std::fmt::Error, or fmt::Error after use std::fmt;) type-checks and runs under Relax
with rustc's output. A repro must find the shape that fails: the impl inside a dependency crate,
serde_core without the std feature, or a facade re-export.
4. One check run spun at full CPU for five minutes (not reproduced)
Seen once (PR #1326) on a serde + instability graph: a check spun for five minutes and finished
in 1.4 s on every repeat. If you see it, attach to the live process (makepad inspect <pid> why-hung) before it ends; a stack beats a repeat.
5. Random tier fuzzer: seed 0x166db5d4c1538fe3 disagrees (register allocation)
cargo test --release -p relax-codegen --test fuzz_tiers random_programs_agree_across_tiers
draws fresh seeds on each run, so it can fail on your machine without any change from you. One
seed found on 2026-10-24 (x86-64): the register-allocation check reports v27 reading r7, which
holds [65]. Reproduce and shrink it with FUZZ_REDUCE=166db5d4c1538fe3 on the same test. That
prints the smallest program that still disagrees and the first tier where it does. Then add the
seed to FOUND_SEEDS in libs/relax/codegen/tests/fuzz_tiers.rs.
6. Late lints still print when the check has an error
#![allow(dead_code)]
fn BadName() {}
struct lower_struct;
fn bad() -> u32 { "no" }
fn main() {}
- rustc: E0308 and the
lower_structwarning (non_camel_case_types is an early lint). NoBadNamewarning: rustc_interface passes.rsanalysisstops at an error of the required analyses (lint errors excluded) beforemisc_checking_3, so the late lint pass (rustc_lint::check_crate) never runs. - Relax: E0308 and both warnings (non_snake_case is reported too).
dead_code, unnameable_types and unfulfilled_lint_expectations already follow that rule
(compiler/src/check.rs analyses_ok, GitHub #1328). The late lints of types/src/lint_late*.rs are
emitted while the bodies are checked, so they need to be held back (or dropped) once an error is
known. Start with a "late" mark on the lints rustc runs in LateLintPass.
7. let mut x assigned but never used: Relax reports at x, rustc at mut x
struct WriteOnly { w: u32 }
fn make_wo() -> u32 { let mut x = WriteOnly { w: 1 }; x.w = 2; 0 }
fn main() { make_wo(); }
- rustc:
2:27: warning: variablexis assigned to, but never used(the span starts atmut). - Relax: the same warning at 2:31.
Start in types/src/lint_liveness.rs (the span of the binding it reports).