Rollup merge of #105793 - lukas-code:circular-deps, r=Mark-Simulacrum
Add note for mismatched types because of circular dependencies If you have crate A with a dependency on crate B, and crate B with a dev-dependency on A, then you might see "mismatched types" errors on types that seem to be equal. This PR adds a note that explains that the types are different, because crate B is compiled twice, one time with `cfg(test)` and one time without. I haven't found a good way to create circular dependencies in UI tests, so I abused the incremental tests instead. As a bonus, incremental tests support "cpass" now. related to https://github.com/rust-lang/rust/issues/22750
This commit is contained in:
commit
e9c25b4ad5
4 changed files with 96 additions and 22 deletions
|
@ -615,9 +615,10 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let report_path_match = |err: &mut Diagnostic, did1: DefId, did2: DefId| {
|
let report_path_match = |err: &mut Diagnostic, did1: DefId, did2: DefId| {
|
||||||
// Only external crates, if either is from a local
|
// Only report definitions from different crates. If both definitions
|
||||||
// module we could have false positives
|
// are from a local module we could have false positives, e.g.
|
||||||
if !(did1.is_local() || did2.is_local()) && did1.krate != did2.krate {
|
// let _ = [{struct Foo; Foo}, {struct Foo; Foo}];
|
||||||
|
if did1.krate != did2.krate {
|
||||||
let abs_path =
|
let abs_path =
|
||||||
|def_id| AbsolutePathPrinter { tcx: self.tcx }.print_def_path(def_id, &[]);
|
|def_id| AbsolutePathPrinter { tcx: self.tcx }.print_def_path(def_id, &[]);
|
||||||
|
|
||||||
|
@ -629,10 +630,16 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
|
||||||
};
|
};
|
||||||
if same_path().unwrap_or(false) {
|
if same_path().unwrap_or(false) {
|
||||||
let crate_name = self.tcx.crate_name(did1.krate);
|
let crate_name = self.tcx.crate_name(did1.krate);
|
||||||
err.note(&format!(
|
let msg = if did1.is_local() || did2.is_local() {
|
||||||
"perhaps two different versions of crate `{}` are being used?",
|
format!(
|
||||||
crate_name
|
"the crate `{crate_name}` is compiled multiple times, possibly with different configurations"
|
||||||
));
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"perhaps two different versions of crate `{crate_name}` are being used?"
|
||||||
|
)
|
||||||
|
};
|
||||||
|
err.note(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -278,13 +278,15 @@ impl<'test> TestCx<'test> {
|
||||||
Incremental => {
|
Incremental => {
|
||||||
let revision =
|
let revision =
|
||||||
self.revision.expect("incremental tests require a list of revisions");
|
self.revision.expect("incremental tests require a list of revisions");
|
||||||
if revision.starts_with("rpass") || revision.starts_with("rfail") {
|
if revision.starts_with("cpass")
|
||||||
|
|| revision.starts_with("rpass")
|
||||||
|
|| revision.starts_with("rfail")
|
||||||
|
{
|
||||||
true
|
true
|
||||||
} else if revision.starts_with("cfail") {
|
} else if revision.starts_with("cfail") {
|
||||||
// FIXME: would be nice if incremental revs could start with "cpass"
|
|
||||||
pm.is_some()
|
pm.is_some()
|
||||||
} else {
|
} else {
|
||||||
panic!("revision name must begin with rpass, rfail, or cfail");
|
panic!("revision name must begin with cpass, rpass, rfail, or cfail");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mode => panic!("unimplemented for mode {:?}", mode),
|
mode => panic!("unimplemented for mode {:?}", mode),
|
||||||
|
@ -384,6 +386,20 @@ impl<'test> TestCx<'test> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_cpass_test(&self) {
|
||||||
|
let emit_metadata = self.should_emit_metadata(self.pass_mode());
|
||||||
|
let proc_res = self.compile_test(WillExecute::No, emit_metadata);
|
||||||
|
|
||||||
|
if !proc_res.status.success() {
|
||||||
|
self.fatal_proc_rec("compilation failed!", &proc_res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME(#41968): Move this check to tidy?
|
||||||
|
if !errors::load_errors(&self.testpaths.file, self.revision).is_empty() {
|
||||||
|
self.fatal("compile-pass tests with expected warnings should be moved to ui/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn run_rpass_test(&self) {
|
fn run_rpass_test(&self) {
|
||||||
let emit_metadata = self.should_emit_metadata(self.pass_mode());
|
let emit_metadata = self.should_emit_metadata(self.pass_mode());
|
||||||
let should_run = self.run_if_enabled();
|
let should_run = self.run_if_enabled();
|
||||||
|
@ -393,17 +409,15 @@ impl<'test> TestCx<'test> {
|
||||||
self.fatal_proc_rec("compilation failed!", &proc_res);
|
self.fatal_proc_rec("compilation failed!", &proc_res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME(#41968): Move this check to tidy?
|
||||||
|
if !errors::load_errors(&self.testpaths.file, self.revision).is_empty() {
|
||||||
|
self.fatal("run-pass tests with expected warnings should be moved to ui/");
|
||||||
|
}
|
||||||
|
|
||||||
if let WillExecute::Disabled = should_run {
|
if let WillExecute::Disabled = should_run {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME(#41968): Move this check to tidy?
|
|
||||||
let expected_errors = errors::load_errors(&self.testpaths.file, self.revision);
|
|
||||||
assert!(
|
|
||||||
expected_errors.is_empty(),
|
|
||||||
"run-pass tests with expected warnings should be moved to ui/"
|
|
||||||
);
|
|
||||||
|
|
||||||
let proc_res = self.exec_compiled_test();
|
let proc_res = self.exec_compiled_test();
|
||||||
if !proc_res.status.success() {
|
if !proc_res.status.success() {
|
||||||
self.fatal_proc_rec("test run failed!", &proc_res);
|
self.fatal_proc_rec("test run failed!", &proc_res);
|
||||||
|
@ -2913,10 +2927,11 @@ impl<'test> TestCx<'test> {
|
||||||
fn run_incremental_test(&self) {
|
fn run_incremental_test(&self) {
|
||||||
// Basic plan for a test incremental/foo/bar.rs:
|
// Basic plan for a test incremental/foo/bar.rs:
|
||||||
// - load list of revisions rpass1, cfail2, rpass3
|
// - load list of revisions rpass1, cfail2, rpass3
|
||||||
// - each should begin with `rpass`, `cfail`, or `rfail`
|
// - each should begin with `cpass`, `rpass`, `cfail`, or `rfail`
|
||||||
// - if `rpass`, expect compile and execution to succeed
|
// - if `cpass`, expect compilation to succeed, don't execute
|
||||||
|
// - if `rpass`, expect compilation and execution to succeed
|
||||||
// - if `cfail`, expect compilation to fail
|
// - if `cfail`, expect compilation to fail
|
||||||
// - if `rfail`, expect execution to fail
|
// - if `rfail`, expect compilation to succeed and execution to fail
|
||||||
// - create a directory build/foo/bar.incremental
|
// - create a directory build/foo/bar.incremental
|
||||||
// - compile foo/bar.rs with -C incremental=.../foo/bar.incremental and -C rpass1
|
// - compile foo/bar.rs with -C incremental=.../foo/bar.incremental and -C rpass1
|
||||||
// - because name of revision starts with "rpass", expect success
|
// - because name of revision starts with "rpass", expect success
|
||||||
|
@ -2940,7 +2955,12 @@ impl<'test> TestCx<'test> {
|
||||||
print!("revision={:?} props={:#?}", revision, self.props);
|
print!("revision={:?} props={:#?}", revision, self.props);
|
||||||
}
|
}
|
||||||
|
|
||||||
if revision.starts_with("rpass") {
|
if revision.starts_with("cpass") {
|
||||||
|
if self.props.should_ice {
|
||||||
|
self.fatal("can only use should-ice in cfail tests");
|
||||||
|
}
|
||||||
|
self.run_cpass_test();
|
||||||
|
} else if revision.starts_with("rpass") {
|
||||||
if self.props.should_ice {
|
if self.props.should_ice {
|
||||||
self.fatal("can only use should-ice in cfail tests");
|
self.fatal("can only use should-ice in cfail tests");
|
||||||
}
|
}
|
||||||
|
@ -2953,7 +2973,7 @@ impl<'test> TestCx<'test> {
|
||||||
} else if revision.starts_with("cfail") {
|
} else if revision.starts_with("cfail") {
|
||||||
self.run_cfail_test();
|
self.run_cfail_test();
|
||||||
} else {
|
} else {
|
||||||
self.fatal("revision name must begin with rpass, rfail, or cfail");
|
self.fatal("revision name must begin with cpass, rpass, rfail, or cfail");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
tests/incremental/auxiliary/circular-dependencies-aux.rs
Normal file
10
tests/incremental/auxiliary/circular-dependencies-aux.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// edition: 2021
|
||||||
|
// compile-flags: --crate-type lib --extern circular_dependencies={{build-base}}/circular-dependencies/libcircular_dependencies.rmeta --emit dep-info,metadata
|
||||||
|
|
||||||
|
use circular_dependencies::Foo;
|
||||||
|
|
||||||
|
pub fn consume_foo(_: Foo) {}
|
||||||
|
|
||||||
|
pub fn produce_foo() -> Foo {
|
||||||
|
Foo
|
||||||
|
}
|
37
tests/incremental/circular-dependencies.rs
Normal file
37
tests/incremental/circular-dependencies.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// ignore-tidy-linelength
|
||||||
|
// revisions: cpass1 cfail2
|
||||||
|
// edition: 2021
|
||||||
|
// [cpass1] compile-flags: --crate-type lib --emit dep-info,metadata
|
||||||
|
// [cfail2] aux-build: circular-dependencies-aux.rs
|
||||||
|
// [cfail2] compile-flags: --test --extern aux={{build-base}}/circular-dependencies/auxiliary/libcircular_dependencies_aux.rmeta -L dependency={{build-base}}/circular-dependencies
|
||||||
|
|
||||||
|
pub struct Foo;
|
||||||
|
//[cfail2]~^ NOTE `Foo` is defined in the current crate
|
||||||
|
//[cfail2]~| NOTE `Foo` is defined in the current crate
|
||||||
|
//[cfail2]~| NOTE `circular_dependencies::Foo` is defined in crate `circular_dependencies`
|
||||||
|
//[cfail2]~| NOTE `circular_dependencies::Foo` is defined in crate `circular_dependencies`
|
||||||
|
|
||||||
|
pub fn consume_foo(_: Foo) {}
|
||||||
|
//[cfail2]~^ NOTE function defined here
|
||||||
|
|
||||||
|
pub fn produce_foo() -> Foo {
|
||||||
|
Foo
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
aux::consume_foo(produce_foo());
|
||||||
|
//[cfail2]~^ ERROR mismatched types [E0308]
|
||||||
|
//[cfail2]~| NOTE expected `circular_dependencies::Foo`, found `Foo`
|
||||||
|
//[cfail2]~| NOTE arguments to this function are incorrect
|
||||||
|
//[cfail2]~| NOTE `Foo` and `circular_dependencies::Foo` have similar names, but are actually distinct types
|
||||||
|
//[cfail2]~| NOTE the crate `circular_dependencies` is compiled multiple times, possibly with different configurations
|
||||||
|
//[cfail2]~| NOTE function defined here
|
||||||
|
|
||||||
|
consume_foo(aux::produce_foo());
|
||||||
|
//[cfail2]~^ ERROR mismatched types [E0308]
|
||||||
|
//[cfail2]~| NOTE expected `Foo`, found `circular_dependencies::Foo`
|
||||||
|
//[cfail2]~| NOTE arguments to this function are incorrect
|
||||||
|
//[cfail2]~| NOTE `circular_dependencies::Foo` and `Foo` have similar names, but are actually distinct types
|
||||||
|
//[cfail2]~| NOTE the crate `circular_dependencies` is compiled multiple times, possibly with different configurations
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue