Summary
thrust-rustc forwards its command line to rustc without setting --edition (see src/main.rs::main, which only appends --extern thrust_macros=…). As a result, when Thrust is invoked the way the README documents —
$ cargo run -- -Adead_code -C debug-assertions=false file.rs
— the input file is compiled at rustc's default edition, 2015. The ui_test harness, however, compiles every tests/ui file at edition 2021 (ui_test-0.30.7/src/config.rs:155: .add_custom("edition", Edition("2021".into())), reached via ui_test::Config::rustc(..) in tests/ui.rs). So the whole test suite runs at 2021 while the documented user-facing invocation runs at 2015, and the two diverge. Two concrete, user-visible consequences:
- Every malformed
#[thrust_macros::…] annotation is misreported as error[E0433]: failed to resolve: you might be missing crate 'core', hiding the macro's real diagnostic.
- Edition-sensitive programs verify differently — e.g. a disjoint-field-capture closure verifies
safe at --edition 2021 but aborts thrust-rustc at the default edition.
Adding --edition 2021 fixes both. Neither is exercised by the test suite because the suite is pinned to 2021.
Reproduction 1 — annotation diagnostics are masked
repro_sig.rs (a natural mistake: a sig on a unit-returning function, which the macro rejects because it wants an explicit return type):
#[thrust_macros::sig(fn(x: i64))]
fn f(x: i64) { let _ = x; }
fn main() {}
$ cargo run -q -- -Adead_code -C debug-assertions=false repro_sig.rs
error[E0433]: failed to resolve: you might be missing crate `core`
--> repro_sig.rs:1:22
|
1 | #[thrust_macros::sig(fn(x: i64))]
| ^^ you might be missing crate `core`
$ cargo run -q -- -Adead_code -C debug-assertions=false --edition 2021 repro_sig.rs
error: expected `->` and a return type in sig annotation
The --edition 2021 run prints the diagnostic the macro actually intends (collect_sig_annotations in thrust-macros/src/rty.rs:152-157). At the default edition the user gets a baffling "missing crate core" pointing at the annotation and no hint about what is wrong.
This affects every thrust-macros error path, since they all route through syn::Error::to_compile_error(). For example a refinement type missing its |:
#[thrust_macros::param(x: { v: i64 })] // should be { v: i64 | φ }
fn f(x: i64) { let _ = x; }
fn main() {}
$ cargo run -q -- … param_bad.rs # default edition
error[E0433]: failed to resolve: you might be missing crate `core`
$ cargo run -q -- … --edition 2021 param_bad.rs
error: refinement type must contain `|`
Direct confirmation that ::core is the unresolved path at the default edition:
fn main() { ::core::compile_error!("hello"); }
$ cargo run -q -- … core.rs # default edition
error[E0433]: failed to resolve: you might be missing crate `core`
$ cargo run -q -- … --edition 2021 core.rs
error: hello
Reproduction 2 — verification behavior differs by edition
repro_closure.rs — a closure that captures a single struct field:
struct P { a: i64, b: i64 }
fn main() {
let mut p = P { a: 0, b: 5 };
let mut f = || { p.a += 1; };
f();
f();
assert!(p.a == 2 && p.b == 5);
}
$ cargo run -q -- -Adead_code -C debug-assertions=false --edition 2021 repro_closure.rs && echo safe
safe
$ cargo run -q -- -Adead_code -C debug-assertions=false repro_closure.rs # default edition
thread 'rustc' … panicked at src/refine/template.rs:440:21:
not implemented: ty: &'{erased} mut P
Under edition 2021, disjoint closure captures make the closure capture only p.a (a &mut i64, which Thrust handles), and the program verifies. At the default edition the closure captures the whole p, producing a &mut P capture that Thrust cannot model. The point is not that &mut P is unsupported — it is that the same source has a different Thrust outcome purely because of the implicit edition default, and the outcome under the documented invocation is the worse one.
Root cause
src/main.rs builds the rustc argument vector and appends only the thrust_macros extern:
if let Some(path) = thrust_macros_path() {
args.push("--extern".to_owned());
args.push(format!("thrust_macros={}", path.display()));
…
}
let code = rustc_driver::catch_with_exit_code(|| {
rustc_driver::run_compiler(&args, &mut CompilerCalls {})
});
No --edition is injected, so rustc uses its default (2015) unless the user happens to pass one. Meanwhile ui_test forces 2021 for the suite. At edition 2015 the extern prelude does not provide core, so the fully-qualified ::core::compile_error! { … } that syn's to_compile_error() emits fails to resolve (E0433), replacing every annotation diagnostic; and the older closure-capture semantics change what MIR Thrust analyzes.
Why it matters
- Anyone following the README (which never passes
--edition) who mistypes an annotation gets E0433: missing crate 'core' instead of the actual error — the surface-annotation DSL is effectively undebuggable through the documented workflow.
- The behavior a user observes for the documented command can differ from what the test suite guarantees, for the same program.
Expected behavior
thrust-rustc should compile input at the same edition its test suite is validated against (inject --edition 2021 in src/main.rs unless the user overrides it), so that (a) to_compile_error() diagnostics resolve and annotation errors are readable, and (b) the documented invocation matches tested behavior. At minimum, the README examples should pass --edition 2021.
Relation to existing issues
Environment
- thrust @
6953863 (Merge pull request #185 …syn-3.0.2)
- rustc
nightly-2025-09-08 (per rust-toolchain.toml); rustc default edition = 2015
ui_test 0.30.7 (pins test edition to 2021)
- Z3 5.0.0, default (spacer) configuration
- Confirmed by running
thrust-rustc (not inspection-only): the default-edition runs above emit E0433 / the &mut P abort; the --edition 2021 runs emit the real diagnostic / safe.
Summary
thrust-rustcforwards its command line torustcwithout setting--edition(seesrc/main.rs::main, which only appends--extern thrust_macros=…). As a result, when Thrust is invoked the way the README documents —$ cargo run -- -Adead_code -C debug-assertions=false file.rs— the input file is compiled at rustc's default edition, 2015. The
ui_testharness, however, compiles everytests/uifile at edition 2021 (ui_test-0.30.7/src/config.rs:155:.add_custom("edition", Edition("2021".into())), reached viaui_test::Config::rustc(..)intests/ui.rs). So the whole test suite runs at 2021 while the documented user-facing invocation runs at 2015, and the two diverge. Two concrete, user-visible consequences:#[thrust_macros::…]annotation is misreported aserror[E0433]: failed to resolve: you might be missing crate 'core', hiding the macro's real diagnostic.safeat--edition 2021but abortsthrust-rustcat the default edition.Adding
--edition 2021fixes both. Neither is exercised by the test suite because the suite is pinned to 2021.Reproduction 1 — annotation diagnostics are masked
repro_sig.rs(a natural mistake: asigon a unit-returning function, which the macro rejects because it wants an explicit return type):The
--edition 2021run prints the diagnostic the macro actually intends (collect_sig_annotationsinthrust-macros/src/rty.rs:152-157). At the default edition the user gets a baffling "missing cratecore" pointing at the annotation and no hint about what is wrong.This affects every thrust-macros error path, since they all route through
syn::Error::to_compile_error(). For example a refinement type missing its|:Direct confirmation that
::coreis the unresolved path at the default edition:Reproduction 2 — verification behavior differs by edition
repro_closure.rs— a closure that captures a single struct field:Under edition 2021, disjoint closure captures make the closure capture only
p.a(a&mut i64, which Thrust handles), and the program verifies. At the default edition the closure captures the wholep, producing a&mut Pcapture that Thrust cannot model. The point is not that&mut Pis unsupported — it is that the same source has a different Thrust outcome purely because of the implicit edition default, and the outcome under the documented invocation is the worse one.Root cause
src/main.rsbuilds the rustc argument vector and appends only thethrust_macrosextern:No
--editionis injected, so rustc uses its default (2015) unless the user happens to pass one. Meanwhileui_testforces 2021 for the suite. At edition 2015 the extern prelude does not providecore, so the fully-qualified::core::compile_error! { … }thatsyn'sto_compile_error()emits fails to resolve (E0433), replacing every annotation diagnostic; and the older closure-capture semantics change what MIR Thrust analyzes.Why it matters
--edition) who mistypes an annotation getsE0433: missing crate 'core'instead of the actual error — the surface-annotation DSL is effectively undebuggable through the documented workflow.Expected behavior
thrust-rustcshould compile input at the same edition its test suite is validated against (inject--edition 2021insrc/main.rsunless the user overrides it), so that (a)to_compile_error()diagnostics resolve and annotation errors are readable, and (b) the documented invocation matches tested behavior. At minimum, the README examples should pass--edition 2021.Relation to existing issues
&{ v | φ }/&mut { v | φ }) in parameter position is not assumed by the callee #166, Incompleteness: a#[param(name: { v | φ })]precondition is silently dropped when the function also has an#[ensures(..)], so trivially-correct functions are rejected #191, Unsound: a#[param(name: { v | φ })]precondition combined with#[ensures(..)]is dropped at call sites too, so a#[thrust::trusted]function with a violated precondition verifies panicking programs assafe#196): those are about how a well-formed annotation is lowered; this is about the compilation edition under which any input (annotated or not) is processed, and it masks the diagnostics those very features would emit on a typo.const_value_ty, causing wrong CHC terms and potential panic #110/Unsound: negativeSwitchIntmatch targets are sign-truncated to large positives, making match arms verify under a wrong path assumption #132/Unsound: unsigned integer constants with the high bit set (e.g.u8 = 200) are decoded as negative inconst_value_ty, so always-panicking programs verify assafe#180/…): the> 0/field values here are small and in range.Environment
6953863(Merge pull request #185 …syn-3.0.2)nightly-2025-09-08(perrust-toolchain.toml); rustc default edition = 2015ui_test0.30.7 (pins test edition to 2021)thrust-rustc(not inspection-only): the default-edition runs above emitE0433/ the&mut Pabort; the--edition 2021runs emit the real diagnostic /safe.