-
Notifications
You must be signed in to change notification settings - Fork 13k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Incremental compilation ICE with from compiler-state-dependent overflow errors #84963
Comments
This is tricky. Adding This means that whether or not the query succeeds or fails depends on the state of global trait selection caches, which is exactly the kind of thing that we want to avoid in order for incremental compilation to work. One solution would be to cache the reached recursion depth as part of the trait selection result. This would allow us to deterministically error when evaluating However, I'm not certain that we actually want to do this. As I understand it, the main role of |
If we only had to do a |
That could even imply that #recursion_limit should be a compiler flag rather than an attribute, in order to leverage the existing machinery for invalidating caches when compiler flags change. |
Triage: I can't reproduce this from the "Detailed reproduction guide". Can you still reproduce? If so, would be great if you could "scriptify" the step-by-step for easier reproduction. |
Still reproduces; need to change it to type S7<T> = S1<S2<S3<S4<S5<S6<T>>>>>>; though |
Here’s a script: use std::{error::Error, fmt::Write, process::Stdio};
fn def_smax(n: u64, out: &mut String) -> String {
let mut n_ = n;
let bits: Vec<u64> = std::iter::from_fn(|| {
(n_ != 0).then(|| {
let b = n_ % 2;
n_ = n_ / 2;
b
})
})
.collect();
writeln!(out, "struct S0<T: ?Sized>(T);").unwrap();
for i in 1..bits.len() {
let i_ = i - 1;
writeln!(out, "type S{i}<T> = S{i_}<S{i_}<T>>;").unwrap();
}
let mut l = bits.len();
if n.is_power_of_two() {
return format!("S{}", l - 1);
}
if l == 0 {
l = 1;
}
write!(out, "type S{l}<T> = ").unwrap();
for (i, _) in bits.iter().enumerate().filter(|(_, &b)| b == 1) {
write!(out, "S{i}<").unwrap();
}
write!(out, "T").unwrap();
for _ in bits.iter().filter(|&&b| b == 1) {
write!(out, ">").unwrap();
}
writeln!(out, ";").unwrap();
format!("S{l}")
}
fn file(n: u64, commented: bool) -> String {
let comment = if commented { "// " } else { "" };
let mut s = String::new();
s += "\
macro_rules! assert_impl {($ty:ty: $($trait:tt)*) => {
const _: () = { fn f<T: $($trait)*>() {} fn g() { f::<$ty>() } };
}}
trait Tr<T: ?Sized> {}
impl Tr<()> for u8 {}
impl<Self_, T: ?Sized> Tr<S0<T>> for Self_ where Self_: Tr<T> {}
\
";
let s_max = def_smax(n, &mut s);
writeln!(
s,
"\n\
trait New<D> {{}}
impl<D: Copy, T: Tr<{s_max}<()>>> New<D> for T {{}}
trait New2<D> {{}}
impl<D> New2<D> for u8 where u8: New<D> {{}}
// asked directly would fail:
{comment}assert_impl!(u8: New2<()>);
// first ask
assert_impl!(u8: Tr<{s_max}<()>>);
// then ask
assert_impl!(u8: New2<()>);
\
"
)
.unwrap();
s
}
type Result<T> = std::result::Result<T, Box<dyn Error>>;
fn test(n: u64) -> Result<bool> {
std::fs::write("repro.rs", file(n, false))?;
let s = std::process::Command::new("rustc")
.args(["repro.rs", "--crate-type=lib"])
.stderr(Stdio::null())
.status()?;
Ok(s.success())
}
fn bisect_true_to_false(mut f: impl FnMut(u64) -> Result<bool>) -> Result<u64> {
if !f(0)? {
panic!("nothing to bisect");
}
// invariant after setup: true for lower, false for upper
let mut lower = 0;
let mut upper = 1;
// setup:
while f(upper)? {
lower = upper;
upper = lower.checked_mul(2).unwrap();
}
// bisection:
while lower + 1 < upper {
let mid = lower + (upper - lower) / 2;
if f(mid)? {
lower = mid;
} else {
upper = mid;
}
}
Ok(upper)
}
fn main() -> Result<()> {
println!("using Rust version:");
let _ = std::process::Command::new("rustc")
.args(["--version"])
// .stderr(Stdio::null())
.status()?;
let n = bisect_true_to_false(test)?;
println!("found suitable nesting: {n}");
let _ = std::fs::remove_dir_all("repro_incremental_build");
std::fs::write("repro.rs", file(n, true))?;
let s = std::process::Command::new("rustc")
.args([
"repro.rs",
"--crate-type=lib",
"-Cincremental=repro_incremental_build",
])
.stderr(Stdio::null())
.status()?;
assert!(s.success());
std::fs::write("repro.rs", file(n, false))?;
let _s = std::process::Command::new("rustc")
.args([
"repro.rs",
"--crate-type=lib",
"-Cincremental=repro_incremental_build",
])
// .stderr(Stdio::null())
.status()?;
std::fs::write("repro.rs", file(n, true))?;
let _ = std::fs::remove_dir_all("repro_incremental_build");
let _ = std::fs::remove_file("librepro.rlib");
Ok(())
} example output: (click “Details”)
|
So… here’s a bunch of traits and a deeply nested types
and here’s a macro that helps us ask the compiler for trait implementations (thanks @digama0)
and if we ask the compiler
then we’ll get an overflow error
So far so good. But if we “first” ask about
u8: Tr<S7<()>>
, then the overflow error disappears:Of course the other way around, this still fails. Or instead of “of course it fails the other way around” let’s rather say: This is pretty bad already since the compilation success depends on the order in which two consts are defined. [Yes,
const
s, look into the definition of that macro ;-)]Anyways. If we add a commented out version of the failing query above
this compiles, and if you uncomment the commented out second line here without
cargo clean
ing your library, incremental compilation suddently becomes veryexcitedunhappy.Detailed reproduction guide (edit: updated, 2023-09)
nightly-2023-09-08
cargo check
assert_impl!(u8: New2<()>);
cargo check
again@rustbot label T-compiler A-incr-comp I-ICE
The text was updated successfully, but these errors were encountered: