Merge from rustc

This commit is contained in:
Ralf Jung 2024-08-14 07:43:52 +02:00
commit b65cdffbe4
675 changed files with 9247 additions and 5422 deletions

View file

@ -11,13 +11,19 @@ use std::str::FromStr;
use std::{env, process};
use bootstrap::{
find_recent_config_change_ids, human_readable_changes, t, Build, Config, Subcommand,
find_recent_config_change_ids, human_readable_changes, t, Build, Config, Flags, Subcommand,
CONFIG_CHANGE_HISTORY,
};
fn main() {
let args = env::args().skip(1).collect::<Vec<_>>();
let config = Config::parse(&args);
if Flags::try_parse_verbose_help(&args) {
return;
}
let flags = Flags::parse(&args);
let config = Config::parse(flags);
let mut build_lock;
let _build_lock_guard;

View file

@ -121,7 +121,7 @@ fn clean(build: &Build, all: bool, stage: Option<u32>) {
fn clean_specific_stage(build: &Build, stage: u32) {
for host in &build.hosts {
let entries = match build.out.join(host.triple).read_dir() {
let entries = match build.out.join(host).read_dir() {
Ok(iter) => iter,
Err(_) => continue,
};
@ -148,7 +148,7 @@ fn clean_default(build: &Build) {
rm_rf(&build.out.join("bootstrap-shims-dump"));
rm_rf(&build.out.join("rustfmt.stamp"));
let mut hosts: Vec<_> = build.hosts.iter().map(|t| build.out.join(t.triple)).collect();
let mut hosts: Vec<_> = build.hosts.iter().map(|t| build.out.join(t)).collect();
// After cross-compilation, artifacts of the host architecture (which may differ from build.host)
// might not get removed.
// Adding its path (linked one for easier accessibility) will solve this problem.

View file

@ -246,7 +246,7 @@ impl Step for Std {
.rustc_snapshot_sysroot()
.join("lib")
.join("rustlib")
.join(compiler.host.triple)
.join(compiler.host)
.join("bin");
if src_sysroot_bin.exists() {
let target_sysroot_bin =
@ -432,7 +432,7 @@ fn copy_self_contained_objects(
DependencyType::TargetSelfContained,
);
}
} else if target.ends_with("windows-gnu") {
} else if target.is_windows_gnu() {
for obj in ["crt2.o", "dllcrt2.o"].iter() {
let src = compiler_file(builder, &builder.cc(target), target, CLang::C, obj);
let target = libdir_self_contained.join(obj);
@ -651,8 +651,8 @@ impl Step for StdLink {
compiler: self.compiler,
force_recompile: self.force_recompile,
});
let libdir = sysroot.join(lib).join("rustlib").join(target.triple).join("lib");
let hostdir = sysroot.join(lib).join("rustlib").join(compiler.host.triple).join("lib");
let libdir = sysroot.join(lib).join("rustlib").join(target).join("lib");
let hostdir = sysroot.join(lib).join("rustlib").join(compiler.host).join("lib");
(libdir, hostdir)
} else {
let libdir = builder.sysroot_libdir(target_compiler, target);
@ -670,12 +670,12 @@ impl Step for StdLink {
.build
.config
.initial_rustc
.starts_with(builder.out.join(compiler.host.triple).join("stage0/bin"))
.starts_with(builder.out.join(compiler.host).join("stage0/bin"))
{
// Copy bin files from stage0/bin to stage0-sysroot/bin
let sysroot = builder.out.join(compiler.host.triple).join("stage0-sysroot");
let sysroot = builder.out.join(compiler.host).join("stage0-sysroot");
let host = compiler.host.triple;
let host = compiler.host;
let stage0_bin_dir = builder.out.join(host).join("stage0/bin");
let sysroot_bin_dir = sysroot.join("bin");
t!(fs::create_dir_all(&sysroot_bin_dir));
@ -793,7 +793,7 @@ impl Step for StartupObjects {
fn run(self, builder: &Builder<'_>) -> Vec<(PathBuf, DependencyType)> {
let for_compiler = self.compiler;
let target = self.target;
if !target.ends_with("windows-gnu") {
if !target.is_windows_gnu() {
return vec![];
}
@ -1554,7 +1554,7 @@ impl Step for Sysroot {
/// For all other stages, it's the same stage directory that the compiler lives in.
fn run(self, builder: &Builder<'_>) -> PathBuf {
let compiler = self.compiler;
let host_dir = builder.out.join(compiler.host.triple);
let host_dir = builder.out.join(compiler.host);
let sysroot_dir = |stage| {
if stage == 0 {

View file

@ -275,12 +275,8 @@ fn make_win_dist(
}
//Copy platform tools to platform-specific bin directory
let target_bin_dir = plat_root
.join("lib")
.join("rustlib")
.join(target.triple)
.join("bin")
.join("self-contained");
let target_bin_dir =
plat_root.join("lib").join("rustlib").join(target).join("bin").join("self-contained");
fs::create_dir_all(&target_bin_dir).expect("creating target_bin_dir failed");
for src in target_tools {
builder.copy_link_to_folder(&src, &target_bin_dir);
@ -295,12 +291,8 @@ fn make_win_dist(
);
//Copy platform libs to platform-specific lib directory
let target_lib_dir = plat_root
.join("lib")
.join("rustlib")
.join(target.triple)
.join("lib")
.join("self-contained");
let target_lib_dir =
plat_root.join("lib").join("rustlib").join(target).join("lib").join("self-contained");
fs::create_dir_all(&target_lib_dir).expect("creating target_lib_dir failed");
for src in target_libs {
builder.copy_link_to_folder(&src, &target_lib_dir);
@ -450,7 +442,7 @@ impl Step for Rustc {
// component for now.
maybe_install_llvm_runtime(builder, host, image);
let dst_dir = image.join("lib/rustlib").join(&*host.triple).join("bin");
let dst_dir = image.join("lib/rustlib").join(host).join("bin");
t!(fs::create_dir_all(&dst_dir));
// Copy over lld if it's there
@ -607,7 +599,7 @@ fn verify_uefi_rlib_format(builder: &Builder<'_>, target: TargetSelection, stamp
/// Copy stamped files into an image's `target/lib` directory.
fn copy_target_libs(builder: &Builder<'_>, target: TargetSelection, image: &Path, stamp: &Path) {
let dst = image.join("lib/rustlib").join(target.triple).join("lib");
let dst = image.join("lib/rustlib").join(target).join("lib");
let self_contained_dst = dst.join("self-contained");
t!(fs::create_dir_all(&dst));
t!(fs::create_dir_all(&self_contained_dst));
@ -769,7 +761,7 @@ impl Step for Analysis {
let src = builder
.stage_out(compiler, Mode::Std)
.join(target.triple)
.join(target)
.join(builder.cargo_dir())
.join("deps")
.join("save-analysis");
@ -1509,7 +1501,7 @@ impl Step for Extended {
tarballs.push(builder.ensure(Rustc { compiler: builder.compiler(stage, target) }));
tarballs.push(builder.ensure(Std { compiler, target }).expect("missing std"));
if target.ends_with("windows-gnu") {
if target.is_windows_gnu() {
tarballs.push(builder.ensure(Mingw { host: target }).expect("missing mingw"));
}
@ -1683,7 +1675,7 @@ impl Step for Extended {
prepare(tool);
}
}
if target.ends_with("windows-gnu") {
if target.is_windows_gnu() {
prepare("rust-mingw");
}
@ -1830,7 +1822,7 @@ impl Step for Extended {
.arg("-t")
.arg(etc.join("msi/remove-duplicates.xsl"))
.run(builder);
if target.ends_with("windows-gnu") {
if target.is_windows_gnu() {
command(&heat)
.current_dir(&exe)
.arg("dir")
@ -1876,7 +1868,7 @@ impl Step for Extended {
if built_tools.contains("miri") {
cmd.arg("-dMiriDir=miri");
}
if target.ends_with("windows-gnu") {
if target.is_windows_gnu() {
cmd.arg("-dGccDir=rust-mingw");
}
cmd.run(builder);
@ -1901,7 +1893,7 @@ impl Step for Extended {
}
candle("AnalysisGroup.wxs".as_ref());
if target.ends_with("windows-gnu") {
if target.is_windows_gnu() {
candle("GccGroup.wxs".as_ref());
}
@ -1941,7 +1933,7 @@ impl Step for Extended {
cmd.arg("DocsGroup.wixobj");
}
if target.ends_with("windows-gnu") {
if target.is_windows_gnu() {
cmd.arg("GccGroup.wixobj");
}
// ICE57 wrongly complains about the shortcuts
@ -1973,7 +1965,7 @@ fn add_env(builder: &Builder<'_>, cmd: &mut BootstrapCommand, target: TargetSele
if target.contains("windows-gnullvm") {
cmd.env("CFG_MINGW", "1").env("CFG_ABI", "LLVM");
} else if target.contains("windows-gnu") {
} else if target.is_windows_gnu() {
cmd.env("CFG_MINGW", "1").env("CFG_ABI", "GNU");
} else {
cmd.env("CFG_MINGW", "0").env("CFG_ABI", "MSVC");
@ -2087,7 +2079,7 @@ fn maybe_install_llvm(
/// Maybe add libLLVM.so to the target lib-dir for linking.
pub fn maybe_install_llvm_target(builder: &Builder<'_>, target: TargetSelection, sysroot: &Path) {
let dst_libdir = sysroot.join("lib/rustlib").join(&*target.triple).join("lib");
let dst_libdir = sysroot.join("lib/rustlib").join(target).join("lib");
// We do not need to copy LLVM files into the sysroot if it is not
// dynamically linked; it is already included into librustc_llvm
// statically.

View file

@ -699,13 +699,12 @@ fn doc_std(
let compiler = builder.compiler(stage, builder.config.build);
let target_doc_dir_name = if format == DocumentationFormat::Json { "json-doc" } else { "doc" };
let target_dir =
builder.stage_out(compiler, Mode::Std).join(target.triple).join(target_doc_dir_name);
let target_dir = builder.stage_out(compiler, Mode::Std).join(target).join(target_doc_dir_name);
// This is directory where the compiler will place the output of the command.
// We will then copy the files from this directory into the final `out` directory, the specified
// as a function parameter.
let out_dir = target_dir.join(target.triple).join("doc");
let out_dir = target_dir.join(target).join("doc");
let mut cargo =
builder::Cargo::new(builder, compiler, Mode::Std, SourceType::InTree, target, Kind::Doc);
@ -846,7 +845,7 @@ impl Step for Rustc {
let mut to_open = None;
let out_dir = builder.stage_out(compiler, Mode::Rustc).join(target.triple).join("doc");
let out_dir = builder.stage_out(compiler, Mode::Rustc).join(target).join("doc");
for krate in &*self.crates {
// Create all crate output directories first to make sure rustdoc uses
// relative links.
@ -992,7 +991,7 @@ macro_rules! tool_doc {
// see https://github.com/rust-lang/rust/pull/122066#issuecomment-1983049222
// cargo.rustdocflag("--generate-link-to-definition");
let out_dir = builder.stage_out(compiler, Mode::ToolRustc).join(target.triple).join("doc");
let out_dir = builder.stage_out(compiler, Mode::ToolRustc).join(target).join("doc");
$(for krate in $crates {
let dir_name = krate.replace("-", "_");
t!(fs::create_dir_all(out_dir.join(&*dir_name)));

View file

@ -149,7 +149,7 @@ You can skip linkcheck with --skip src/tools/linkchecker"
let _guard =
builder.msg(Kind::Test, compiler.stage, "Linkcheck", bootstrap_host, bootstrap_host);
let _time = helpers::timeit(builder);
linkchecker.delay_failure().arg(builder.out.join(host.triple).join("doc")).run(builder);
linkchecker.delay_failure().arg(builder.out.join(host).join("doc")).run(builder);
}
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
@ -434,8 +434,8 @@ impl Miri {
builder: &Builder<'_>,
compiler: Compiler,
target: TargetSelection,
) -> String {
let miri_sysroot = builder.out.join(compiler.host.triple).join("miri-sysroot");
) -> PathBuf {
let miri_sysroot = builder.out.join(compiler.host).join("miri-sysroot");
let mut cargo = builder::Cargo::new(
builder,
compiler,
@ -467,7 +467,7 @@ impl Miri {
// Output is "<sysroot>\n".
let sysroot = stdout.trim_end();
builder.verbose(|| println!("`cargo miri setup --print-sysroot` said: {sysroot:?}"));
sysroot.to_owned()
PathBuf::from(sysroot)
}
}
@ -520,12 +520,14 @@ impl Step for Miri {
builder.ensure(compile::Std::new(target_compiler, host));
let host_sysroot = builder.sysroot(target_compiler);
// Miri has its own "target dir" for ui test dependencies. Make sure it gets cleared
// properly when rustc changes. Similar to `Builder::cargo`, we skip this in dry runs to
// make sure the relevant compiler has been set up properly.
// Miri has its own "target dir" for ui test dependencies. Make sure it gets cleared when
// the sysroot gets rebuilt, to avoid "found possibly newer version of crate `std`" errors.
if !builder.config.dry_run() {
let ui_test_dep_dir = builder.stage_out(host_compiler, Mode::ToolStd).join("miri_ui");
builder.clear_if_dirty(&ui_test_dep_dir, &builder.rustc(host_compiler));
// The mtime of `miri_sysroot` changes when the sysroot gets rebuilt (also see
// <https://github.com/RalfJung/rustc-build-sysroot/commit/10ebcf60b80fe2c3dc765af0ff19fdc0da4b7466>).
// We can hence use that directly as a signal to clear the ui test dir.
builder.clear_if_dirty(&ui_test_dep_dir, &miri_sysroot);
}
// Run `cargo test`.
@ -1113,7 +1115,7 @@ HELP: to skip test's attempt to check tidiness, pass `--skip src/tools/tidy` to
}
fn testdir(builder: &Builder<'_>, host: TargetSelection) -> PathBuf {
builder.out.join(host.triple).join("test")
builder.out.join(host).join("test")
}
macro_rules! default_test {
@ -1815,7 +1817,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the
let mut flags = if is_rustdoc { Vec::new() } else { vec!["-Crpath".to_string()] };
flags.push(format!("-Cdebuginfo={}", builder.config.rust_debuginfo_level_tests));
flags.extend(builder.config.cmd.rustc_args().iter().map(|s| s.to_string()));
flags.extend(builder.config.cmd.compiletest_rustc_args().iter().map(|s| s.to_string()));
if suite != "mir-opt" {
if let Some(linker) = builder.linker(target) {
@ -2683,7 +2685,7 @@ impl Step for Crate {
if builder.download_rustc() && compiler.stage > 0 {
let sysroot = builder
.out
.join(compiler.host.triple)
.join(compiler.host)
.join(format!("stage{}-test-sysroot", compiler.stage));
cargo.env("RUSTC_SYSROOT", sysroot);
}

View file

@ -1171,7 +1171,7 @@ impl<'a> Builder<'a> {
.sysroot(self.compiler)
.join(lib)
.join("rustlib")
.join(self.target.triple)
.join(self.target)
.join("lib");
// Avoid deleting the rustlib/ directory we just copied
// (in `impl Step for Sysroot`).
@ -1254,7 +1254,7 @@ impl<'a> Builder<'a> {
// Ensure that the downloaded LLVM libraries can be found.
if self.config.llvm_from_ci {
let ci_llvm_lib = self.out.join(&*compiler.host.triple).join("ci-llvm").join("lib");
let ci_llvm_lib = self.out.join(compiler.host).join("ci-llvm").join("lib");
dylib_dirs.push(ci_llvm_lib);
}
@ -1504,9 +1504,9 @@ impl<'a> Builder<'a> {
Mode::Rustc | Mode::ToolRustc => self.compiler_doc_out(target),
Mode::Std => {
if self.config.cmd.json() {
out_dir.join(target.triple).join("json-doc")
out_dir.join(target).join("json-doc")
} else {
out_dir.join(target.triple).join("doc")
out_dir.join(target).join("doc")
}
}
_ => panic!("doc mode {mode:?} not expected"),
@ -2226,11 +2226,6 @@ impl<'a> Builder<'a> {
rustdocflags.arg("--cfg=parallel_compiler");
}
// Pass the value of `--rustc-args` from test command. If it's not a test command, this won't set anything.
self.config.cmd.rustc_args().iter().for_each(|v| {
rustflags.arg(v);
});
Cargo {
command: cargo,
compiler,

View file

@ -3,13 +3,14 @@ use std::thread;
use super::*;
use crate::core::build_steps::doc::DocumentationFormat;
use crate::core::config::Config;
use crate::Flags;
fn configure(cmd: &str, host: &[&str], target: &[&str]) -> Config {
configure_with_args(&[cmd.to_owned()], host, target)
}
fn configure_with_args(cmd: &[String], host: &[&str], target: &[&str]) -> Config {
let mut config = Config::parse(cmd);
let mut config = Config::parse(Flags::parse(cmd));
// don't save toolstates
config.save_toolstates = None;
config.dry_run = DryRun::SelfCheck;
@ -23,7 +24,7 @@ fn configure_with_args(cmd: &[String], host: &[&str], target: &[&str]) -> Config
let submodule_build = Build::new(Config {
// don't include LLVM, so CI doesn't require ninja/cmake to be installed
rust_codegen_backends: vec![],
..Config::parse(&["check".to_owned()])
..Config::parse(Flags::parse(&["check".to_owned()]))
});
submodule_build.require_submodule("src/doc/book", None);
config.submodules = Some(false);
@ -632,7 +633,7 @@ mod dist {
config.paths = vec!["library/std".into()];
config.cmd = Subcommand::Test {
test_args: vec![],
rustc_args: vec![],
compiletest_rustc_args: vec![],
no_fail_fast: false,
no_doc: true,
doc: false,
@ -703,7 +704,7 @@ mod dist {
let mut config = configure(&["A-A"], &["A-A"]);
config.cmd = Subcommand::Test {
test_args: vec![],
rustc_args: vec![],
compiletest_rustc_args: vec![],
no_fail_fast: false,
doc: true,
no_doc: false,

View file

@ -514,6 +514,10 @@ impl TargetSelection {
self.contains("windows")
}
pub fn is_windows_gnu(&self) -> bool {
self.ends_with("windows-gnu")
}
/// Path to the file defining the custom target, if any.
pub fn filepath(&self) -> Option<&Path> {
self.file.as_ref().map(Path::new)
@ -542,6 +546,14 @@ impl PartialEq<&str> for TargetSelection {
}
}
// Targets are often used as directory names throughout bootstrap.
// This impl makes it more ergonomics to use them as such.
impl AsRef<Path> for TargetSelection {
fn as_ref(&self) -> &Path {
self.triple.as_ref()
}
}
/// Per-target configuration stored in the global configuration structure.
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Target {
@ -1191,7 +1203,7 @@ impl Config {
}
}
pub fn parse(args: &[String]) -> Config {
pub fn parse(flags: Flags) -> Config {
#[cfg(test)]
fn get_toml(_: &Path) -> TomlConfig {
TomlConfig::default()
@ -1221,11 +1233,10 @@ impl Config {
exit!(2);
})
}
Self::parse_inner(args, get_toml)
Self::parse_inner(flags, get_toml)
}
pub(crate) fn parse_inner(args: &[String], get_toml: impl Fn(&Path) -> TomlConfig) -> Config {
let mut flags = Flags::parse(args);
pub(crate) fn parse_inner(mut flags: Flags, get_toml: impl Fn(&Path) -> TomlConfig) -> Config {
let mut config = Config::default_opts();
// Set flags.
@ -1470,7 +1481,7 @@ impl Config {
config.download_beta_toolchain();
config
.out
.join(config.build.triple)
.join(config.build)
.join("stage0")
.join("bin")
.join(exe("rustc", config.build))
@ -1485,7 +1496,7 @@ impl Config {
config.download_beta_toolchain();
config
.out
.join(config.build.triple)
.join(config.build)
.join("stage0")
.join("bin")
.join(exe("cargo", config.build))
@ -2278,13 +2289,13 @@ impl Config {
/// The absolute path to the downloaded LLVM artifacts.
pub(crate) fn ci_llvm_root(&self) -> PathBuf {
assert!(self.llvm_from_ci);
self.out.join(&*self.build.triple).join("ci-llvm")
self.out.join(self.build).join("ci-llvm")
}
/// Directory where the extracted `rustc-dev` component is stored.
pub(crate) fn ci_rustc_dir(&self) -> PathBuf {
assert!(self.download_rustc());
self.out.join(self.build.triple).join("ci-rustc")
self.out.join(self.build).join("ci-rustc")
}
/// Determine whether llvm should be linked dynamically.

View file

@ -183,9 +183,9 @@ pub struct Flags {
}
impl Flags {
pub fn parse(args: &[String]) -> Self {
let first = String::from("x.py");
let it = std::iter::once(&first).chain(args.iter());
/// Check if `<cmd> -h -v` was passed.
/// If yes, print the available paths and return `true`.
pub fn try_parse_verbose_help(args: &[String]) -> bool {
// We need to check for `<cmd> -h -v`, in which case we list the paths
#[derive(Parser)]
#[command(disable_help_flag(true))]
@ -198,10 +198,10 @@ impl Flags {
cmd: Kind,
}
if let Ok(HelpVerboseOnly { help: true, verbose: 1.., cmd: subcommand }) =
HelpVerboseOnly::try_parse_from(it.clone())
HelpVerboseOnly::try_parse_from(normalize_args(args))
{
println!("NOTE: updating submodules before printing available paths");
let config = Config::parse(&[String::from("build")]);
let config = Config::parse(Self::parse(&[String::from("build")]));
let build = Build::new(config);
let paths = Builder::get_help(&build, subcommand);
if let Some(s) = paths {
@ -209,11 +209,21 @@ impl Flags {
} else {
panic!("No paths available for subcommand `{}`", subcommand.as_str());
}
crate::exit!(0);
true
} else {
false
}
Flags::parse_from(it)
}
pub fn parse(args: &[String]) -> Self {
Flags::parse_from(normalize_args(args))
}
}
fn normalize_args(args: &[String]) -> Vec<String> {
let first = String::from("x.py");
let it = std::iter::once(first).chain(args.iter().cloned());
it.collect()
}
#[derive(Debug, Clone, Default, clap::Subcommand)]
@ -347,9 +357,9 @@ pub enum Subcommand {
/// extra arguments to be passed for the test tool being used
/// (e.g. libtest, compiletest or rustdoc)
test_args: Vec<String>,
/// extra options to pass the compiler when running tests
/// extra options to pass the compiler when running compiletest tests
#[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
rustc_args: Vec<String>,
compiletest_rustc_args: Vec<String>,
#[arg(long)]
/// do not run doc tests
no_doc: bool,
@ -392,9 +402,6 @@ pub enum Subcommand {
/// extra arguments to be passed for the test tool being used
/// (e.g. libtest, compiletest or rustdoc)
test_args: Vec<String>,
/// extra options to pass the compiler when running tests
#[arg(long, value_name = "ARGS", allow_hyphen_values(true))]
rustc_args: Vec<String>,
#[arg(long)]
/// do not run doc tests
no_doc: bool,
@ -499,10 +506,10 @@ impl Subcommand {
}
}
pub fn rustc_args(&self) -> Vec<&str> {
pub fn compiletest_rustc_args(&self) -> Vec<&str> {
match *self {
Subcommand::Test { ref rustc_args, .. } | Subcommand::Miri { ref rustc_args, .. } => {
rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
Subcommand::Test { ref compiletest_rustc_args, .. } => {
compiletest_rustc_args.iter().flat_map(|s| s.split_whitespace()).collect()
}
_ => vec![],
}

View file

@ -1,6 +1,6 @@
#[allow(clippy::module_inception)]
mod config;
pub(crate) mod flags;
pub mod flags;
#[cfg(test)]
mod tests;

View file

@ -12,9 +12,10 @@ use crate::core::build_steps::clippy::get_clippy_rules_in_order;
use crate::core::config::{LldMode, Target, TargetSelection, TomlConfig};
fn parse(config: &str) -> Config {
Config::parse_inner(&["check".to_string(), "--config=/does/not/exist".to_string()], |&_| {
toml::from_str(&config).unwrap()
})
Config::parse_inner(
Flags::parse(&["check".to_string(), "--config=/does/not/exist".to_string()]),
|&_| toml::from_str(&config).unwrap(),
)
}
#[test]
@ -108,7 +109,7 @@ fn clap_verify() {
#[test]
fn override_toml() {
let config = Config::parse_inner(
&[
Flags::parse(&[
"check".to_owned(),
"--config=/does/not/exist".to_owned(),
"--set=change-id=1".to_owned(),
@ -121,7 +122,7 @@ fn override_toml() {
"--set=target.x86_64-unknown-linux-gnu.rpath=false".to_owned(),
"--set=target.aarch64-unknown-linux-gnu.sanitizers=false".to_owned(),
"--set=target.aarch64-apple-darwin.runner=apple".to_owned(),
],
]),
|&_| {
toml::from_str(
r#"
@ -201,12 +202,12 @@ runner = "x86_64-runner"
#[should_panic]
fn override_toml_duplicate() {
Config::parse_inner(
&[
Flags::parse(&[
"check".to_owned(),
"--config=/does/not/exist".to_string(),
"--set=change-id=1".to_owned(),
"--set=change-id=2".to_owned(),
],
]),
|&_| toml::from_str("change-id = 0").unwrap(),
);
}
@ -226,7 +227,7 @@ fn profile_user_dist() {
.and_then(|table: toml::Value| TomlConfig::deserialize(table))
.unwrap()
}
Config::parse_inner(&["check".to_owned()], get_toml);
Config::parse_inner(Flags::parse(&["check".to_owned()]), get_toml);
}
#[test]
@ -301,7 +302,7 @@ fn order_of_clippy_rules() {
"-Aclippy::foo1".to_string(),
"-Aclippy::foo2".to_string(),
];
let config = Config::parse(&args);
let config = Config::parse(Flags::parse(&args));
let actual = match &config.cmd {
crate::Subcommand::Clippy { allow, deny, warn, forbid, .. } => {

View file

@ -379,7 +379,7 @@ impl Config {
let version = &self.stage0_metadata.compiler.version;
let host = self.build;
let bin_root = self.out.join(host.triple).join("stage0");
let bin_root = self.out.join(host).join("stage0");
let clippy_stamp = bin_root.join(".clippy-stamp");
let cargo_clippy = bin_root.join("bin").join(exe("cargo-clippy", host));
if cargo_clippy.exists() && !program_out_of_date(&clippy_stamp, date) {
@ -412,7 +412,7 @@ impl Config {
let channel = format!("{version}-{date}");
let host = self.build;
let bin_root = self.out.join(host.triple).join("rustfmt");
let bin_root = self.out.join(host).join("rustfmt");
let rustfmt_path = bin_root.join("bin").join(exe("rustfmt", host));
let rustfmt_stamp = bin_root.join(".rustfmt-stamp");
if rustfmt_path.exists() && !program_out_of_date(&rustfmt_stamp, &channel) {
@ -592,7 +592,7 @@ impl Config {
t!(fs::create_dir_all(&cache_dir));
}
let bin_root = self.out.join(self.build.triple).join(destination);
let bin_root = self.out.join(self.build).join(destination);
let tarball = cache_dir.join(&filename);
let (base_url, url, should_verify) = match mode {
DownloadSource::CI => {

View file

@ -43,7 +43,7 @@ mod core;
mod utils;
pub use core::builder::PathSet;
pub use core::config::flags::Subcommand;
pub use core::config::flags::{Flags, Subcommand};
pub use core::config::Config;
pub use utils::change_tracker::{
@ -452,7 +452,7 @@ impl Build {
}
// Make a symbolic link so we can use a consistent directory in the documentation.
let build_triple = build.out.join(build.build.triple);
let build_triple = build.out.join(build.build);
t!(fs::create_dir_all(&build_triple));
let host = build.out.join("host");
if host.is_symlink() {
@ -807,10 +807,7 @@ impl Build {
}
fn tools_dir(&self, compiler: Compiler) -> PathBuf {
let out = self
.out
.join(&*compiler.host.triple)
.join(format!("stage{}-tools-bin", compiler.stage));
let out = self.out.join(compiler.host).join(format!("stage{}-tools-bin", compiler.stage));
t!(fs::create_dir_all(&out));
out
}
@ -827,14 +824,14 @@ impl Build {
Mode::ToolBootstrap => "-bootstrap-tools",
Mode::ToolStd | Mode::ToolRustc => "-tools",
};
self.out.join(&*compiler.host.triple).join(format!("stage{}{}", compiler.stage, suffix))
self.out.join(compiler.host).join(format!("stage{}{}", compiler.stage, suffix))
}
/// Returns the root output directory for all Cargo output in a given stage,
/// running a particular compiler, whether or not we're building the
/// standard library, and targeting the specified architecture.
fn cargo_out(&self, compiler: Compiler, mode: Mode, target: TargetSelection) -> PathBuf {
self.stage_out(compiler, mode).join(&*target.triple).join(self.cargo_dir())
self.stage_out(compiler, mode).join(target).join(self.cargo_dir())
}
/// Root output directory of LLVM for `target`
@ -845,36 +842,36 @@ impl Build {
if self.config.llvm_from_ci && self.config.build == target {
self.config.ci_llvm_root()
} else {
self.out.join(&*target.triple).join("llvm")
self.out.join(target).join("llvm")
}
}
fn lld_out(&self, target: TargetSelection) -> PathBuf {
self.out.join(&*target.triple).join("lld")
self.out.join(target).join("lld")
}
/// Output directory for all documentation for a target
fn doc_out(&self, target: TargetSelection) -> PathBuf {
self.out.join(&*target.triple).join("doc")
self.out.join(target).join("doc")
}
/// Output directory for all JSON-formatted documentation for a target
fn json_doc_out(&self, target: TargetSelection) -> PathBuf {
self.out.join(&*target.triple).join("json-doc")
self.out.join(target).join("json-doc")
}
fn test_out(&self, target: TargetSelection) -> PathBuf {
self.out.join(&*target.triple).join("test")
self.out.join(target).join("test")
}
/// Output directory for all documentation for a target
fn compiler_doc_out(&self, target: TargetSelection) -> PathBuf {
self.out.join(&*target.triple).join("compiler-doc")
self.out.join(target).join("compiler-doc")
}
/// Output directory for some generated md crate documentation for a target (temporary)
fn md_doc_out(&self, target: TargetSelection) -> PathBuf {
self.out.join(&*target.triple).join("md-doc")
self.out.join(target).join("md-doc")
}
/// Returns `true` if this is an external version of LLVM not managed by bootstrap.
@ -954,7 +951,7 @@ impl Build {
/// Directory for libraries built from C/C++ code and shared between stages.
fn native_dir(&self, target: TargetSelection) -> PathBuf {
self.out.join(&*target.triple).join("native")
self.out.join(target).join("native")
}
/// Root output directory for rust_test_helpers library compiled for

View file

@ -225,4 +225,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
severity: ChangeSeverity::Info,
summary: "New option `llvm.libzstd` to control whether llvm is built with zstd support.",
},
ChangeInfo {
change_id: 128841,
severity: ChangeSeverity::Warning,
summary: "./x test --rustc-args was renamed to --compiletest-rustc-args as it only applies there. ./x miri --rustc-args was also removed.",
},
];

View file

@ -5,7 +5,7 @@ use std::path::PathBuf;
use crate::utils::helpers::{
check_cfg_arg, extract_beta_rev, hex_encode, make, program_out_of_date, symlink_dir,
};
use crate::Config;
use crate::{Config, Flags};
#[test]
fn test_make() {
@ -58,7 +58,8 @@ fn test_check_cfg_arg() {
#[test]
fn test_program_out_of_date() {
let config = Config::parse(&["check".to_owned(), "--config=/does/not/exist".to_owned()]);
let config =
Config::parse(Flags::parse(&["check".to_owned(), "--config=/does/not/exist".to_owned()]));
let tempfile = config.tempdir().join(".tmp-stamp-file");
File::create(&tempfile).unwrap().write_all(b"dummy value").unwrap();
assert!(tempfile.exists());
@ -73,7 +74,8 @@ fn test_program_out_of_date() {
#[test]
fn test_symlink_dir() {
let config = Config::parse(&["check".to_owned(), "--config=/does/not/exist".to_owned()]);
let config =
Config::parse(Flags::parse(&["check".to_owned(), "--config=/does/not/exist".to_owned()]));
let tempdir = config.tempdir().join(".tmp-dir");
let link_path = config.tempdir().join(".tmp-link");

View file

@ -18,9 +18,9 @@ if [[ -z "${PR_CI_JOB}" ]]; then
# compiler, and is sensitive to the addition of new flags.
../x.py --stage 1 test tests/ui-fulldeps
# The tests are run a second time with the size optimizations enabled.
../x.py --stage 1 test library/std library/alloc library/core \
--rustc-args "--cfg feature=\"optimize_for_size\""
# Rebuild the stdlib with the size optimizations enabled and run tests again.
RUSTFLAGS_NOT_BOOTSTRAP="--cfg feature=\"optimize_for_size\"" ../x.py --stage 1 test \
library/std library/alloc library/core
fi
# NOTE: intentionally uses all of `x.py`, `x`, and `x.ps1` to make sure they all work on Linux.

View file

@ -376,6 +376,57 @@ that the code sample should be compiled using the respective edition of Rust.
# fn foo() {}
```
Starting in the 2024 edition[^edition-note], compatible doctests are merged as one before being
run. We combine doctests for performance reasons: the slowest part of doctests is to compile them.
Merging all of them into one file and compiling this new file, then running the doctests is much
faster. Whether doctests are merged or not, they are run in their own process.
An example of time spent when running doctests:
[sysinfo crate](https://crates.io/crates/sysinfo):
```text
wall-time duration: 4.59s
total compile time: 27.067s
total runtime: 3.969s
```
Rust core library:
```text
wall-time duration: 102s
total compile time: 775.204s
total runtime: 15.487s
```
[^edition-note]: This is based on the edition of the whole crate, not the edition of the individual
test case that may be specified in its code attribute.
In some cases, doctests cannot be merged. For example, if you have:
```rust
//! ```
//! let location = std::panic::Location::caller();
//! assert_eq!(location.line(), 4);
//! ```
```
The problem with this code is that, if you change any other doctests, it'll likely break when
runing `rustdoc --test`, making it tricky to maintain.
This is where the `standalone` attribute comes in: it tells `rustdoc` that a doctest
should not be merged with the others. So the previous code should use it:
```rust
//! ```standalone
//! let location = std::panic::Location::caller();
//! assert_eq!(location.line(), 4);
//! ```
```
In this case, it means that the line information will not change if you add/remove other
doctests.
### Custom CSS classes for code blocks
```rust

View file

@ -1,11 +0,0 @@
# `asm_const`
The tracking issue for this feature is: [#93332]
[#93332]: https://github.com/rust-lang/rust/issues/93332
------------------------
This feature adds a `const <expr>` operand type to `asm!` and `global_asm!`.
- `<expr>` must be an integer constant expression.
- The value of the expression is formatted as a string and substituted directly into the asm template string.

View file

@ -266,7 +266,7 @@ complete -c x.py -n "__fish_seen_subcommand_from doc" -l enable-bolt-settings -d
complete -c x.py -n "__fish_seen_subcommand_from doc" -l skip-stage0-validation -d 'Skip stage0 compiler validation'
complete -c x.py -n "__fish_seen_subcommand_from doc" -s h -l help -d 'Print help (see more with \'--help\')'
complete -c x.py -n "__fish_seen_subcommand_from test" -l test-args -d 'extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)' -r
complete -c x.py -n "__fish_seen_subcommand_from test" -l rustc-args -d 'extra options to pass the compiler when running tests' -r
complete -c x.py -n "__fish_seen_subcommand_from test" -l compiletest-rustc-args -d 'extra options to pass the compiler when running compiletest tests' -r
complete -c x.py -n "__fish_seen_subcommand_from test" -l extra-checks -d 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell)' -r
complete -c x.py -n "__fish_seen_subcommand_from test" -l compare-mode -d 'mode describing what file the actual ui output will be compared to' -r
complete -c x.py -n "__fish_seen_subcommand_from test" -l pass -d 'force {check,build,run}-pass tests to this mode' -r
@ -313,7 +313,6 @@ complete -c x.py -n "__fish_seen_subcommand_from test" -l enable-bolt-settings -
complete -c x.py -n "__fish_seen_subcommand_from test" -l skip-stage0-validation -d 'Skip stage0 compiler validation'
complete -c x.py -n "__fish_seen_subcommand_from test" -s h -l help -d 'Print help (see more with \'--help\')'
complete -c x.py -n "__fish_seen_subcommand_from miri" -l test-args -d 'extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)' -r
complete -c x.py -n "__fish_seen_subcommand_from miri" -l rustc-args -d 'extra options to pass the compiler when running tests' -r
complete -c x.py -n "__fish_seen_subcommand_from miri" -l config -d 'TOML configuration file for build' -r -F
complete -c x.py -n "__fish_seen_subcommand_from miri" -l build-dir -d 'Build directory, overrides `build.build-dir` in `config.toml`' -r -f -a "(__fish_complete_directories)"
complete -c x.py -n "__fish_seen_subcommand_from miri" -l build -d 'build target of the stage0 compiler' -r -f

View file

@ -338,7 +338,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock {
}
'x.py;test' {
[CompletionResult]::new('--test-args', 'test-args', [CompletionResultType]::ParameterName, 'extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)')
[CompletionResult]::new('--rustc-args', 'rustc-args', [CompletionResultType]::ParameterName, 'extra options to pass the compiler when running tests')
[CompletionResult]::new('--compiletest-rustc-args', 'compiletest-rustc-args', [CompletionResultType]::ParameterName, 'extra options to pass the compiler when running compiletest tests')
[CompletionResult]::new('--extra-checks', 'extra-checks', [CompletionResultType]::ParameterName, 'comma-separated list of other files types to check (accepts py, py:lint, py:fmt, shell)')
[CompletionResult]::new('--compare-mode', 'compare-mode', [CompletionResultType]::ParameterName, 'mode describing what file the actual ui output will be compared to')
[CompletionResult]::new('--pass', 'pass', [CompletionResultType]::ParameterName, 'force {check,build,run}-pass tests to this mode')
@ -392,7 +392,6 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock {
}
'x.py;miri' {
[CompletionResult]::new('--test-args', 'test-args', [CompletionResultType]::ParameterName, 'extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)')
[CompletionResult]::new('--rustc-args', 'rustc-args', [CompletionResultType]::ParameterName, 'extra options to pass the compiler when running tests')
[CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'TOML configuration file for build')
[CompletionResult]::new('--build-dir', 'build-dir', [CompletionResultType]::ParameterName, 'Build directory, overrides `build.build-dir` in `config.toml`')
[CompletionResult]::new('--build', 'build', [CompletionResultType]::ParameterName, 'build target of the stage0 compiler')

View file

@ -1300,7 +1300,7 @@ _x.py() {
return 0
;;
x.py__miri)
opts="-v -i -j -h --no-fail-fast --test-args --rustc-args --no-doc --doc --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --bypass-bootstrap-lock --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --help [PATHS]... [ARGS]..."
opts="-v -i -j -h --no-fail-fast --test-args --no-doc --doc --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --bypass-bootstrap-lock --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --help [PATHS]... [ARGS]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
@ -1310,10 +1310,6 @@ _x.py() {
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--rustc-args)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--config)
COMPREPLY=($(compgen -f "${cur}"))
return 0
@ -1862,7 +1858,7 @@ _x.py() {
return 0
;;
x.py__test)
opts="-v -i -j -h --no-fail-fast --test-args --rustc-args --no-doc --doc --bless --extra-checks --force-rerun --only-modified --compare-mode --pass --run --rustfix-coverage --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --bypass-bootstrap-lock --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --help [PATHS]... [ARGS]..."
opts="-v -i -j -h --no-fail-fast --test-args --compiletest-rustc-args --no-doc --doc --bless --extra-checks --force-rerun --only-modified --compare-mode --pass --run --rustfix-coverage --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --bypass-bootstrap-lock --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --help [PATHS]... [ARGS]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
@ -1872,7 +1868,7 @@ _x.py() {
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--rustc-args)
--compiletest-rustc-args)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;

View file

@ -337,7 +337,7 @@ _arguments "${_arguments_options[@]}" \
(test)
_arguments "${_arguments_options[@]}" \
'*--test-args=[extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)]:ARGS: ' \
'*--rustc-args=[extra options to pass the compiler when running tests]:ARGS: ' \
'*--compiletest-rustc-args=[extra options to pass the compiler when running compiletest tests]:ARGS: ' \
'--extra-checks=[comma-separated list of other files types to check (accepts py, py\:lint, py\:fmt, shell)]:EXTRA_CHECKS: ' \
'--compare-mode=[mode describing what file the actual ui output will be compared to]:COMPARE MODE: ' \
'--pass=[force {check,build,run}-pass tests to this mode]:check | build | run: ' \
@ -393,7 +393,6 @@ _arguments "${_arguments_options[@]}" \
(miri)
_arguments "${_arguments_options[@]}" \
'*--test-args=[extra arguments to be passed for the test tool being used (e.g. libtest, compiletest or rustdoc)]:ARGS: ' \
'*--rustc-args=[extra options to pass the compiler when running tests]:ARGS: ' \
'--config=[TOML configuration file for build]:FILE:_files' \
'--build-dir=[Build directory, overrides \`build.build-dir\` in \`config.toml\`]:DIR:_files -/' \
'--build=[build target of the stage0 compiler]:BUILD:( )' \

View file

@ -2069,7 +2069,7 @@ pub(crate) fn clean_middle_ty<'tcx>(
Some(ContainerTy::Ref(r)),
)),
},
ty::FnDef(..) | ty::FnPtr(_) => {
ty::FnDef(..) | ty::FnPtr(..) => {
// FIXME: should we merge the outer and inner binders somehow?
let sig = bound_ty.skip_binder().fn_sig(cx.tcx);
let decl = clean_poly_fn_sig(cx, None, sig);

View file

@ -733,9 +733,11 @@ impl Options {
let html_no_source = matches.opt_present("html-no-source");
if generate_link_to_definition && (show_coverage || output_format != OutputFormat::Html) {
dcx.fatal(
"--generate-link-to-definition option can only be used with HTML output format",
);
dcx.struct_warn(
"`--generate-link-to-definition` option can only be used with HTML output format",
)
.with_note("`--generate-link-to-definition` option will be ignored")
.emit();
}
let scrape_examples_options = ScrapeExamplesOptions::new(matches, dcx);

View file

@ -1,5 +1,6 @@
mod make;
mod markdown;
mod runner;
mod rust;
use std::fs::File;
@ -10,7 +11,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::{panic, str};
pub(crate) use make::make_test;
pub(crate) use make::DocTestBuilder;
pub(crate) use markdown::test as test_markdown;
use rustc_ast as ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@ -150,8 +151,6 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, options: RustdocOptions) -> Result<()
expanded_args: options.expanded_args.clone(),
};
let test_args = options.test_args.clone();
let nocapture = options.nocapture;
let externs = options.externs.clone();
let json_unused_externs = options.json_unused_externs;
@ -164,39 +163,46 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, options: RustdocOptions) -> Result<()
let args_path = temp_dir.path().join("rustdoc-cfgs");
crate::wrap_return(dcx, generate_args_file(&args_path, &options))?;
let (tests, unused_extern_reports, compiling_test_count) =
interface::run_compiler(config, |compiler| {
compiler.enter(|queries| {
let collector = queries.global_ctxt()?.enter(|tcx| {
let crate_name = tcx.crate_name(LOCAL_CRATE).to_string();
let crate_attrs = tcx.hir().attrs(CRATE_HIR_ID);
let opts = scrape_test_config(crate_name, crate_attrs, args_path);
let enable_per_target_ignores = options.enable_per_target_ignores;
let CreateRunnableDocTests {
standalone_tests,
mergeable_tests,
rustdoc_options,
opts,
unused_extern_reports,
compiling_test_count,
..
} = interface::run_compiler(config, |compiler| {
compiler.enter(|queries| {
let collector = queries.global_ctxt()?.enter(|tcx| {
let crate_name = tcx.crate_name(LOCAL_CRATE).to_string();
let crate_attrs = tcx.hir().attrs(CRATE_HIR_ID);
let opts = scrape_test_config(crate_name, crate_attrs, args_path);
let enable_per_target_ignores = options.enable_per_target_ignores;
let mut collector = CreateRunnableDoctests::new(options, opts);
let hir_collector = HirCollector::new(
&compiler.sess,
tcx.hir(),
ErrorCodes::from(compiler.sess.opts.unstable_features.is_nightly_build()),
enable_per_target_ignores,
tcx,
);
let tests = hir_collector.collect_crate();
tests.into_iter().for_each(|t| collector.add_test(t));
let mut collector = CreateRunnableDocTests::new(options, opts);
let hir_collector = HirCollector::new(
&compiler.sess,
tcx.hir(),
ErrorCodes::from(compiler.sess.opts.unstable_features.is_nightly_build()),
enable_per_target_ignores,
tcx,
);
let tests = hir_collector.collect_crate();
tests.into_iter().for_each(|t| collector.add_test(t));
collector
});
if compiler.sess.dcx().has_errors().is_some() {
FatalError.raise();
}
collector
});
if compiler.sess.dcx().has_errors().is_some() {
FatalError.raise();
}
let unused_extern_reports = collector.unused_extern_reports.clone();
let compiling_test_count = collector.compiling_test_count.load(Ordering::SeqCst);
Ok((collector.tests, unused_extern_reports, compiling_test_count))
})
})?;
Ok(collector)
})
})?;
run_tests(test_args, nocapture, tests);
run_tests(opts, &rustdoc_options, &unused_extern_reports, standalone_tests, mergeable_tests);
let compiling_test_count = compiling_test_count.load(Ordering::SeqCst);
// Collect and warn about unused externs, but only if we've gotten
// reports for each doctest
@ -240,16 +246,83 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, options: RustdocOptions) -> Result<()
}
pub(crate) fn run_tests(
mut test_args: Vec<String>,
nocapture: bool,
mut tests: Vec<test::TestDescAndFn>,
opts: GlobalTestOptions,
rustdoc_options: &Arc<RustdocOptions>,
unused_extern_reports: &Arc<Mutex<Vec<UnusedExterns>>>,
mut standalone_tests: Vec<test::TestDescAndFn>,
mergeable_tests: FxHashMap<Edition, Vec<(DocTestBuilder, ScrapedDocTest)>>,
) {
let mut test_args = Vec::with_capacity(rustdoc_options.test_args.len() + 1);
test_args.insert(0, "rustdoctest".to_string());
if nocapture {
test_args.extend_from_slice(&rustdoc_options.test_args);
if rustdoc_options.nocapture {
test_args.push("--nocapture".to_string());
}
tests.sort_by(|a, b| a.desc.name.as_slice().cmp(&b.desc.name.as_slice()));
test::test_main(&test_args, tests, None);
let mut nb_errors = 0;
let mut ran_edition_tests = 0;
let target_str = rustdoc_options.target.to_string();
for (edition, mut doctests) in mergeable_tests {
if doctests.is_empty() {
continue;
}
doctests.sort_by(|(_, a), (_, b)| a.name.cmp(&b.name));
let mut tests_runner = runner::DocTestRunner::new();
let rustdoc_test_options = IndividualTestOptions::new(
&rustdoc_options,
&Some(format!("merged_doctest_{edition}")),
PathBuf::from(format!("doctest_{edition}.rs")),
);
for (doctest, scraped_test) in &doctests {
tests_runner.add_test(doctest, scraped_test, &target_str);
}
if let Ok(success) = tests_runner.run_merged_tests(
rustdoc_test_options,
edition,
&opts,
&test_args,
rustdoc_options,
) {
ran_edition_tests += 1;
if !success {
nb_errors += 1;
}
continue;
}
// We failed to compile all compatible tests as one so we push them into the
// `standalone_tests` doctests.
debug!("Failed to compile compatible doctests for edition {} all at once", edition);
for (doctest, scraped_test) in doctests {
doctest.generate_unique_doctest(
&scraped_test.text,
scraped_test.langstr.test_harness,
&opts,
Some(&opts.crate_name),
);
standalone_tests.push(generate_test_desc_and_fn(
doctest,
scraped_test,
opts.clone(),
Arc::clone(&rustdoc_options),
unused_extern_reports.clone(),
));
}
}
// We need to call `test_main` even if there is no doctest to run to get the output
// `running 0 tests...`.
if ran_edition_tests == 0 || !standalone_tests.is_empty() {
standalone_tests.sort_by(|a, b| a.desc.name.as_slice().cmp(&b.desc.name.as_slice()));
test::test_main(&test_args, standalone_tests, None);
}
if nb_errors != 0 {
// libtest::ERROR_EXIT_CODE is not public but it's the same value.
std::process::exit(101);
}
}
// Look for `#![doc(test(no_crate_inject))]`, used by crates in the std facade.
@ -330,7 +403,7 @@ impl DirState {
// We could unify this struct the one in rustc but they have different
// ownership semantics, so doing so would create wasteful allocations.
#[derive(serde::Serialize, serde::Deserialize)]
struct UnusedExterns {
pub(crate) struct UnusedExterns {
/// Lint level of the unused_crate_dependencies lint
lint_level: String,
/// List of unused externs by their names.
@ -359,22 +432,41 @@ fn wrapped_rustc_command(rustc_wrappers: &[PathBuf], rustc_binary: &Path) -> Com
command
}
struct RunnableDoctest {
/// Information needed for running a bundle of doctests.
///
/// This data structure contains the "full" test code, including the wrappers
/// (if multiple doctests are merged), `main` function,
/// and everything needed to calculate the compiler's command-line arguments.
/// The `# ` prefix on boring lines has also been stripped.
pub(crate) struct RunnableDocTest {
full_test_code: String,
full_test_line_offset: usize,
test_opts: IndividualTestOptions,
global_opts: GlobalTestOptions,
scraped_test: ScrapedDoctest,
langstr: LangString,
line: usize,
edition: Edition,
no_run: bool,
is_multiple_tests: bool,
}
impl RunnableDocTest {
fn path_for_merged_doctest(&self) -> PathBuf {
self.test_opts.outdir.path().join(&format!("doctest_{}.rs", self.edition))
}
}
/// Execute a `RunnableDoctest`.
///
/// This is the function that calculates the compiler command line, invokes the compiler, then
/// invokes the test or tests in a separate executable (if applicable).
fn run_test(
doctest: RunnableDoctest,
doctest: RunnableDocTest,
rustdoc_options: &RustdocOptions,
supports_color: bool,
report_unused_externs: impl Fn(UnusedExterns),
) -> Result<(), TestFailure> {
let scraped_test = &doctest.scraped_test;
let langstr = &scraped_test.langstr;
let langstr = &doctest.langstr;
// Make sure we emit well-formed executable names for our target.
let rust_out = add_exe_suffix("rust_out".to_owned(), &rustdoc_options.target);
let output_file = doctest.test_opts.outdir.path().join(rust_out);
@ -391,12 +483,15 @@ fn run_test(
compiler.arg(format!("--sysroot={}", sysroot.display()));
}
compiler.arg("--edition").arg(&scraped_test.edition(rustdoc_options).to_string());
compiler.env("UNSTABLE_RUSTDOC_TEST_PATH", &doctest.test_opts.path);
compiler.env(
"UNSTABLE_RUSTDOC_TEST_LINE",
format!("{}", scraped_test.line as isize - doctest.full_test_line_offset as isize),
);
compiler.arg("--edition").arg(&doctest.edition.to_string());
if !doctest.is_multiple_tests {
// Setting these environment variables is unneeded if this is a merged doctest.
compiler.env("UNSTABLE_RUSTDOC_TEST_PATH", &doctest.test_opts.path);
compiler.env(
"UNSTABLE_RUSTDOC_TEST_LINE",
format!("{}", doctest.line as isize - doctest.full_test_line_offset as isize),
);
}
compiler.arg("-o").arg(&output_file);
if langstr.test_harness {
compiler.arg("--test");
@ -408,10 +503,7 @@ fn run_test(
compiler.arg("-Z").arg("unstable-options");
}
if scraped_test.no_run(rustdoc_options)
&& !langstr.compile_fail
&& rustdoc_options.persist_doctests.is_none()
{
if doctest.no_run && !langstr.compile_fail && rustdoc_options.persist_doctests.is_none() {
// FIXME: why does this code check if it *shouldn't* persist doctests
// -- shouldn't it be the negation?
compiler.arg("--emit=metadata");
@ -442,18 +534,40 @@ fn run_test(
}
}
compiler.arg("-");
compiler.stdin(Stdio::piped());
compiler.stderr(Stdio::piped());
// If this is a merged doctest, we need to write it into a file instead of using stdin
// because if the size of the merged doctests is too big, it'll simply break stdin.
if doctest.is_multiple_tests {
// It makes the compilation failure much faster if it is for a combined doctest.
compiler.arg("--error-format=short");
let input_file = doctest.path_for_merged_doctest();
if std::fs::write(&input_file, &doctest.full_test_code).is_err() {
// If we cannot write this file for any reason, we leave. All combined tests will be
// tested as standalone tests.
return Err(TestFailure::CompileError);
}
compiler.arg(input_file);
if !rustdoc_options.nocapture {
// If `nocapture` is disabled, then we don't display rustc's output when compiling
// the merged doctests.
compiler.stderr(Stdio::null());
}
} else {
compiler.arg("-");
compiler.stdin(Stdio::piped());
compiler.stderr(Stdio::piped());
}
debug!("compiler invocation for doctest: {compiler:?}");
let mut child = compiler.spawn().expect("Failed to spawn rustc process");
{
let output = if doctest.is_multiple_tests {
let status = child.wait().expect("Failed to wait");
process::Output { status, stdout: Vec::new(), stderr: Vec::new() }
} else {
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
stdin.write_all(doctest.full_test_code.as_bytes()).expect("could write out test sources");
}
let output = child.wait_with_output().expect("Failed to read stdout");
child.wait_with_output().expect("Failed to read stdout")
};
struct Bomb<'a>(&'a str);
impl Drop for Bomb<'_> {
@ -492,8 +606,7 @@ fn run_test(
// We used to check if the output contained "error[{}]: " but since we added the
// colored output, we can't anymore because of the color escape characters before
// the ":".
let missing_codes: Vec<String> = scraped_test
.langstr
let missing_codes: Vec<String> = langstr
.error_codes
.iter()
.filter(|err| !out.contains(&format!("error[{err}]")))
@ -510,7 +623,7 @@ fn run_test(
}
}
if scraped_test.no_run(rustdoc_options) {
if doctest.no_run {
return Ok(());
}
@ -522,15 +635,19 @@ fn run_test(
let tool = make_maybe_absolute_path(tool.into());
cmd = Command::new(tool);
cmd.args(&rustdoc_options.runtool_args);
cmd.arg(output_file);
cmd.arg(&output_file);
} else {
cmd = Command::new(output_file);
cmd = Command::new(&output_file);
if doctest.is_multiple_tests {
cmd.arg("*doctest-bin-path");
cmd.arg(&output_file);
}
}
if let Some(run_directory) = &rustdoc_options.test_run_directory {
cmd.current_dir(run_directory);
}
let result = if rustdoc_options.nocapture {
let result = if doctest.is_multiple_tests || rustdoc_options.nocapture {
cmd.status().map(|status| process::Output {
status,
stdout: Vec::new(),
@ -568,15 +685,14 @@ fn make_maybe_absolute_path(path: PathBuf) -> PathBuf {
}
struct IndividualTestOptions {
outdir: DirState,
test_id: String,
path: PathBuf,
}
impl IndividualTestOptions {
fn new(options: &RustdocOptions, test_id: String, test_path: PathBuf) -> Self {
fn new(options: &RustdocOptions, test_id: &Option<String>, test_path: PathBuf) -> Self {
let outdir = if let Some(ref path) = options.persist_doctests {
let mut path = path.clone();
path.push(&test_id);
path.push(&test_id.as_deref().unwrap_or_else(|| "<doctest>"));
if let Err(err) = std::fs::create_dir_all(&path) {
eprintln!("Couldn't create directory for doctest executables: {err}");
@ -588,20 +704,45 @@ impl IndividualTestOptions {
DirState::Temp(get_doctest_dir().expect("rustdoc needs a tempdir"))
};
Self { outdir, test_id, path: test_path }
Self { outdir, path: test_path }
}
}
/// A doctest scraped from the code, ready to be turned into a runnable test.
struct ScrapedDoctest {
///
/// The pipeline goes: [`clean`] AST -> `ScrapedDoctest` -> `RunnableDoctest`.
/// [`run_merged_tests`] converts a bunch of scraped doctests to a single runnable doctest,
/// while [`generate_unique_doctest`] does the standalones.
///
/// [`clean`]: crate::clean
/// [`run_merged_tests`]: crate::doctest::runner::DocTestRunner::run_merged_tests
/// [`generate_unique_doctest`]: crate::doctest::make::DocTestBuilder::generate_unique_doctest
pub(crate) struct ScrapedDocTest {
filename: FileName,
line: usize,
logical_path: Vec<String>,
langstr: LangString,
text: String,
name: String,
}
impl ScrapedDoctest {
impl ScrapedDocTest {
fn new(
filename: FileName,
line: usize,
logical_path: Vec<String>,
langstr: LangString,
text: String,
) -> Self {
let mut item_path = logical_path.join("::");
item_path.retain(|c| c != ' ');
if !item_path.is_empty() {
item_path.push(' ');
}
let name =
format!("{} - {item_path}(line {line})", filename.prefer_remapped_unconditionaly());
Self { filename, line, langstr, text, name }
}
fn edition(&self, opts: &RustdocOptions) -> Edition {
self.langstr.edition.unwrap_or(opts.edition)
}
@ -609,54 +750,8 @@ impl ScrapedDoctest {
fn no_run(&self, opts: &RustdocOptions) -> bool {
self.langstr.no_run || opts.no_run
}
}
pub(crate) trait DoctestVisitor {
fn visit_test(&mut self, test: String, config: LangString, rel_line: MdRelLine);
fn visit_header(&mut self, _name: &str, _level: u32) {}
}
struct CreateRunnableDoctests {
tests: Vec<test::TestDescAndFn>,
rustdoc_options: Arc<RustdocOptions>,
opts: GlobalTestOptions,
visited_tests: FxHashMap<(String, usize), usize>,
unused_extern_reports: Arc<Mutex<Vec<UnusedExterns>>>,
compiling_test_count: AtomicUsize,
}
impl CreateRunnableDoctests {
fn new(rustdoc_options: RustdocOptions, opts: GlobalTestOptions) -> CreateRunnableDoctests {
CreateRunnableDoctests {
tests: Vec::new(),
rustdoc_options: Arc::new(rustdoc_options),
opts,
visited_tests: FxHashMap::default(),
unused_extern_reports: Default::default(),
compiling_test_count: AtomicUsize::new(0),
}
}
fn generate_name(&self, filename: &FileName, line: usize, logical_path: &[String]) -> String {
let mut item_path = logical_path.join("::");
item_path.retain(|c| c != ' ');
if !item_path.is_empty() {
item_path.push(' ');
}
format!("{} - {item_path}(line {line})", filename.prefer_remapped_unconditionaly())
}
fn add_test(&mut self, test: ScrapedDoctest) {
let name = self.generate_name(&test.filename, test.line, &test.logical_path);
let opts = self.opts.clone();
let target_str = self.rustdoc_options.target.to_string();
let unused_externs = self.unused_extern_reports.clone();
if !test.langstr.compile_fail {
self.compiling_test_count.fetch_add(1, Ordering::SeqCst);
}
let path = match &test.filename {
fn path(&self) -> PathBuf {
match &self.filename {
FileName::Real(path) => {
if let Some(local_path) = path.local_path() {
local_path.to_path_buf()
@ -666,10 +761,45 @@ impl CreateRunnableDoctests {
}
}
_ => PathBuf::from(r"doctest.rs"),
};
}
}
}
pub(crate) trait DocTestVisitor {
fn visit_test(&mut self, test: String, config: LangString, rel_line: MdRelLine);
fn visit_header(&mut self, _name: &str, _level: u32) {}
}
struct CreateRunnableDocTests {
standalone_tests: Vec<test::TestDescAndFn>,
mergeable_tests: FxHashMap<Edition, Vec<(DocTestBuilder, ScrapedDocTest)>>,
rustdoc_options: Arc<RustdocOptions>,
opts: GlobalTestOptions,
visited_tests: FxHashMap<(String, usize), usize>,
unused_extern_reports: Arc<Mutex<Vec<UnusedExterns>>>,
compiling_test_count: AtomicUsize,
can_merge_doctests: bool,
}
impl CreateRunnableDocTests {
fn new(rustdoc_options: RustdocOptions, opts: GlobalTestOptions) -> CreateRunnableDocTests {
let can_merge_doctests = rustdoc_options.edition >= Edition::Edition2024;
CreateRunnableDocTests {
standalone_tests: Vec::new(),
mergeable_tests: FxHashMap::default(),
rustdoc_options: Arc::new(rustdoc_options),
opts,
visited_tests: FxHashMap::default(),
unused_extern_reports: Default::default(),
compiling_test_count: AtomicUsize::new(0),
can_merge_doctests,
}
}
fn add_test(&mut self, scraped_test: ScrapedDocTest) {
// For example `module/file.rs` would become `module_file_rs`
let file = test
let file = scraped_test
.filename
.prefer_local()
.to_string_lossy()
@ -679,75 +809,134 @@ impl CreateRunnableDoctests {
let test_id = format!(
"{file}_{line}_{number}",
file = file,
line = test.line,
line = scraped_test.line,
number = {
// Increases the current test number, if this file already
// exists or it creates a new entry with a test number of 0.
self.visited_tests
.entry((file.clone(), test.line))
.entry((file.clone(), scraped_test.line))
.and_modify(|v| *v += 1)
.or_insert(0)
},
);
let rustdoc_options = self.rustdoc_options.clone();
let rustdoc_test_options = IndividualTestOptions::new(&self.rustdoc_options, test_id, path);
let edition = scraped_test.edition(&self.rustdoc_options);
let doctest = DocTestBuilder::new(
&scraped_test.text,
Some(&self.opts.crate_name),
edition,
self.can_merge_doctests,
Some(test_id),
Some(&scraped_test.langstr),
);
let is_standalone = !doctest.can_be_merged
|| scraped_test.langstr.compile_fail
|| scraped_test.langstr.test_harness
|| scraped_test.langstr.standalone
|| self.rustdoc_options.nocapture
|| self.rustdoc_options.test_args.iter().any(|arg| arg == "--show-output");
if is_standalone {
let test_desc = self.generate_test_desc_and_fn(doctest, scraped_test);
self.standalone_tests.push(test_desc);
} else {
self.mergeable_tests.entry(edition).or_default().push((doctest, scraped_test));
}
}
debug!("creating test {name}: {}", test.text);
self.tests.push(test::TestDescAndFn {
desc: test::TestDesc {
name: test::DynTestName(name),
ignore: match test.langstr.ignore {
Ignore::All => true,
Ignore::None => false,
Ignore::Some(ref ignores) => ignores.iter().any(|s| target_str.contains(s)),
},
ignore_message: None,
source_file: "",
start_line: 0,
start_col: 0,
end_line: 0,
end_col: 0,
// compiler failures are test failures
should_panic: test::ShouldPanic::No,
compile_fail: test.langstr.compile_fail,
no_run: test.no_run(&rustdoc_options),
test_type: test::TestType::DocTest,
fn generate_test_desc_and_fn(
&mut self,
test: DocTestBuilder,
scraped_test: ScrapedDocTest,
) -> test::TestDescAndFn {
if !scraped_test.langstr.compile_fail {
self.compiling_test_count.fetch_add(1, Ordering::SeqCst);
}
generate_test_desc_and_fn(
test,
scraped_test,
self.opts.clone(),
Arc::clone(&self.rustdoc_options),
self.unused_extern_reports.clone(),
)
}
}
fn generate_test_desc_and_fn(
test: DocTestBuilder,
scraped_test: ScrapedDocTest,
opts: GlobalTestOptions,
rustdoc_options: Arc<RustdocOptions>,
unused_externs: Arc<Mutex<Vec<UnusedExterns>>>,
) -> test::TestDescAndFn {
let target_str = rustdoc_options.target.to_string();
let rustdoc_test_options =
IndividualTestOptions::new(&rustdoc_options, &test.test_id, scraped_test.path());
debug!("creating test {}: {}", scraped_test.name, scraped_test.text);
test::TestDescAndFn {
desc: test::TestDesc {
name: test::DynTestName(scraped_test.name.clone()),
ignore: match scraped_test.langstr.ignore {
Ignore::All => true,
Ignore::None => false,
Ignore::Some(ref ignores) => ignores.iter().any(|s| target_str.contains(s)),
},
testfn: test::DynTestFn(Box::new(move || {
doctest_run_fn(rustdoc_test_options, opts, test, rustdoc_options, unused_externs)
})),
});
ignore_message: None,
source_file: "",
start_line: 0,
start_col: 0,
end_line: 0,
end_col: 0,
// compiler failures are test failures
should_panic: test::ShouldPanic::No,
compile_fail: scraped_test.langstr.compile_fail,
no_run: scraped_test.no_run(&rustdoc_options),
test_type: test::TestType::DocTest,
},
testfn: test::DynTestFn(Box::new(move || {
doctest_run_fn(
rustdoc_test_options,
opts,
test,
scraped_test,
rustdoc_options,
unused_externs,
)
})),
}
}
fn doctest_run_fn(
test_opts: IndividualTestOptions,
global_opts: GlobalTestOptions,
scraped_test: ScrapedDoctest,
doctest: DocTestBuilder,
scraped_test: ScrapedDocTest,
rustdoc_options: Arc<RustdocOptions>,
unused_externs: Arc<Mutex<Vec<UnusedExterns>>>,
) -> Result<(), String> {
let report_unused_externs = |uext| {
unused_externs.lock().unwrap().push(uext);
};
let edition = scraped_test.edition(&rustdoc_options);
let (full_test_code, full_test_line_offset, supports_color) = make_test(
let (full_test_code, full_test_line_offset) = doctest.generate_unique_doctest(
&scraped_test.text,
Some(&global_opts.crate_name),
scraped_test.langstr.test_harness,
&global_opts,
edition,
Some(&test_opts.test_id),
Some(&global_opts.crate_name),
);
let runnable_test = RunnableDoctest {
let runnable_test = RunnableDocTest {
full_test_code,
full_test_line_offset,
test_opts,
global_opts,
scraped_test,
langstr: scraped_test.langstr.clone(),
line: scraped_test.line,
edition: scraped_test.edition(&rustdoc_options),
no_run: scraped_test.no_run(&rustdoc_options),
is_multiple_tests: false,
};
let res = run_test(runnable_test, &rustdoc_options, supports_color, report_unused_externs);
let res =
run_test(runnable_test, &rustdoc_options, doctest.supports_color, report_unused_externs);
if let Err(err) = res {
match err {
@ -804,7 +993,7 @@ fn doctest_run_fn(
}
#[cfg(test)] // used in tests
impl DoctestVisitor for Vec<usize> {
impl DocTestVisitor for Vec<usize> {
fn visit_test(&mut self, _test: String, _config: LangString, rel_line: MdRelLine) {
self.push(1 + rel_line.offset());
}

View file

@ -16,250 +16,428 @@ use rustc_span::symbol::sym;
use rustc_span::FileName;
use super::GlobalTestOptions;
use crate::html::markdown::LangString;
/// Transforms a test into code that can be compiled into a Rust binary, and returns the number of
/// lines before the test code begins as well as if the output stream supports colors or not.
pub(crate) fn make_test(
s: &str,
crate_name: Option<&str>,
dont_insert_main: bool,
opts: &GlobalTestOptions,
edition: Edition,
test_id: Option<&str>,
) -> (String, usize, bool) {
let (crate_attrs, everything_else, crates) = partition_source(s, edition);
let everything_else = everything_else.trim();
let mut line_offset = 0;
let mut prog = String::new();
let mut supports_color = false;
/// This struct contains information about the doctest itself which is then used to generate
/// doctest source code appropriately.
pub(crate) struct DocTestBuilder {
pub(crate) supports_color: bool,
pub(crate) already_has_extern_crate: bool,
pub(crate) has_main_fn: bool,
pub(crate) crate_attrs: String,
/// If this is a merged doctest, it will be put into `everything_else`, otherwise it will
/// put into `crate_attrs`.
pub(crate) maybe_crate_attrs: String,
pub(crate) crates: String,
pub(crate) everything_else: String,
pub(crate) test_id: Option<String>,
pub(crate) failed_ast: bool,
pub(crate) can_be_merged: bool,
}
if opts.attrs.is_empty() {
// If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
// lints that are commonly triggered in doctests. The crate-level test attributes are
// commonly used to make tests fail in case they trigger warnings, so having this there in
// that case may cause some tests to pass when they shouldn't have.
prog.push_str("#![allow(unused)]\n");
line_offset += 1;
impl DocTestBuilder {
pub(crate) fn new(
source: &str,
crate_name: Option<&str>,
edition: Edition,
can_merge_doctests: bool,
// If `test_id` is `None`, it means we're generating code for a code example "run" link.
test_id: Option<String>,
lang_str: Option<&LangString>,
) -> Self {
let can_merge_doctests = can_merge_doctests
&& lang_str.is_some_and(|lang_str| {
!lang_str.compile_fail && !lang_str.test_harness && !lang_str.standalone
});
let SourceInfo { crate_attrs, maybe_crate_attrs, crates, everything_else } =
partition_source(source, edition);
// Uses librustc_ast to parse the doctest and find if there's a main fn and the extern
// crate already is included.
let Ok((
ParseSourceInfo {
has_main_fn,
found_extern_crate,
supports_color,
has_global_allocator,
has_macro_def,
..
},
failed_ast,
)) = check_for_main_and_extern_crate(
crate_name,
source,
&everything_else,
&crates,
edition,
can_merge_doctests,
)
else {
// If the parser panicked due to a fatal error, pass the test code through unchanged.
// The error will be reported during compilation.
return Self {
supports_color: false,
has_main_fn: false,
crate_attrs,
maybe_crate_attrs,
crates,
everything_else,
already_has_extern_crate: false,
test_id,
failed_ast: true,
can_be_merged: false,
};
};
// If the AST returned an error, we don't want this doctest to be merged with the
// others. Same if it contains `#[feature]` or `#[no_std]`.
let can_be_merged = can_merge_doctests
&& !failed_ast
&& !has_global_allocator
&& crate_attrs.is_empty()
// If this is a merged doctest and a defined macro uses `$crate`, then the path will
// not work, so better not put it into merged doctests.
&& !(has_macro_def && everything_else.contains("$crate"));
Self {
supports_color,
has_main_fn,
crate_attrs,
maybe_crate_attrs,
crates,
everything_else,
already_has_extern_crate: found_extern_crate,
test_id,
failed_ast: false,
can_be_merged,
}
}
// Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
for attr in &opts.attrs {
prog.push_str(&format!("#![{attr}]\n"));
line_offset += 1;
}
/// Transforms a test into code that can be compiled into a Rust binary, and returns the number of
/// lines before the test code begins.
pub(crate) fn generate_unique_doctest(
&self,
test_code: &str,
dont_insert_main: bool,
opts: &GlobalTestOptions,
crate_name: Option<&str>,
) -> (String, usize) {
if self.failed_ast {
// If the AST failed to compile, no need to go generate a complete doctest, the error
// will be better this way.
return (test_code.to_string(), 0);
}
let mut line_offset = 0;
let mut prog = String::new();
let everything_else = self.everything_else.trim();
if opts.attrs.is_empty() {
// If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
// lints that are commonly triggered in doctests. The crate-level test attributes are
// commonly used to make tests fail in case they trigger warnings, so having this there in
// that case may cause some tests to pass when they shouldn't have.
prog.push_str("#![allow(unused)]\n");
line_offset += 1;
}
// Now push any outer attributes from the example, assuming they
// are intended to be crate attributes.
prog.push_str(&crate_attrs);
prog.push_str(&crates);
// Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
for attr in &opts.attrs {
prog.push_str(&format!("#![{attr}]\n"));
line_offset += 1;
}
// Uses librustc_ast to parse the doctest and find if there's a main fn and the extern
// crate already is included.
let Ok((already_has_main, already_has_extern_crate)) =
check_for_main_and_extern_crate(crate_name, s.to_owned(), edition, &mut supports_color)
else {
// If the parser panicked due to a fatal error, pass the test code through unchanged.
// The error will be reported during compilation.
return (s.to_owned(), 0, false);
};
// Now push any outer attributes from the example, assuming they
// are intended to be crate attributes.
prog.push_str(&self.crate_attrs);
prog.push_str(&self.maybe_crate_attrs);
prog.push_str(&self.crates);
// Don't inject `extern crate std` because it's already injected by the
// compiler.
if !already_has_extern_crate && !opts.no_crate_inject && crate_name != Some("std") {
if let Some(crate_name) = crate_name {
// Don't inject `extern crate std` because it's already injected by the
// compiler.
if !self.already_has_extern_crate &&
!opts.no_crate_inject &&
let Some(crate_name) = crate_name &&
crate_name != "std" &&
// Don't inject `extern crate` if the crate is never used.
// NOTE: this is terribly inaccurate because it doesn't actually
// parse the source, but only has false positives, not false
// negatives.
if s.contains(crate_name) {
// rustdoc implicitly inserts an `extern crate` item for the own crate
// which may be unused, so we need to allow the lint.
prog.push_str("#[allow(unused_extern_crates)]\n");
test_code.contains(crate_name)
{
// rustdoc implicitly inserts an `extern crate` item for the own crate
// which may be unused, so we need to allow the lint.
prog.push_str("#[allow(unused_extern_crates)]\n");
prog.push_str(&format!("extern crate r#{crate_name};\n"));
line_offset += 1;
prog.push_str(&format!("extern crate r#{crate_name};\n"));
line_offset += 1;
}
// FIXME: This code cannot yet handle no_std test cases yet
if dont_insert_main || self.has_main_fn || prog.contains("![no_std]") {
prog.push_str(everything_else);
} else {
let returns_result = everything_else.ends_with("(())");
// Give each doctest main function a unique name.
// This is for example needed for the tooling around `-C instrument-coverage`.
let inner_fn_name = if let Some(ref test_id) = self.test_id {
format!("_doctest_main_{test_id}")
} else {
"_inner".into()
};
let inner_attr = if self.test_id.is_some() { "#[allow(non_snake_case)] " } else { "" };
let (main_pre, main_post) = if returns_result {
(
format!(
"fn main() {{ {inner_attr}fn {inner_fn_name}() -> Result<(), impl core::fmt::Debug> {{\n",
),
format!("\n}} {inner_fn_name}().unwrap() }}"),
)
} else if self.test_id.is_some() {
(
format!("fn main() {{ {inner_attr}fn {inner_fn_name}() {{\n",),
format!("\n}} {inner_fn_name}() }}"),
)
} else {
("fn main() {\n".into(), "\n}".into())
};
// Note on newlines: We insert a line/newline *before*, and *after*
// the doctest and adjust the `line_offset` accordingly.
// In the case of `-C instrument-coverage`, this means that the generated
// inner `main` function spans from the doctest opening codeblock to the
// closing one. For example
// /// ``` <- start of the inner main
// /// <- code under doctest
// /// ``` <- end of the inner main
line_offset += 1;
prog.push_str(&main_pre);
// add extra 4 spaces for each line to offset the code block
if opts.insert_indent_space {
prog.push_str(
&everything_else
.lines()
.map(|line| format!(" {}", line))
.collect::<Vec<String>>()
.join("\n"),
);
} else {
prog.push_str(everything_else);
};
prog.push_str(&main_post);
}
debug!("final doctest:\n{prog}");
(prog, line_offset)
}
}
#[derive(PartialEq, Eq, Debug)]
enum ParsingResult {
Failed,
AstError,
Ok,
}
fn cancel_error_count(psess: &ParseSess) {
// Reset errors so that they won't be reported as compiler bugs when dropping the
// dcx. Any errors in the tests will be reported when the test file is compiled,
// Note that we still need to cancel the errors above otherwise `Diag` will panic on
// drop.
psess.dcx().reset_err_count();
}
fn parse_source(
source: String,
info: &mut ParseSourceInfo,
crate_name: &Option<&str>,
) -> ParsingResult {
use rustc_errors::emitter::{Emitter, HumanEmitter};
use rustc_errors::DiagCtxt;
use rustc_parse::parser::ForceCollect;
use rustc_span::source_map::FilePathMapping;
let filename = FileName::anon_source_code(&source);
// Any errors in parsing should also appear when the doctest is compiled for real, so just
// send all the errors that librustc_ast emits directly into a `Sink` instead of stderr.
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
let fallback_bundle = rustc_errors::fallback_fluent_bundle(
rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(),
false,
);
info.supports_color =
HumanEmitter::new(stderr_destination(ColorConfig::Auto), fallback_bundle.clone())
.supports_color();
let emitter = HumanEmitter::new(Box::new(io::sink()), fallback_bundle);
// FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
let dcx = DiagCtxt::new(Box::new(emitter)).disable_warnings();
let psess = ParseSess::with_dcx(dcx, sm);
let mut parser = match new_parser_from_source_str(&psess, filename, source) {
Ok(p) => p,
Err(errs) => {
errs.into_iter().for_each(|err| err.cancel());
cancel_error_count(&psess);
return ParsingResult::Failed;
}
};
let mut parsing_result = ParsingResult::Ok;
// Recurse through functions body. It is necessary because the doctest source code is
// wrapped in a function to limit the number of AST errors. If we don't recurse into
// functions, we would thing all top-level items (so basically nothing).
fn check_item(item: &ast::Item, info: &mut ParseSourceInfo, crate_name: &Option<&str>) {
if !info.has_global_allocator
&& item.attrs.iter().any(|attr| attr.name_or_empty() == sym::global_allocator)
{
info.has_global_allocator = true;
}
match item.kind {
ast::ItemKind::Fn(ref fn_item) if !info.has_main_fn => {
if item.ident.name == sym::main {
info.has_main_fn = true;
}
if let Some(ref body) = fn_item.body {
for stmt in &body.stmts {
match stmt.kind {
ast::StmtKind::Item(ref item) => check_item(item, info, crate_name),
ast::StmtKind::MacCall(..) => info.found_macro = true,
_ => {}
}
}
}
}
ast::ItemKind::ExternCrate(original) => {
if !info.found_extern_crate
&& let Some(ref crate_name) = crate_name
{
info.found_extern_crate = match original {
Some(name) => name.as_str() == *crate_name,
None => item.ident.as_str() == *crate_name,
};
}
}
ast::ItemKind::MacCall(..) => info.found_macro = true,
ast::ItemKind::MacroDef(..) => info.has_macro_def = true,
_ => {}
}
}
// FIXME: This code cannot yet handle no_std test cases yet
if dont_insert_main || already_has_main || prog.contains("![no_std]") {
prog.push_str(everything_else);
} else {
let returns_result = everything_else.trim_end().ends_with("(())");
// Give each doctest main function a unique name.
// This is for example needed for the tooling around `-C instrument-coverage`.
let inner_fn_name = if let Some(test_id) = test_id {
format!("_doctest_main_{test_id}")
} else {
"_inner".into()
};
let inner_attr = if test_id.is_some() { "#[allow(non_snake_case)] " } else { "" };
let (main_pre, main_post) = if returns_result {
(
format!(
"fn main() {{ {inner_attr}fn {inner_fn_name}() -> Result<(), impl core::fmt::Debug> {{\n",
),
format!("\n}} {inner_fn_name}().unwrap() }}"),
)
} else if test_id.is_some() {
(
format!("fn main() {{ {inner_attr}fn {inner_fn_name}() {{\n",),
format!("\n}} {inner_fn_name}() }}"),
)
} else {
("fn main() {\n".into(), "\n}".into())
};
// Note on newlines: We insert a line/newline *before*, and *after*
// the doctest and adjust the `line_offset` accordingly.
// In the case of `-C instrument-coverage`, this means that the generated
// inner `main` function spans from the doctest opening codeblock to the
// closing one. For example
// /// ``` <- start of the inner main
// /// <- code under doctest
// /// ``` <- end of the inner main
line_offset += 1;
loop {
match parser.parse_item(ForceCollect::No) {
Ok(Some(item)) => {
check_item(&item, info, crate_name);
// add extra 4 spaces for each line to offset the code block
let content = if opts.insert_indent_space {
everything_else
.lines()
.map(|line| format!(" {}", line))
.collect::<Vec<String>>()
.join("\n")
} else {
everything_else.to_string()
};
prog.extend([&main_pre, content.as_str(), &main_post].iter().cloned());
if info.has_main_fn && info.found_extern_crate {
break;
}
}
Ok(None) => break,
Err(e) => {
parsing_result = ParsingResult::AstError;
e.cancel();
break;
}
}
// The supplied item is only used for diagnostics,
// which are swallowed here anyway.
parser.maybe_consume_incorrect_semicolon(None);
}
debug!("final doctest:\n{prog}");
cancel_error_count(&psess);
parsing_result
}
(prog, line_offset, supports_color)
#[derive(Default)]
struct ParseSourceInfo {
has_main_fn: bool,
found_extern_crate: bool,
found_macro: bool,
supports_color: bool,
has_global_allocator: bool,
has_macro_def: bool,
}
fn check_for_main_and_extern_crate(
crate_name: Option<&str>,
source: String,
original_source_code: &str,
everything_else: &str,
crates: &str,
edition: Edition,
supports_color: &mut bool,
) -> Result<(bool, bool), FatalError> {
can_merge_doctests: bool,
) -> Result<(ParseSourceInfo, bool), FatalError> {
let result = rustc_driver::catch_fatal_errors(|| {
rustc_span::create_session_if_not_set_then(edition, |_| {
use rustc_errors::emitter::{Emitter, HumanEmitter};
use rustc_errors::DiagCtxt;
use rustc_parse::parser::ForceCollect;
use rustc_span::source_map::FilePathMapping;
let mut info =
ParseSourceInfo { found_extern_crate: crate_name.is_none(), ..Default::default() };
let filename = FileName::anon_source_code(&source);
// Any errors in parsing should also appear when the doctest is compiled for real, so just
// send all the errors that librustc_ast emits directly into a `Sink` instead of stderr.
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
let fallback_bundle = rustc_errors::fallback_fluent_bundle(
rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(),
false,
);
*supports_color =
HumanEmitter::new(stderr_destination(ColorConfig::Auto), fallback_bundle.clone())
.supports_color();
let emitter = HumanEmitter::new(Box::new(io::sink()), fallback_bundle);
// FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
let dcx = DiagCtxt::new(Box::new(emitter)).disable_warnings();
let psess = ParseSess::with_dcx(dcx, sm);
let mut found_main = false;
let mut found_extern_crate = crate_name.is_none();
let mut found_macro = false;
let mut parser = match new_parser_from_source_str(&psess, filename, source.clone()) {
Ok(p) => p,
Err(errs) => {
errs.into_iter().for_each(|err| err.cancel());
return (found_main, found_extern_crate, found_macro);
}
};
loop {
match parser.parse_item(ForceCollect::No) {
Ok(Some(item)) => {
if !found_main
&& let ast::ItemKind::Fn(..) = item.kind
&& item.ident.name == sym::main
{
found_main = true;
}
if !found_extern_crate
&& let ast::ItemKind::ExternCrate(original) = item.kind
{
// This code will never be reached if `crate_name` is none because
// `found_extern_crate` is initialized to `true` if it is none.
let crate_name = crate_name.unwrap();
match original {
Some(name) => found_extern_crate = name.as_str() == crate_name,
None => found_extern_crate = item.ident.as_str() == crate_name,
}
}
if !found_macro && let ast::ItemKind::MacCall(..) = item.kind {
found_macro = true;
}
if found_main && found_extern_crate {
break;
}
}
Ok(None) => break,
Err(e) => {
e.cancel();
break;
}
}
// The supplied item is only used for diagnostics,
// which are swallowed here anyway.
parser.maybe_consume_incorrect_semicolon(None);
let mut parsing_result =
parse_source(format!("{crates}{everything_else}"), &mut info, &crate_name);
// No need to double-check this if the "merged doctests" feature isn't enabled (so
// before the 2024 edition).
if can_merge_doctests && parsing_result != ParsingResult::Ok {
// If we found an AST error, we want to ensure it's because of an expression being
// used outside of a function.
//
// To do so, we wrap in a function in order to make sure that the doctest AST is
// correct. For example, if your doctest is `foo::bar()`, if we don't wrap it in a
// block, it would emit an AST error, which would be problematic for us since we
// want to filter out such errors which aren't "real" errors.
//
// The end goal is to be able to merge as many doctests as possible as one for much
// faster doctests run time.
parsing_result = parse_source(
format!("{crates}\nfn __doctest_wrap(){{{everything_else}\n}}"),
&mut info,
&crate_name,
);
}
// Reset errors so that they won't be reported as compiler bugs when dropping the
// dcx. Any errors in the tests will be reported when the test file is compiled,
// Note that we still need to cancel the errors above otherwise `Diag` will panic on
// drop.
psess.dcx().reset_err_count();
(found_main, found_extern_crate, found_macro)
(info, parsing_result)
})
});
let (already_has_main, already_has_extern_crate, found_macro) = result?;
let (mut info, parsing_result) = match result {
Err(..) | Ok((_, ParsingResult::Failed)) => return Err(FatalError),
Ok((info, parsing_result)) => (info, parsing_result),
};
// If a doctest's `fn main` is being masked by a wrapper macro, the parsing loop above won't
// see it. In that case, run the old text-based scan to see if they at least have a main
// function written inside a macro invocation. See
// https://github.com/rust-lang/rust/issues/56898
let already_has_main = if found_macro && !already_has_main {
source
if info.found_macro
&& !info.has_main_fn
&& original_source_code
.lines()
.map(|line| {
let comment = line.find("//");
if let Some(comment_begins) = comment { &line[0..comment_begins] } else { line }
})
.any(|code| code.contains("fn main"))
} else {
already_has_main
};
{
info.has_main_fn = true;
}
Ok((already_has_main, already_has_extern_crate))
Ok((info, parsing_result != ParsingResult::Ok))
}
fn check_if_attr_is_complete(source: &str, edition: Edition) -> bool {
enum AttrKind {
CrateAttr,
Attr,
}
/// Returns `Some` if the attribute is complete and `Some(true)` if it is an attribute that can be
/// placed at the crate root.
fn check_if_attr_is_complete(source: &str, edition: Edition) -> Option<AttrKind> {
if source.is_empty() {
// Empty content so nothing to check in here...
return true;
return None;
}
let not_crate_attrs = [sym::forbid, sym::allow, sym::warn, sym::deny];
rustc_driver::catch_fatal_errors(|| {
rustc_span::create_session_if_not_set_then(edition, |_| {
use rustc_errors::emitter::HumanEmitter;
@ -285,32 +463,75 @@ fn check_if_attr_is_complete(source: &str, edition: Edition) -> bool {
errs.into_iter().for_each(|err| err.cancel());
// If there is an unclosed delimiter, an error will be returned by the
// tokentrees.
return false;
return None;
}
};
// If a parsing error happened, it's very likely that the attribute is incomplete.
if let Err(e) = parser.parse_attribute(InnerAttrPolicy::Permitted) {
e.cancel();
return false;
}
true
let ret = match parser.parse_attribute(InnerAttrPolicy::Permitted) {
Ok(attr) => {
let attr_name = attr.name_or_empty();
if not_crate_attrs.contains(&attr_name) {
// There is one exception to these attributes:
// `#![allow(internal_features)]`. If this attribute is used, we need to
// consider it only as a crate-level attribute.
if attr_name == sym::allow
&& let Some(list) = attr.meta_item_list()
&& list.iter().any(|sub_attr| {
sub_attr.name_or_empty().as_str() == "internal_features"
})
{
Some(AttrKind::CrateAttr)
} else {
Some(AttrKind::Attr)
}
} else {
Some(AttrKind::CrateAttr)
}
}
Err(e) => {
e.cancel();
None
}
};
ret
})
})
.unwrap_or(false)
.unwrap_or(None)
}
fn partition_source(s: &str, edition: Edition) -> (String, String, String) {
fn handle_attr(mod_attr_pending: &mut String, source_info: &mut SourceInfo, edition: Edition) {
if let Some(attr_kind) = check_if_attr_is_complete(mod_attr_pending, edition) {
let push_to = match attr_kind {
AttrKind::CrateAttr => &mut source_info.crate_attrs,
AttrKind::Attr => &mut source_info.maybe_crate_attrs,
};
push_to.push_str(mod_attr_pending);
push_to.push('\n');
// If it's complete, then we can clear the pending content.
mod_attr_pending.clear();
} else if mod_attr_pending.ends_with('\\') {
mod_attr_pending.push('n');
}
}
#[derive(Default)]
struct SourceInfo {
crate_attrs: String,
maybe_crate_attrs: String,
crates: String,
everything_else: String,
}
fn partition_source(s: &str, edition: Edition) -> SourceInfo {
#[derive(Copy, Clone, PartialEq)]
enum PartitionState {
Attrs,
Crates,
Other,
}
let mut source_info = SourceInfo::default();
let mut state = PartitionState::Attrs;
let mut before = String::new();
let mut crates = String::new();
let mut after = String::new();
let mut mod_attr_pending = String::new();
for line in s.lines() {
@ -321,12 +542,9 @@ fn partition_source(s: &str, edition: Edition) -> (String, String, String) {
match state {
PartitionState::Attrs => {
state = if trimline.starts_with("#![") {
if !check_if_attr_is_complete(line, edition) {
mod_attr_pending = line.to_owned();
} else {
mod_attr_pending.clear();
}
PartitionState::Attrs
mod_attr_pending = line.to_owned();
handle_attr(&mut mod_attr_pending, &mut source_info, edition);
continue;
} else if trimline.chars().all(|c| c.is_whitespace())
|| (trimline.starts_with("//") && !trimline.starts_with("///"))
{
@ -341,15 +559,10 @@ fn partition_source(s: &str, edition: Edition) -> (String, String, String) {
// If not, then we append the new line into the pending attribute to check
// if this time it's complete...
mod_attr_pending.push_str(line);
if !trimline.is_empty()
&& check_if_attr_is_complete(&mod_attr_pending, edition)
{
// If it's complete, then we can clear the pending content.
mod_attr_pending.clear();
if !trimline.is_empty() {
handle_attr(&mut mod_attr_pending, &mut source_info, edition);
}
// In any case, this is considered as `PartitionState::Attrs` so it's
// prepended before rustdoc's inserts.
PartitionState::Attrs
continue;
} else {
PartitionState::Other
}
@ -371,23 +584,25 @@ fn partition_source(s: &str, edition: Edition) -> (String, String, String) {
match state {
PartitionState::Attrs => {
before.push_str(line);
before.push('\n');
source_info.crate_attrs.push_str(line);
source_info.crate_attrs.push('\n');
}
PartitionState::Crates => {
crates.push_str(line);
crates.push('\n');
source_info.crates.push_str(line);
source_info.crates.push('\n');
}
PartitionState::Other => {
after.push_str(line);
after.push('\n');
source_info.everything_else.push_str(line);
source_info.everything_else.push('\n');
}
}
}
debug!("before:\n{before}");
debug!("crates:\n{crates}");
debug!("after:\n{after}");
source_info.everything_else = source_info.everything_else.trim().to_string();
(before, after, crates)
debug!("crate_attrs:\n{}{}", source_info.crate_attrs, source_info.maybe_crate_attrs);
debug!("crates:\n{}", source_info.crates);
debug!("after:\n{}", source_info.everything_else);
source_info
}

View file

@ -1,34 +1,29 @@
//! Doctest functionality used only for doctests in `.md` Markdown files.
use std::fs::read_to_string;
use std::sync::{Arc, Mutex};
use rustc_span::FileName;
use tempfile::tempdir;
use super::{
generate_args_file, CreateRunnableDoctests, DoctestVisitor, GlobalTestOptions, ScrapedDoctest,
generate_args_file, CreateRunnableDocTests, DocTestVisitor, GlobalTestOptions, ScrapedDocTest,
};
use crate::config::Options;
use crate::html::markdown::{find_testable_code, ErrorCodes, LangString, MdRelLine};
struct MdCollector {
tests: Vec<ScrapedDoctest>,
tests: Vec<ScrapedDocTest>,
cur_path: Vec<String>,
filename: FileName,
}
impl DoctestVisitor for MdCollector {
impl DocTestVisitor for MdCollector {
fn visit_test(&mut self, test: String, config: LangString, rel_line: MdRelLine) {
let filename = self.filename.clone();
// First line of Markdown is line 1.
let line = 1 + rel_line.offset();
self.tests.push(ScrapedDoctest {
filename,
line,
logical_path: self.cur_path.clone(),
langstr: config,
text: test,
});
self.tests.push(ScrapedDocTest::new(filename, line, self.cur_path.clone(), config, test));
}
fn visit_header(&mut self, name: &str, level: u32) {
@ -118,8 +113,16 @@ pub(crate) fn test(options: Options) -> Result<(), String> {
None,
);
let mut collector = CreateRunnableDoctests::new(options.clone(), opts);
let mut collector = CreateRunnableDocTests::new(options.clone(), opts);
md_collector.tests.into_iter().for_each(|t| collector.add_test(t));
crate::doctest::run_tests(options.test_args, options.nocapture, collector.tests);
let CreateRunnableDocTests { opts, rustdoc_options, standalone_tests, mergeable_tests, .. } =
collector;
crate::doctest::run_tests(
opts,
&rustdoc_options,
&Arc::new(Mutex::new(Vec::new())),
standalone_tests,
mergeable_tests,
);
Ok(())
}

View file

@ -0,0 +1,269 @@
use std::fmt::Write;
use rustc_data_structures::fx::FxHashSet;
use rustc_span::edition::Edition;
use crate::doctest::{
run_test, DocTestBuilder, GlobalTestOptions, IndividualTestOptions, RunnableDocTest,
RustdocOptions, ScrapedDocTest, TestFailure, UnusedExterns,
};
use crate::html::markdown::{Ignore, LangString};
/// Convenient type to merge compatible doctests into one.
pub(crate) struct DocTestRunner {
crate_attrs: FxHashSet<String>,
ids: String,
output: String,
supports_color: bool,
nb_tests: usize,
}
impl DocTestRunner {
pub(crate) fn new() -> Self {
Self {
crate_attrs: FxHashSet::default(),
ids: String::new(),
output: String::new(),
supports_color: true,
nb_tests: 0,
}
}
pub(crate) fn add_test(
&mut self,
doctest: &DocTestBuilder,
scraped_test: &ScrapedDocTest,
target_str: &str,
) {
let ignore = match scraped_test.langstr.ignore {
Ignore::All => true,
Ignore::None => false,
Ignore::Some(ref ignores) => ignores.iter().any(|s| target_str.contains(s)),
};
if !ignore {
for line in doctest.crate_attrs.split('\n') {
self.crate_attrs.insert(line.to_string());
}
}
if !self.ids.is_empty() {
self.ids.push(',');
}
self.ids.push_str(&format!(
"{}::TEST",
generate_mergeable_doctest(
doctest,
scraped_test,
ignore,
self.nb_tests,
&mut self.output
),
));
self.supports_color &= doctest.supports_color;
self.nb_tests += 1;
}
pub(crate) fn run_merged_tests(
&mut self,
test_options: IndividualTestOptions,
edition: Edition,
opts: &GlobalTestOptions,
test_args: &[String],
rustdoc_options: &RustdocOptions,
) -> Result<bool, ()> {
let mut code = "\
#![allow(unused_extern_crates)]
#![allow(internal_features)]
#![feature(test)]
#![feature(rustc_attrs)]
#![feature(coverage_attribute)]
"
.to_string();
for crate_attr in &self.crate_attrs {
code.push_str(crate_attr);
code.push('\n');
}
if opts.attrs.is_empty() {
// If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
// lints that are commonly triggered in doctests. The crate-level test attributes are
// commonly used to make tests fail in case they trigger warnings, so having this there in
// that case may cause some tests to pass when they shouldn't have.
code.push_str("#![allow(unused)]\n");
}
// Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
for attr in &opts.attrs {
code.push_str(&format!("#![{attr}]\n"));
}
code.push_str("extern crate test;\n");
let test_args =
test_args.iter().map(|arg| format!("{arg:?}.to_string(),")).collect::<String>();
write!(
code,
"\
{output}
mod __doctest_mod {{
use std::sync::OnceLock;
use std::path::PathBuf;
pub static BINARY_PATH: OnceLock<PathBuf> = OnceLock::new();
pub const RUN_OPTION: &str = \"*doctest-inner-test\";
pub const BIN_OPTION: &str = \"*doctest-bin-path\";
#[allow(unused)]
pub fn doctest_path() -> Option<&'static PathBuf> {{
self::BINARY_PATH.get()
}}
#[allow(unused)]
pub fn doctest_runner(bin: &std::path::Path, test_nb: usize) -> Result<(), String> {{
let out = std::process::Command::new(bin)
.arg(self::RUN_OPTION)
.arg(test_nb.to_string())
.output()
.expect(\"failed to run command\");
if !out.status.success() {{
Err(String::from_utf8_lossy(&out.stderr).to_string())
}} else {{
Ok(())
}}
}}
}}
#[rustc_main]
#[coverage(off)]
fn main() -> std::process::ExitCode {{
const TESTS: [test::TestDescAndFn; {nb_tests}] = [{ids}];
let bin_marker = std::ffi::OsStr::new(__doctest_mod::BIN_OPTION);
let test_marker = std::ffi::OsStr::new(__doctest_mod::RUN_OPTION);
let test_args = &[{test_args}];
let mut args = std::env::args_os().skip(1);
while let Some(arg) = args.next() {{
if arg == bin_marker {{
let Some(binary) = args.next() else {{
panic!(\"missing argument after `{{}}`\", __doctest_mod::BIN_OPTION);
}};
if crate::__doctest_mod::BINARY_PATH.set(binary.into()).is_err() {{
panic!(\"`{{}}` option was used more than once\", bin_marker.to_string_lossy());
}}
return std::process::Termination::report(test::test_main(test_args, Vec::from(TESTS), None));
}} else if arg == test_marker {{
let Some(nb_test) = args.next() else {{
panic!(\"missing argument after `{{}}`\", __doctest_mod::RUN_OPTION);
}};
if let Some(nb_test) = nb_test.to_str().and_then(|nb| nb.parse::<usize>().ok()) {{
if let Some(test) = TESTS.get(nb_test) {{
if let test::StaticTestFn(f) = test.testfn {{
return std::process::Termination::report(f());
}}
}}
}}
panic!(\"Unexpected value after `{{}}`\", __doctest_mod::RUN_OPTION);
}}
}}
eprintln!(\"WARNING: No argument provided so doctests will be run in the same process\");
std::process::Termination::report(test::test_main(test_args, Vec::from(TESTS), None))
}}",
nb_tests = self.nb_tests,
output = self.output,
ids = self.ids,
)
.expect("failed to generate test code");
let runnable_test = RunnableDocTest {
full_test_code: code,
full_test_line_offset: 0,
test_opts: test_options,
global_opts: opts.clone(),
langstr: LangString::default(),
line: 0,
edition,
no_run: false,
is_multiple_tests: true,
};
let ret =
run_test(runnable_test, rustdoc_options, self.supports_color, |_: UnusedExterns| {});
if let Err(TestFailure::CompileError) = ret { Err(()) } else { Ok(ret.is_ok()) }
}
}
/// Push new doctest content into `output`. Returns the test ID for this doctest.
fn generate_mergeable_doctest(
doctest: &DocTestBuilder,
scraped_test: &ScrapedDocTest,
ignore: bool,
id: usize,
output: &mut String,
) -> String {
let test_id = format!("__doctest_{id}");
if ignore {
// We generate nothing else.
writeln!(output, "mod {test_id} {{\n").unwrap();
} else {
writeln!(output, "mod {test_id} {{\n{}{}", doctest.crates, doctest.maybe_crate_attrs)
.unwrap();
if scraped_test.langstr.no_run {
// To prevent having warnings about unused items since they're not called.
writeln!(output, "#![allow(unused)]").unwrap();
}
if doctest.has_main_fn {
output.push_str(&doctest.everything_else);
} else {
let returns_result = if doctest.everything_else.trim_end().ends_with("(())") {
"-> Result<(), impl core::fmt::Debug>"
} else {
""
};
write!(
output,
"\
fn main() {returns_result} {{
{}
}}",
doctest.everything_else
)
.unwrap();
}
}
let not_running = ignore || scraped_test.langstr.no_run;
writeln!(
output,
"
#[rustc_test_marker = {test_name:?}]
pub const TEST: test::TestDescAndFn = test::TestDescAndFn::new_doctest(
{test_name:?}, {ignore}, {file:?}, {line}, {no_run}, {should_panic},
test::StaticTestFn(
#[coverage(off)]
|| {{{runner}}},
));
}}",
test_name = scraped_test.name,
file = scraped_test.path(),
line = scraped_test.line,
no_run = scraped_test.langstr.no_run,
should_panic = !scraped_test.langstr.no_run && scraped_test.langstr.should_panic,
// Setting `no_run` to `true` in `TestDesc` still makes the test run, so we simply
// don't give it the function to run.
runner = if not_running {
"test::assert_test_result(Ok::<(), String>(()))".to_string()
} else {
format!(
"
if let Some(bin_path) = crate::__doctest_mod::doctest_path() {{
test::assert_test_result(crate::__doctest_mod::doctest_runner(bin_path, {id}))
}} else {{
test::assert_test_result(self::main())
}}
",
)
},
)
.unwrap();
test_id
}

View file

@ -14,14 +14,14 @@ use rustc_session::Session;
use rustc_span::source_map::SourceMap;
use rustc_span::{BytePos, FileName, Pos, Span, DUMMY_SP};
use super::{DoctestVisitor, ScrapedDoctest};
use super::{DocTestVisitor, ScrapedDocTest};
use crate::clean::types::AttributesExt;
use crate::clean::Attributes;
use crate::html::markdown::{self, ErrorCodes, LangString, MdRelLine};
struct RustCollector {
source_map: Lrc<SourceMap>,
tests: Vec<ScrapedDoctest>,
tests: Vec<ScrapedDocTest>,
cur_path: Vec<String>,
position: Span,
}
@ -48,16 +48,16 @@ impl RustCollector {
}
}
impl DoctestVisitor for RustCollector {
impl DocTestVisitor for RustCollector {
fn visit_test(&mut self, test: String, config: LangString, rel_line: MdRelLine) {
let line = self.get_base_line() + rel_line.offset();
self.tests.push(ScrapedDoctest {
filename: self.get_filename(),
self.tests.push(ScrapedDocTest::new(
self.get_filename(),
line,
logical_path: self.cur_path.clone(),
langstr: config,
text: test,
});
self.cur_path.clone(),
config,
test,
));
}
fn visit_header(&mut self, _name: &str, _level: u32) {}
@ -89,7 +89,7 @@ impl<'a, 'tcx> HirCollector<'a, 'tcx> {
Self { sess, map, codes, enable_per_target_ignores, tcx, collector }
}
pub fn collect_crate(mut self) -> Vec<ScrapedDoctest> {
pub fn collect_crate(mut self) -> Vec<ScrapedDocTest> {
let tcx = self.tcx;
self.visit_testable("".to_string(), CRATE_DEF_ID, tcx.hir().span(CRATE_HIR_ID), |this| {
tcx.hir().walk_toplevel_module(this)

View file

@ -2,7 +2,27 @@ use std::path::PathBuf;
use rustc_span::edition::DEFAULT_EDITION;
use super::{make_test, GlobalTestOptions};
use super::{DocTestBuilder, GlobalTestOptions};
fn make_test(
test_code: &str,
crate_name: Option<&str>,
dont_insert_main: bool,
opts: &GlobalTestOptions,
test_id: Option<&str>,
) -> (String, usize) {
let doctest = DocTestBuilder::new(
test_code,
crate_name,
DEFAULT_EDITION,
false,
test_id.map(|s| s.to_string()),
None,
);
let (code, line_offset) =
doctest.generate_unique_doctest(test_code, dont_insert_main, opts, crate_name);
(code, line_offset)
}
/// Default [`GlobalTestOptions`] for these unit tests.
fn default_global_opts(crate_name: impl Into<String>) -> GlobalTestOptions {
@ -25,7 +45,7 @@ fn main() {
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len, _) = make_test(input, None, false, &opts, DEFAULT_EDITION, None);
let (output, len) = make_test(input, None, false, &opts, None);
assert_eq!((output, len), (expected, 2));
}
@ -40,7 +60,7 @@ fn main() {
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len, _) = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION, None);
let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
assert_eq!((output, len), (expected, 2));
}
@ -59,7 +79,7 @@ use asdf::qwop;
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len, _) = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION, None);
let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
assert_eq!((output, len), (expected, 3));
}
@ -76,7 +96,7 @@ use asdf::qwop;
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len, _) = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION, None);
let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
assert_eq!((output, len), (expected, 2));
}
@ -94,7 +114,7 @@ use std::*;
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len, _) = make_test(input, Some("std"), false, &opts, DEFAULT_EDITION, None);
let (output, len) = make_test(input, Some("std"), false, &opts, None);
assert_eq!((output, len), (expected, 2));
}
@ -113,7 +133,7 @@ use asdf::qwop;
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len, _) = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION, None);
let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
assert_eq!((output, len), (expected, 2));
}
@ -130,7 +150,7 @@ use asdf::qwop;
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len, _) = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION, None);
let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
assert_eq!((output, len), (expected, 2));
}
@ -150,7 +170,7 @@ use asdf::qwop;
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len, _) = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION, None);
let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
assert_eq!((output, len), (expected, 3));
// Adding more will also bump the returned line offset.
@ -164,7 +184,7 @@ use asdf::qwop;
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len, _) = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION, None);
let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
assert_eq!((output, len), (expected, 4));
}
@ -181,7 +201,7 @@ fn main() {
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len, _) = make_test(input, None, false, &opts, DEFAULT_EDITION, None);
let (output, len) = make_test(input, None, false, &opts, None);
assert_eq!((output, len), (expected, 2));
}
@ -197,7 +217,7 @@ fn main() {
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len, _) = make_test(input, None, false, &opts, DEFAULT_EDITION, None);
let (output, len) = make_test(input, None, false, &opts, None);
assert_eq!((output, len), (expected, 1));
}
@ -213,7 +233,7 @@ fn main() {
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len, _) = make_test(input, None, false, &opts, DEFAULT_EDITION, None);
let (output, len) = make_test(input, None, false, &opts, None);
assert_eq!((output, len), (expected, 2));
}
@ -227,7 +247,7 @@ assert_eq!(2+2, 4);";
//Ceci n'est pas une `fn main`
assert_eq!(2+2, 4);"
.to_string();
let (output, len, _) = make_test(input, None, true, &opts, DEFAULT_EDITION, None);
let (output, len) = make_test(input, None, true, &opts, None);
assert_eq!((output, len), (expected, 1));
}
@ -245,7 +265,7 @@ assert_eq!(2+2, 4);
}"
.to_string();
let (output, len, _) = make_test(input, None, false, &opts, DEFAULT_EDITION, None);
let (output, len) = make_test(input, None, false, &opts, None);
assert_eq!((output, len), (expected, 2));
}
@ -265,7 +285,7 @@ assert_eq!(asdf::foo, 4);
}"
.to_string();
let (output, len, _) = make_test(input, Some("asdf"), false, &opts, DEFAULT_EDITION, None);
let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
assert_eq!((output, len), (expected, 3));
}
@ -283,7 +303,7 @@ test_wrapper! {
}"
.to_string();
let (output, len, _) = make_test(input, Some("my_crate"), false, &opts, DEFAULT_EDITION, None);
let (output, len) = make_test(input, Some("my_crate"), false, &opts, None);
assert_eq!((output, len), (expected, 1));
}
@ -303,7 +323,7 @@ io::stdin().read_line(&mut input)?;
Ok::<(), io:Error>(())
} _inner().unwrap() }"
.to_string();
let (output, len, _) = make_test(input, None, false, &opts, DEFAULT_EDITION, None);
let (output, len) = make_test(input, None, false, &opts, None);
assert_eq!((output, len), (expected, 2));
}
@ -317,8 +337,7 @@ fn main() { #[allow(non_snake_case)] fn _doctest_main__some_unique_name() {
assert_eq!(2+2, 4);
} _doctest_main__some_unique_name() }"
.to_string();
let (output, len, _) =
make_test(input, None, false, &opts, DEFAULT_EDITION, Some("_some_unique_name"));
let (output, len) = make_test(input, None, false, &opts, Some("_some_unique_name"));
assert_eq!((output, len), (expected, 2));
}
@ -337,7 +356,7 @@ fn main() {
eprintln!(\"hello anan\");
}"
.to_string();
let (output, len, _) = make_test(input, None, false, &opts, DEFAULT_EDITION, None);
let (output, len) = make_test(input, None, false, &opts, None);
assert_eq!((output, len), (expected, 2));
}
@ -357,6 +376,6 @@ fn main() {
eprintln!(\"hello anan\");
}"
.to_string();
let (output, len, _) = make_test(input, None, false, &opts, DEFAULT_EDITION, None);
let (output, len) = make_test(input, None, false, &opts, None);
assert_eq!((output, len), (expected, 1));
}

View file

@ -297,14 +297,16 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
attrs: vec![],
args_file: PathBuf::new(),
};
let (test, _, _) = doctest::make_test(&test, krate, false, &opts, edition, None);
let doctest = doctest::DocTestBuilder::new(&test, krate, edition, false, None, None);
let (test, _) = doctest.generate_unique_doctest(&test, false, &opts, krate);
let channel = if test.contains("#![feature(") { "&amp;version=nightly" } else { "" };
let test_escaped = small_url_encode(test);
Some(format!(
"<a class=\"test-arrow\" \
target=\"_blank\" \
href=\"{url}?code={test_escaped}{channel}&amp;edition={edition}\">Run</a>",
title=\"Run code\" \
href=\"{url}?code={test_escaped}{channel}&amp;edition={edition}\"></a>",
))
});
@ -736,7 +738,7 @@ impl MdRelLine {
}
}
pub(crate) fn find_testable_code<T: doctest::DoctestVisitor>(
pub(crate) fn find_testable_code<T: doctest::DocTestVisitor>(
doc: &str,
tests: &mut T,
error_codes: ErrorCodes,
@ -746,7 +748,7 @@ pub(crate) fn find_testable_code<T: doctest::DoctestVisitor>(
find_codes(doc, tests, error_codes, enable_per_target_ignores, extra_info, false)
}
pub(crate) fn find_codes<T: doctest::DoctestVisitor>(
pub(crate) fn find_codes<T: doctest::DocTestVisitor>(
doc: &str,
tests: &mut T,
error_codes: ErrorCodes,
@ -867,6 +869,7 @@ pub(crate) struct LangString {
pub(crate) rust: bool,
pub(crate) test_harness: bool,
pub(crate) compile_fail: bool,
pub(crate) standalone: bool,
pub(crate) error_codes: Vec<String>,
pub(crate) edition: Option<Edition>,
pub(crate) added_classes: Vec<String>,
@ -1189,6 +1192,7 @@ impl Default for LangString {
rust: true,
test_harness: false,
compile_fail: false,
standalone: false,
error_codes: Vec::new(),
edition: None,
added_classes: Vec::new(),
@ -1258,6 +1262,10 @@ impl LangString {
seen_rust_tags = !seen_other_tags || seen_rust_tags;
data.no_run = true;
}
LangStringToken::LangToken("standalone") => {
data.standalone = true;
seen_rust_tags = !seen_other_tags || seen_rust_tags;
}
LangStringToken::LangToken(x) if x.starts_with("edition") => {
data.edition = x[7..].parse::<Edition>().ok();
}

View file

@ -104,10 +104,6 @@ nav.sub {
--code-highlight-doc-comment-color: #4d4d4c;
--src-line-numbers-span-color: #c67e2d;
--src-line-number-highlighted-background-color: #fdffd3;
--test-arrow-color: #f5f5f5;
--test-arrow-background-color: rgba(78, 139, 202, 0.2);
--test-arrow-hover-color: #f5f5f5;
--test-arrow-hover-background-color: rgb(78, 139, 202);
--target-background-color: #fdffd3;
--target-border-color: #ad7c37;
--kbd-color: #000;
@ -210,10 +206,6 @@ nav.sub {
--code-highlight-doc-comment-color: #8ca375;
--src-line-numbers-span-color: #3b91e2;
--src-line-number-highlighted-background-color: #0a042f;
--test-arrow-color: #dedede;
--test-arrow-background-color: rgba(78, 139, 202, 0.2);
--test-arrow-hover-color: #dedede;
--test-arrow-hover-background-color: #4e8bca;
--target-background-color: #494a3d;
--target-border-color: #bb7410;
--kbd-color: #000;

View file

@ -353,7 +353,7 @@ details:not(.toggle) summary {
margin-bottom: .6em;
}
code, pre, a.test-arrow, .code-header {
code, pre, .code-header {
font-family: "Source Code Pro", monospace;
}
.docblock code, .docblock-short code {
@ -946,8 +946,8 @@ because of the `[-]` element which would overlap with it. */
.main-heading a:hover,
.example-wrap .rust a:hover,
.all-items a:hover,
.docblock a:not(.test-arrow):not(.scrape-help):not(.tooltip):hover:not(.doc-anchor),
.docblock-short a:not(.test-arrow):not(.scrape-help):not(.tooltip):hover,
.docblock a:not(.scrape-help):not(.tooltip):hover:not(.doc-anchor),
.docblock-short a:not(.scrape-help):not(.tooltip):hover,
.item-info a {
text-decoration: underline;
}
@ -1461,22 +1461,17 @@ documentation. */
z-index: 1;
}
a.test-arrow {
padding: 5px 7px;
border-radius: var(--button-border-radius);
font-size: 1rem;
color: var(--test-arrow-color);
background-color: var(--test-arrow-background-color);
height: var(--copy-path-height);
padding: 6px 4px 0 11px;
}
a.test-arrow:hover {
color: var(--test-arrow-hover-color);
background-color: var(--test-arrow-hover-background-color);
a.test-arrow::before {
content: url('data:image/svg+xml,<svg viewBox="0 0 20 20" width="18" height="20" \
xmlns="http://www.w3.org/2000/svg"><path d="M0 0l18 10-18 10z"/></svg>');
}
.example-wrap .button-holder {
display: flex;
}
.example-wrap:hover > .test-arrow {
padding: 2px 7px;
}
/*
On iPad, the ":hover" state sticks around, making things work not greatly. Do work around
it, we move it into this media query. More information can be found at:
@ -1486,29 +1481,34 @@ However, using `@media (hover: hover)` makes this rule never to be applied in GU
instead, we check that it's not a "finger" cursor.
*/
@media not (pointer: coarse) {
.example-wrap:hover > .test-arrow, .example-wrap:hover > .button-holder {
.example-wrap:hover > a.test-arrow, .example-wrap:hover > .button-holder {
visibility: visible;
}
}
.example-wrap .button-holder.keep-visible {
visibility: visible;
}
.example-wrap .button-holder .copy-button {
color: var(--copy-path-button-color);
.example-wrap .button-holder .copy-button, .example-wrap .test-arrow {
background: var(--main-background-color);
cursor: pointer;
border-radius: var(--button-border-radius);
height: var(--copy-path-height);
width: var(--copy-path-width);
}
.example-wrap .button-holder .copy-button {
margin-left: var(--button-left-margin);
padding: 2px 0 0 4px;
border: 0;
cursor: pointer;
border-radius: var(--button-border-radius);
}
.example-wrap .button-holder .copy-button::before,
.example-wrap .test-arrow::before {
filter: var(--copy-path-img-filter);
}
.example-wrap .button-holder .copy-button::before {
filter: var(--copy-path-img-filter);
content: var(--clipboard-image);
}
.example-wrap .button-holder .copy-button:hover::before {
.example-wrap .button-holder .copy-button:hover::before,
.example-wrap .test-arrow:hover::before {
filter: var(--copy-path-img-hover-filter);
}
.example-wrap .button-holder .copy-button.clicked::before {
@ -2552,10 +2552,6 @@ by default.
--code-highlight-doc-comment-color: #4d4d4c;
--src-line-numbers-span-color: #c67e2d;
--src-line-number-highlighted-background-color: #fdffd3;
--test-arrow-color: #f5f5f5;
--test-arrow-background-color: rgba(78, 139, 202, 0.2);
--test-arrow-hover-color: #f5f5f5;
--test-arrow-hover-background-color: rgb(78, 139, 202);
--target-background-color: #fdffd3;
--target-border-color: #ad7c37;
--kbd-color: #000;
@ -2658,10 +2654,6 @@ by default.
--code-highlight-doc-comment-color: #8ca375;
--src-line-numbers-span-color: #3b91e2;
--src-line-number-highlighted-background-color: #0a042f;
--test-arrow-color: #dedede;
--test-arrow-background-color: rgba(78, 139, 202, 0.2);
--test-arrow-hover-color: #dedede;
--test-arrow-hover-background-color: #4e8bca;
--target-background-color: #494a3d;
--target-border-color: #bb7410;
--kbd-color: #000;
@ -2771,10 +2763,6 @@ Original by Dempfi (https://github.com/dempfi/ayu)
--code-highlight-doc-comment-color: #a1ac88;
--src-line-numbers-span-color: #5c6773;
--src-line-number-highlighted-background-color: rgba(255, 236, 164, 0.06);
--test-arrow-color: #788797;
--test-arrow-background-color: rgba(57, 175, 215, 0.09);
--test-arrow-hover-color: #c5c5c5;
--test-arrow-hover-background-color: rgba(57, 175, 215, 0.368);
--target-background-color: rgba(255, 236, 164, 0.06);
--target-border-color: rgba(255, 180, 76, 0.85);
--kbd-color: #c5c5c5;

View file

@ -1835,10 +1835,14 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/read-documentation/search.htm
function getExampleWrap(event) {
let elem = event.target;
while (!hasClass(elem, "example-wrap")) {
elem = elem.parentElement;
if (elem === document.body || hasClass(elem, "docblock")) {
if (elem === document.body ||
elem.tagName === "A" ||
elem.tagName === "BUTTON" ||
hasClass(elem, "docblock")
) {
return null;
}
elem = elem.parentElement;
}
return elem;
}

View file

@ -45,7 +45,7 @@ pub(crate) struct Tests {
pub(crate) found_tests: usize,
}
impl crate::doctest::DoctestVisitor for Tests {
impl crate::doctest::DocTestVisitor for Tests {
fn visit_test(&mut self, _: String, config: LangString, _: MdRelLine) {
if config.rust && config.ignore == Ignore::None {
self.found_tests += 1;

View file

@ -495,7 +495,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
ty::RawPtr(_, _) => Res::Primitive(RawPointer),
ty::Ref(..) => Res::Primitive(Reference),
ty::FnDef(..) => panic!("type alias to a function definition"),
ty::FnPtr(_) => Res::Primitive(Fn),
ty::FnPtr(..) => Res::Primitive(Fn),
ty::Never => Res::Primitive(Never),
ty::Adt(ty::AdtDef(Interned(&ty::AdtDefData { did, .. }, _)), _) | ty::Foreign(did) => {
Res::from_def_id(self.cx.tcx, did)

@ -1 +1 @@
Subproject commit ccf4c38bdd73f1a37ec266c73bdaef80e39f8cf6
Subproject commit 57ae1a3474057fead2c438928ed368b3740bf0ec

View file

@ -15,7 +15,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
}
match cast_from.kind() {
ty::FnDef(..) | ty::FnPtr(_) => {
ty::FnDef(..) | ty::FnPtr(..) => {
let mut applicability = Applicability::MaybeIncorrect;
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability);
let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);

View file

@ -14,7 +14,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
_ => { /* continue to checks */ },
}
if let ty::FnDef(..) | ty::FnPtr(_) = cast_from.kind() {
if let ty::FnDef(..) | ty::FnPtr(..) = cast_from.kind() {
let mut applicability = Applicability::MaybeIncorrect;
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability);

View file

@ -14,7 +14,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
_ => return,
}
match cast_from.kind() {
ty::FnDef(..) | ty::FnPtr(_) => {
ty::FnDef(..) | ty::FnPtr(..) => {
let mut applicability = Applicability::MaybeIncorrect;
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability);

View file

@ -236,7 +236,7 @@ fn fn_sig_opt<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<PolyFnSig<'
// We can't use `Ty::fn_sig` because it automatically performs args, this may result in FNs.
match node_ty.kind() {
ty::FnDef(def_id, _) => Some(cx.tcx.fn_sig(*def_id).instantiate_identity()),
ty::FnPtr(fn_sig) => Some(*fn_sig),
ty::FnPtr(sig_tys, hdr) => Some(sig_tys.with(*hdr)),
_ => None,
}
}

View file

@ -872,7 +872,7 @@ impl TyCoercionStability {
| ty::Pat(..)
| ty::Float(_)
| ty::RawPtr(..)
| ty::FnPtr(_)
| ty::FnPtr(..)
| ty::Str
| ty::Slice(..)
| ty::Adt(..)

View file

@ -158,7 +158,7 @@ fn check_clousure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tc
cx.tcx.fn_sig(def).skip_binder().skip_binder()
},
ty::FnPtr(sig) => sig.skip_binder(),
ty::FnPtr(sig_tys, hdr) => sig_tys.with(*hdr).skip_binder(),
ty::Closure(_, subs) => cx
.tcx
.signature_unclosure(subs.as_closure().sig(), Safety::Safe)

View file

@ -58,7 +58,7 @@ fn try_get_caller_ty_name_and_method_name(
fn is_map_to_option(cx: &LateContext<'_>, map_arg: &Expr<'_>) -> bool {
let map_closure_ty = cx.typeck_results().expr_ty(map_arg);
match map_closure_ty.kind() {
ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => {
ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(..) => {
let map_closure_sig = match map_closure_ty.kind() {
ty::Closure(_, args) => args.as_closure().sig(),
_ => map_closure_ty.fn_sig(cx.tcx),

View file

@ -166,7 +166,7 @@ impl<'a, 'tcx> Visitor<'tcx> for DivergenceVisitor<'a, 'tcx> {
ExprKind::Call(func, _) => {
let typ = self.cx.typeck_results().expr_ty(func);
match typ.kind() {
ty::FnDef(..) | ty::FnPtr(_) => {
ty::FnDef(..) | ty::FnPtr(..) => {
let sig = typ.fn_sig(self.cx.tcx);
if self.cx.tcx.instantiate_bound_regions_with_erased(sig).output().kind() == &ty::Never {
self.report_diverging_sub_expr(e);

View file

@ -130,7 +130,7 @@ fn collect_unsafe_exprs<'tcx>(
ExprKind::Call(path_expr, _) => {
let sig = match *cx.typeck_results().expr_ty(path_expr).kind() {
ty::FnDef(id, _) => cx.tcx.fn_sig(id).skip_binder(),
ty::FnPtr(sig) => sig,
ty::FnPtr(sig_tys, hdr) => sig_tys.with(hdr),
_ => return Continue(Descend::Yes),
};
if sig.safety() == Safety::Unsafe {

View file

@ -79,7 +79,7 @@ fn check_arguments<'tcx>(
fn_kind: &str,
) {
match type_definition.kind() {
ty::FnDef(..) | ty::FnPtr(_) => {
ty::FnDef(..) | ty::FnPtr(..) => {
let parameters = type_definition.fn_sig(cx.tcx).skip_binder().inputs();
for (argument, parameter) in iter::zip(arguments, parameters) {
match parameter.kind() {

View file

@ -541,7 +541,7 @@ pub fn peel_mid_ty_refs_is_mutable(ty: Ty<'_>) -> (Ty<'_>, usize, Mutability) {
/// Returns `true` if the given type is an `unsafe` function.
pub fn type_is_unsafe_function<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
match ty.kind() {
ty::FnDef(..) | ty::FnPtr(_) => ty.fn_sig(cx.tcx).safety() == Safety::Unsafe,
ty::FnDef(..) | ty::FnPtr(..) => ty.fn_sig(cx.tcx).safety() == Safety::Unsafe,
_ => false,
}
}
@ -721,7 +721,7 @@ pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'t
cx.tcx.item_super_predicates(def_id).iter_instantiated(cx.tcx, args),
cx.tcx.opt_parent(def_id),
),
ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig, None)),
ty::FnPtr(sig_tys, hdr) => Some(ExprFnSig::Sig(sig_tys.with(hdr), None)),
ty::Dynamic(bounds, _, _) => {
let lang_items = cx.tcx.lang_items();
match bounds.principal() {

View file

@ -441,7 +441,7 @@ pub fn is_expr_unsafe<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
ty::FnDef(id, _) if self.cx.tcx.fn_sig(id).skip_binder().safety() == Safety::Unsafe => {
self.is_unsafe = true;
},
ty::FnPtr(sig) if sig.safety() == Safety::Unsafe => self.is_unsafe = true,
ty::FnPtr(_, hdr) if hdr.safety == Safety::Unsafe => self.is_unsafe = true,
_ => walk_expr(self, e),
},
ExprKind::Path(ref p)

View file

@ -1,5 +1,4 @@
//! These structs are a subset of the ones found in `rustc_errors::json`.
//! They are only used for deserialization of JSON output provided by libtest.
use std::path::{Path, PathBuf};
use std::str::FromStr;
@ -127,11 +126,10 @@ pub fn extract_rendered(output: &str) -> String {
// Ignore the notification.
None
} else {
print!(
"failed to decode compiler output as json: line: {}\noutput: {}",
line, output
);
panic!()
// This function is called for both compiler and non-compiler output,
// so if the line isn't recognized as JSON from the compiler then
// just print it as-is.
Some(format!("{line}\n"))
}
} else {
// preserve non-JSON lines, such as ICEs

View file

@ -3027,11 +3027,17 @@ impl<'test> TestCx<'test> {
const PREFIX: &str = "MONO_ITEM ";
const CGU_MARKER: &str = "@@";
// Some MonoItems can contain {closure@/path/to/checkout/tests/codgen-units/test.rs}
// To prevent the current dir from leaking, we just replace the entire path to the test
// file with TEST_PATH.
let actual: Vec<MonoItem> = proc_res
.stdout
.lines()
.filter(|line| line.starts_with(PREFIX))
.map(|line| str_to_mono_item(line, true))
.map(|line| {
line.replace(&self.testpaths.file.display().to_string(), "TEST_PATH").to_string()
})
.map(|line| str_to_mono_item(&line, true))
.collect();
let expected: Vec<MonoItem> = errors::load_errors(&self.testpaths.file, None)
@ -3729,15 +3735,14 @@ impl<'test> TestCx<'test> {
}
if self.config.bless {
cmd.env("RUSTC_BLESS_TEST", "--bless");
// Assume this option is active if the environment variable is "defined", with _any_ value.
// As an example, a `Makefile` can use this option by:
// If we're running in `--bless` mode, set an environment variable to tell
// `run_make_support` to bless snapshot files instead of checking them.
//
// ifdef RUSTC_BLESS_TEST
// cp "$(TMPDIR)"/actual_something.ext expected_something.ext
// else
// $(DIFF) expected_something.ext "$(TMPDIR)"/actual_something.ext
// endif
// The value is this test's source directory, because the support code
// will need that path in order to bless the _original_ snapshot files,
// not the copies in `rmake_out`.
// (See <https://github.com/rust-lang/rust/issues/129038>.)
cmd.env("RUSTC_BLESS_TEST", &self.testpaths.file);
}
if self.config.target.contains("msvc") && !self.config.cc.is_empty() {

View file

@ -35,7 +35,6 @@ fn main() -> Result<(), Box<dyn Error>> {
let mut f = std::fs::File::options().append(true).open("windows_sys.rs")?;
f.write_all(ARM32_SHIM.as_bytes())?;
writeln!(&mut f, "// ignore-tidy-filelength")?;
writeln!(&mut f, "use super::windows_targets;")?;
Ok(())
}

View file

@ -56,6 +56,7 @@ extern crate either;
extern crate tracing;
// The rustc crates we need
extern crate rustc_attr;
extern crate rustc_apfloat;
extern crate rustc_ast;
extern crate rustc_const_eval;

View file

@ -11,6 +11,7 @@ use rand::rngs::StdRng;
use rand::Rng;
use rand::SeedableRng;
use rustc_attr::InlineAttr;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
#[allow(unused)]
use rustc_data_structures::static_assert_size;
@ -23,6 +24,7 @@ use rustc_middle::{
Instance, Ty, TyCtxt,
},
};
use rustc_session::config::InliningThreshold;
use rustc_span::def_id::{CrateNum, DefId};
use rustc_span::{Span, SpanData, Symbol};
use rustc_target::abi::{Align, Size};
@ -47,10 +49,10 @@ pub const SIGRTMIN: i32 = 34;
/// `SIGRTMAX` - `SIGRTMIN` >= 8 (which is the value of `_POSIX_RTSIG_MAX`)
pub const SIGRTMAX: i32 = 42;
/// Each const has multiple addresses, but only this many. Since const allocations are never
/// deallocated, choosing a new [`AllocId`] and thus base address for each evaluation would
/// produce unbounded memory usage.
const ADDRS_PER_CONST: usize = 16;
/// Each anonymous global (constant, vtable, function pointer, ...) has multiple addresses, but only
/// this many. Since const allocations are never deallocated, choosing a new [`AllocId`] and thus
/// base address for each evaluation would produce unbounded memory usage.
const ADDRS_PER_ANON_GLOBAL: usize = 32;
/// Extra data stored with each stack frame
pub struct FrameExtra<'tcx> {
@ -1372,7 +1374,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
catch_unwind: None,
timing,
is_user_relevant: ecx.machine.is_user_relevant(&frame),
salt: ecx.machine.rng.borrow_mut().gen::<usize>() % ADDRS_PER_CONST,
salt: ecx.machine.rng.borrow_mut().gen::<usize>() % ADDRS_PER_ANON_GLOBAL,
};
Ok(frame.with_extra(extra))
@ -1518,4 +1520,45 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
Entry::Occupied(oe) => Ok(oe.get().clone()),
}
}
fn get_global_alloc_salt(
ecx: &InterpCx<'tcx, Self>,
instance: Option<ty::Instance<'tcx>>,
) -> usize {
let unique = if let Some(instance) = instance {
// Functions cannot be identified by pointers, as asm-equal functions can get
// deduplicated by the linker (we set the "unnamed_addr" attribute for LLVM) and
// functions can be duplicated across crates. We thus generate a new `AllocId` for every
// mention of a function. This means that `main as fn() == main as fn()` is false, while
// `let x = main as fn(); x == x` is true. However, as a quality-of-life feature it can
// be useful to identify certain functions uniquely, e.g. for backtraces. So we identify
// whether codegen will actually emit duplicate functions. It does that when they have
// non-lifetime generics, or when they can be inlined. All other functions are given a
// unique address. This is not a stable guarantee! The `inline` attribute is a hint and
// cannot be relied upon for anything. But if we don't do this, the
// `__rust_begin_short_backtrace`/`__rust_end_short_backtrace` logic breaks and panic
// backtraces look terrible.
let is_generic = instance
.args
.into_iter()
.any(|kind| !matches!(kind.unpack(), ty::GenericArgKind::Lifetime(_)));
let can_be_inlined = matches!(
ecx.tcx.sess.opts.unstable_opts.cross_crate_inline_threshold,
InliningThreshold::Always
) || !matches!(
ecx.tcx.codegen_fn_attrs(instance.def_id()).inline,
InlineAttr::Never
);
!is_generic && !can_be_inlined
} else {
// Non-functions are never unique.
false
};
// Always use the same salt if the allocation is unique.
if unique {
CTFE_ALLOC_SALT
} else {
ecx.machine.rng.borrow_mut().gen::<usize>() % ADDRS_PER_ANON_GLOBAL
}
}
}

View file

@ -141,7 +141,17 @@ fn unsized_dyn_autoderef() {
}
*/
fn vtable_ptr_eq() {
use std::{fmt, ptr};
// We don't always get the same vtable when casting this to a wide pointer.
let x = &2;
let x_wide = x as &dyn fmt::Display;
assert!((0..256).any(|_| !ptr::eq(x as &dyn fmt::Display, x_wide)));
}
fn main() {
ref_box_dyn();
box_box_trait();
vtable_ptr_eq();
}

View file

@ -82,7 +82,8 @@ fn main() {
assert!(return_fn_ptr(i) == i);
assert!(return_fn_ptr(i) as unsafe fn() -> i32 == i as fn() -> i32 as unsafe fn() -> i32);
// Miri gives different addresses to different reifications of a generic function.
assert!(return_fn_ptr(f) != f);
// at least if we try often enough.
assert!((0..256).any(|_| return_fn_ptr(f) != f));
// However, if we only turn `f` into a function pointer and use that pointer,
// it is equal to itself.
let f2 = f as fn() -> i32;

View file

@ -75,7 +75,8 @@ fn rc_fat_ptr_eq() {
let p = Rc::new(1) as Rc<dyn Debug>;
let a: *const dyn Debug = &*p;
let r = Rc::into_raw(p);
assert!(a == r);
// Only compare the pointer parts, as the vtable might differ.
assert!(a as *const () == r as *const ());
drop(unsafe { Rc::from_raw(r) });
}

View file

@ -77,6 +77,20 @@ pub fn assert_not_contains_regex<H: AsRef<str>, N: AsRef<str>>(haystack: H, need
}
}
/// Assert that `haystack` contains `needle` a `count` number of times.
#[track_caller]
pub fn assert_count_is<H: AsRef<str>, N: AsRef<str>>(count: usize, haystack: H, needle: N) {
let haystack = haystack.as_ref();
let needle = needle.as_ref();
if count != haystack.matches(needle).count() {
eprintln!("=== HAYSTACK ===");
eprintln!("{}", haystack);
eprintln!("=== NEEDLE ===");
eprintln!("{}", needle);
panic!("needle did not appear {count} times in haystack");
}
}
/// Assert that all files in `dir1` exist and have the same content in `dir2`
pub fn assert_dirs_are_equal(dir1: impl AsRef<Path>, dir2: impl AsRef<Path>) {
let dir2 = dir2.as_ref();

View file

@ -112,14 +112,8 @@ impl Diff {
let (expected_name, actual_name, output, actual) = self.run_common();
if !output.is_empty() {
// If we can bless (meaning we have a file to write into and the `RUSTC_BLESS_TEST`
// environment variable set), then we write into the file and return.
if let Some(ref expected_file) = self.expected_file {
if std::env::var("RUSTC_BLESS_TEST").is_ok() {
println!("Blessing `{}`", expected_file.display());
fs::write(expected_file, actual);
return;
}
if self.maybe_bless_expected_file(&actual) {
return;
}
panic!(
"test failed: `{}` is different from `{}`\n\n{}",
@ -134,14 +128,8 @@ impl Diff {
let (expected_name, actual_name, output, actual) = self.run_common();
if output.is_empty() {
// If we can bless (meaning we have a file to write into and the `RUSTC_BLESS_TEST`
// environment variable set), then we write into the file and return.
if let Some(ref expected_file) = self.expected_file {
if std::env::var("RUSTC_BLESS_TEST").is_ok() {
println!("Blessing `{}`", expected_file.display());
fs::write(expected_file, actual);
return;
}
if self.maybe_bless_expected_file(&actual) {
return;
}
panic!(
"test failed: `{}` is not different from `{}`\n\n{}",
@ -149,4 +137,24 @@ impl Diff {
)
}
}
/// If we have an expected file to write into, and `RUSTC_BLESS_TEST` is
/// set, then write the actual output into the file and return `true`.
///
/// We assume that `RUSTC_BLESS_TEST` contains the path to the original test's
/// source directory. That lets us bless the original snapshot file in the
/// source tree, not the copy in `rmake_out` that we would normally use.
fn maybe_bless_expected_file(&self, actual: &str) -> bool {
let Some(ref expected_file) = self.expected_file else {
return false;
};
let Ok(bless_dir) = std::env::var("RUSTC_BLESS_TEST") else {
return false;
};
let bless_file = Path::new(&bless_dir).join(expected_file);
println!("Blessing `{}`", bless_file.display());
fs::write(bless_file, actual);
true
}
}

View file

@ -48,6 +48,12 @@ pub fn llvm_bcanalyzer() -> LlvmBcanalyzer {
LlvmBcanalyzer::new()
}
/// Construct a new `llvm-dwarfdump` invocation. This assumes that `llvm-dwarfdump` is available
/// at `$LLVM_BIN_DIR/llvm-dwarfdump`.
pub fn llvm_dwarfdump() -> LlvmDwarfdump {
LlvmDwarfdump::new()
}
/// A `llvm-readobj` invocation builder.
#[derive(Debug)]
#[must_use]
@ -97,6 +103,13 @@ pub struct LlvmBcanalyzer {
cmd: Command,
}
/// A `llvm-dwarfdump` invocation builder.
#[derive(Debug)]
#[must_use]
pub struct LlvmDwarfdump {
cmd: Command,
}
crate::macros::impl_common_helpers!(LlvmReadobj);
crate::macros::impl_common_helpers!(LlvmProfdata);
crate::macros::impl_common_helpers!(LlvmFilecheck);
@ -104,6 +117,7 @@ crate::macros::impl_common_helpers!(LlvmObjdump);
crate::macros::impl_common_helpers!(LlvmAr);
crate::macros::impl_common_helpers!(LlvmNm);
crate::macros::impl_common_helpers!(LlvmBcanalyzer);
crate::macros::impl_common_helpers!(LlvmDwarfdump);
/// Generate the path to the bin directory of LLVM.
#[must_use]
@ -317,3 +331,19 @@ impl LlvmBcanalyzer {
self
}
}
impl LlvmDwarfdump {
/// Construct a new `llvm-dwarfdump` invocation. This assumes that `llvm-dwarfdump` is available
/// at `$LLVM_BIN_DIR/llvm-dwarfdump`.
pub fn new() -> Self {
let llvm_dwarfdump = llvm_bin_dir().join("llvm-dwarfdump");
let cmd = Command::new(llvm_dwarfdump);
Self { cmd }
}
/// Provide an input file.
pub fn input<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
self.cmd.arg(path.as_ref());
self
}
}

View file

@ -49,8 +49,9 @@ pub use c_build::{build_native_dynamic_lib, build_native_static_lib, build_nativ
pub use clang::{clang, Clang};
pub use htmldocck::htmldocck;
pub use llvm::{
llvm_ar, llvm_bcanalyzer, llvm_filecheck, llvm_nm, llvm_objdump, llvm_profdata, llvm_readobj,
LlvmAr, LlvmBcanalyzer, LlvmFilecheck, LlvmNm, LlvmObjdump, LlvmProfdata, LlvmReadobj,
llvm_ar, llvm_bcanalyzer, llvm_dwarfdump, llvm_filecheck, llvm_nm, llvm_objdump, llvm_profdata,
llvm_readobj, LlvmAr, LlvmBcanalyzer, LlvmDwarfdump, LlvmFilecheck, LlvmNm, LlvmObjdump,
LlvmProfdata, LlvmReadobj,
};
pub use python::python_command;
pub use rustc::{aux_build, bare_rustc, rustc, Rustc};
@ -86,7 +87,7 @@ pub use path_helpers::{
pub use scoped_run::{run_in_tmpdir, test_while_readonly};
pub use assertion_helpers::{
assert_contains, assert_contains_regex, assert_dirs_are_equal, assert_equals,
assert_contains, assert_contains_regex, assert_count_is, assert_dirs_are_equal, assert_equals,
assert_not_contains, assert_not_contains_regex,
};

View file

@ -30,7 +30,6 @@ jobs:
run: |
git config --global user.email "runner@gha.local"
git config --global user.name "GitHub Action"
# Remove r-a crates from the workspaces so we don't auto-publish them as well
sed -i 's/ "crates\/\*"//' ./Cargo.toml
sed -i 's/ "xtask\/"//' ./Cargo.toml
# Only publish the crates under lib/
sed -i 's|^members = .*$|members = ["lib/*"]|' Cargo.toml
cargo workspaces publish --yes --exact --from-git --no-git-commit --allow-dirty

View file

@ -52,16 +52,16 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "backtrace"
version = "0.3.72"
version = "0.3.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11"
checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object 0.35.0",
"object 0.36.3",
"rustc-demangle",
]
@ -92,9 +92,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.5.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "byteorder"
@ -136,9 +136,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.98"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f"
checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292"
[[package]]
name = "cfg"
@ -148,10 +148,10 @@ dependencies = [
"derive_arbitrary",
"expect-test",
"intern",
"mbe",
"oorandom",
"rustc-hash",
"syntax",
"syntax-bridge",
"tt",
]
@ -185,7 +185,7 @@ version = "0.98.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f2eb1cd6054da221bd1ac0197fb2fe5e2caf3dcb93619398fc1433f8f09093"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"chalk-derive",
]
@ -226,9 +226,9 @@ checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
[[package]]
name = "cov-mark"
version = "2.0.0-pre.1"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d48d8f76bd9331f19fe2aaf3821a9f9fb32c3963e1e3d6ce82a8c09cef7444a"
checksum = "0570650661aa447e7335f1d5e4f499d8e58796e617bedc9267d971e51c8b49d4"
[[package]]
name = "crc32fast"
@ -366,9 +366,9 @@ checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1"
[[package]]
name = "either"
version = "1.12.0"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "ena"
@ -397,14 +397,14 @@ dependencies = [
[[package]]
name = "filetime"
version = "0.2.23"
version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
checksum = "bf401df4a4e3872c4fe8151134cf483738e74b67fc934d6532c882b3d24a4550"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.4.1",
"windows-sys 0.52.0",
"libredox",
"windows-sys 0.59.0",
]
[[package]]
@ -415,31 +415,14 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flate2"
version = "1.0.30"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "flycheck"
version = "0.0.0"
dependencies = [
"cargo_metadata",
"crossbeam-channel",
"paths",
"process-wrap",
"project-model",
"rustc-hash",
"serde",
"serde_json",
"stdx",
"toolchain",
"tracing",
]
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@ -529,7 +512,7 @@ version = "0.0.0"
dependencies = [
"arrayvec",
"base-db",
"bitflags 2.5.0",
"bitflags 2.6.0",
"cfg",
"cov-mark",
"dashmap",
@ -554,6 +537,7 @@ dependencies = [
"span",
"stdx",
"syntax",
"syntax-bridge",
"test-fixture",
"test-utils",
"tracing",
@ -582,6 +566,7 @@ dependencies = [
"span",
"stdx",
"syntax",
"syntax-bridge",
"tracing",
"triomphe",
"tt",
@ -593,7 +578,7 @@ version = "0.0.0"
dependencies = [
"arrayvec",
"base-db",
"bitflags 2.5.0",
"bitflags 2.6.0",
"chalk-derive",
"chalk-ir",
"chalk-recursive",
@ -722,7 +707,7 @@ version = "0.0.0"
dependencies = [
"arrayvec",
"base-db",
"bitflags 2.5.0",
"bitflags 2.6.0",
"cov-mark",
"crossbeam-channel",
"either",
@ -803,9 +788,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.2.6"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0"
dependencies = [
"equivalent",
"hashbrown",
@ -895,9 +880,9 @@ checksum = "3752f229dcc5a481d60f385fa479ff46818033d881d2d801aa27dffcfb5e8306"
[[package]]
name = "lazy_static"
version = "1.4.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
@ -907,19 +892,19 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "libloading"
version = "0.8.3"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
dependencies = [
"cfg-if",
"windows-targets 0.52.5",
"windows-targets 0.52.6",
]
[[package]]
name = "libmimalloc-sys"
version = "0.1.38"
version = "0.1.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7bb23d733dfcc8af652a78b7bf232f0e967710d044732185e561e47c0336b6"
checksum = "23aa6811d3bd4deb8a84dde645f943476d13b248d818edcf8ce0b2f37f036b44"
dependencies = [
"cc",
"libc",
@ -931,8 +916,9 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"libc",
"redox_syscall",
]
[[package]]
@ -996,9 +982,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.21"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "lsp-server"
@ -1056,6 +1042,7 @@ dependencies = [
"span",
"stdx",
"syntax",
"syntax-bridge",
"test-utils",
"tracing",
"tt",
@ -1063,9 +1050,9 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.7.2"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "memmap2"
@ -1087,18 +1074,18 @@ dependencies = [
[[package]]
name = "mimalloc"
version = "0.1.42"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9186d86b79b52f4a77af65604b51225e8db1d6ee7e3f41aec1e40829c71a176"
checksum = "68914350ae34959d83f732418d51e2427a794055d0b9529f48259ac07af65633"
dependencies = [
"libmimalloc-sys",
]
[[package]]
name = "miniz_oxide"
version = "0.7.3"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae"
checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
dependencies = [
"adler",
]
@ -1130,7 +1117,7 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"cfg-if",
"cfg_aliases",
"libc",
@ -1148,7 +1135,7 @@ version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"crossbeam-channel",
"filetime",
"fsevent-sys",
@ -1163,11 +1150,11 @@ dependencies = [
[[package]]
name = "nu-ansi-term"
version = "0.50.0"
version = "0.50.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd2800e1520bdc966782168a627aa5d1ad92e33b984bf7c7615d31280c83ff14"
checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
@ -1197,9 +1184,9 @@ dependencies = [
[[package]]
name = "object"
version = "0.35.0"
version = "0.36.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e"
checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9"
dependencies = [
"memchr",
]
@ -1212,9 +1199,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "oorandom"
version = "11.1.3"
version = "11.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
[[package]]
name = "option-ext"
@ -1240,9 +1227,9 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.5.1",
"redox_syscall",
"smallvec",
"windows-targets 0.52.5",
"windows-targets 0.52.6",
]
[[package]]
@ -1268,6 +1255,7 @@ name = "paths"
version = "0.0.0"
dependencies = [
"camino",
"serde",
]
[[package]]
@ -1319,9 +1307,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro-api"
@ -1330,14 +1321,12 @@ dependencies = [
"base-db",
"indexmap",
"intern",
"la-arena 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"paths",
"rustc-hash",
"serde",
"serde_json",
"span",
"stdx",
"text-size",
"tracing",
"tt",
]
@ -1350,7 +1339,6 @@ dependencies = [
"expect-test",
"intern",
"libloading",
"mbe",
"memmap2",
"object 0.33.0",
"paths",
@ -1360,6 +1348,7 @@ dependencies = [
"snap",
"span",
"stdx",
"syntax-bridge",
"tt",
]
@ -1380,9 +1369,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.85"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [
"unicode-ident",
]
@ -1460,7 +1449,7 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"memchr",
"unicase",
]
@ -1485,20 +1474,20 @@ dependencies = [
[[package]]
name = "ra-ap-rustc_abi"
version = "0.53.0"
version = "0.63.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80b1d613eee933486c0613a7bc26e515e46f43adf479d1edd5e537f983e9ce46"
checksum = "b011c39d409940a890414e3a7b239762ac16d88029ad71b050a8374831b93790"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
"ra-ap-rustc_index",
"tracing",
]
[[package]]
name = "ra-ap-rustc_index"
version = "0.53.0"
version = "0.63.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f072060ac77e9e1a02cc20028095993af7e72cc0804779c68bcbf47b16de49c9"
checksum = "9027acdee649b0b27eb10b7db5be833efee3362d394935c5eed8f0745a9d43ce"
dependencies = [
"arrayvec",
"ra-ap-rustc_index_macros",
@ -1507,21 +1496,20 @@ dependencies = [
[[package]]
name = "ra-ap-rustc_index_macros"
version = "0.53.0"
version = "0.63.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82f3d6dcb30a66905388e14756b8f2216131d9f8004922c07f13335840e058d1"
checksum = "540b86dc0384141ac8e825fc2874cd44bffd4277d99d8ec63ee416f1a98d5997"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "ra-ap-rustc_lexer"
version = "0.53.0"
version = "0.63.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbd8a2b0bdcba9892cbce0b25f6c953d31b0febc1f3420fc692884fce5a23ad8"
checksum = "3bdf98bb457b47b9ae4aeebf867d0ca440c86925e0b6381658c4a02589748c9d"
dependencies = [
"unicode-properties",
"unicode-xid",
@ -1529,9 +1517,9 @@ dependencies = [
[[package]]
name = "ra-ap-rustc_parse_format"
version = "0.53.0"
version = "0.63.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dad7a491c2554590222e0c9212dcb7c2e7aceb668875075012a35ea780d135"
checksum = "e8fe3556ab6311bb775220563a300e2bf62ec56404521fe0c511a583937683d5"
dependencies = [
"ra-ap-rustc_index",
"ra-ap-rustc_lexer",
@ -1539,9 +1527,9 @@ dependencies = [
[[package]]
name = "ra-ap-rustc_pattern_analysis"
version = "0.53.0"
version = "0.63.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34768e1faf88c31f2e9ad57b48318a52b507dafac0cddbf01b5d63bfc0b0a365"
checksum = "1709080fdeb5db630e1c2644026c2962aaa32416cd92f0190c04b0c21e114b91"
dependencies = [
"ra-ap-rustc_index",
"rustc-hash",
@ -1602,20 +1590,11 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.4.1"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e"
dependencies = [
"bitflags 2.5.0",
"bitflags 2.6.0",
]
[[package]]
@ -1648,12 +1627,12 @@ version = "0.0.0"
dependencies = [
"always-assert",
"anyhow",
"cargo_metadata",
"cfg",
"crossbeam-channel",
"dirs",
"dissimilar",
"expect-test",
"flycheck",
"hir",
"hir-def",
"hir-ty",
@ -1665,7 +1644,6 @@ dependencies = [
"load-cargo",
"lsp-server 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
"lsp-types",
"mbe",
"memchr",
"mimalloc",
"nohash-hasher",
@ -1675,6 +1653,7 @@ dependencies = [
"parser",
"paths",
"proc-macro-api",
"process-wrap",
"profile",
"project-model",
"rayon",
@ -1685,6 +1664,7 @@ dependencies = [
"serde_json",
"stdx",
"syntax",
"syntax-bridge",
"test-fixture",
"test-utils",
"tikv-jemallocator",
@ -1716,9 +1696,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_apfloat"
version = "0.2.0+llvm-462a31f5a5ab"
version = "0.2.1+llvm-462a31f5a5ab"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "465187772033a5ee566f69fe008df03628fce549a0899aae76f0a0c2e34696be"
checksum = "886d94c63c812a8037c4faca2607453a0fa4cf82f734665266876b022244543f"
dependencies = [
"bitflags 1.3.2",
"smallvec",
@ -1801,18 +1781,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.203"
version = "1.0.206"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
checksum = "5b3e4cd94123dd520a128bcd11e34d9e9e423e7e3e50425cb1b4b1e3549d0284"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.203"
version = "1.0.206"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97"
dependencies = [
"proc-macro2",
"quote",
@ -1821,12 +1801,13 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.117"
version = "1.0.124"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d"
dependencies = [
"indexmap",
"itoa",
"memchr",
"ryu",
"serde",
]
@ -1844,9 +1825,9 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "0.6.6"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
dependencies = [
"serde",
]
@ -1923,9 +1904,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.66"
version = "2.0.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7"
dependencies = [
"proc-macro2",
"quote",
@ -1967,6 +1948,21 @@ dependencies = [
"triomphe",
]
[[package]]
name = "syntax-bridge"
version = "0.0.0"
dependencies = [
"intern",
"parser",
"rustc-hash",
"span",
"stdx",
"syntax",
"test-utils",
"tracing",
"tt",
]
[[package]]
name = "test-fixture"
version = "0.0.0"
@ -1987,6 +1983,7 @@ name = "test-utils"
version = "0.0.0"
dependencies = [
"dissimilar",
"paths",
"profile",
"rustc-hash",
"stdx",
@ -2010,18 +2007,18 @@ checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233"
[[package]]
name = "thiserror"
version = "1.0.61"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.61"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [
"proc-macro2",
"quote",
@ -2090,9 +2087,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "tinyvec"
version = "1.6.0"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
dependencies = [
"tinyvec_macros",
]
@ -2105,9 +2102,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "toml"
version = "0.8.14"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
dependencies = [
"serde",
"serde_spanned",
@ -2117,18 +2114,18 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.6.6"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.14"
version = "0.22.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
dependencies = [
"indexmap",
"serde",
@ -2214,9 +2211,9 @@ dependencies = [
[[package]]
name = "triomphe"
version = "0.1.12"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b2cb4fbb9995eeb36ac86fadf24031ccd58f99d6b4b2d7b911db70bddb80d90"
checksum = "e6631e42e10b40c0690bf92f404ebcfe6e1fdb480391d15f17cc8e96eeed5369"
dependencies = [
"serde",
"stable_deref_trait",
@ -2289,9 +2286,9 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "url"
version = "2.5.0"
version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
dependencies = [
"form_urlencoded",
"idna",
@ -2307,14 +2304,15 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "version_check"
version = "0.9.4"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "vfs"
version = "0.0.0"
dependencies = [
"crossbeam-channel",
"fst",
"indexmap",
"nohash-hasher",
@ -2331,6 +2329,8 @@ dependencies = [
"crossbeam-channel",
"notify",
"paths",
"rayon",
"rustc-hash",
"stdx",
"tracing",
"vfs",
@ -2355,11 +2355,11 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi-util"
version = "0.1.8"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -2369,7 +2369,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132"
dependencies = [
"windows-core",
"windows-targets 0.52.5",
"windows-targets 0.52.6",
]
[[package]]
@ -2381,7 +2381,7 @@ dependencies = [
"windows-implement",
"windows-interface",
"windows-result",
"windows-targets 0.52.5",
"windows-targets 0.52.6",
]
[[package]]
@ -2408,11 +2408,11 @@ dependencies = [
[[package]]
name = "windows-result"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "749f0da9cc72d82e600d8d2e44cadd0b9eedb9038f71a1c58556ac1c5791813b"
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
dependencies = [
"windows-targets 0.52.5",
"windows-targets 0.52.6",
]
[[package]]
@ -2430,7 +2430,16 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.5",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
@ -2450,18 +2459,18 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.5",
"windows_aarch64_msvc 0.52.5",
"windows_i686_gnu 0.52.5",
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.5",
"windows_x86_64_gnu 0.52.5",
"windows_x86_64_gnullvm 0.52.5",
"windows_x86_64_msvc 0.52.5",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
@ -2472,9 +2481,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
@ -2484,9 +2493,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
@ -2496,15 +2505,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
@ -2514,9 +2523,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
@ -2526,9 +2535,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
@ -2538,9 +2547,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
@ -2550,15 +2559,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.6.11"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c52728401e1dc672a56e81e593e912aa54c78f40246869f78359a2bf24d29d"
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
dependencies = [
"memchr",
]
@ -2618,6 +2627,27 @@ dependencies = [
"zip",
]
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zip"
version = "0.6.6"

View file

@ -4,10 +4,11 @@ exclude = ["crates/proc-macro-srv/proc-macro-test/imp"]
resolver = "2"
[workspace.package]
rust-version = "1.78"
rust-version = "1.80"
edition = "2021"
license = "MIT OR Apache-2.0"
authors = ["rust-analyzer team"]
repository = "https://github.com/rust-lang/rust-analyzer"
[profile.dev]
debug = 1
@ -51,7 +52,6 @@ debug = 2
# local crates
base-db = { path = "./crates/base-db", version = "0.0.0" }
cfg = { path = "./crates/cfg", version = "0.0.0", features = ["tt"] }
flycheck = { path = "./crates/flycheck", version = "0.0.0" }
hir = { path = "./crates/hir", version = "0.0.0" }
hir-def = { path = "./crates/hir-def", version = "0.0.0" }
hir-expand = { path = "./crates/hir-expand", version = "0.0.0" }
@ -77,17 +77,18 @@ salsa = { path = "./crates/salsa", version = "0.0.0" }
span = { path = "./crates/span", version = "0.0.0" }
stdx = { path = "./crates/stdx", version = "0.0.0" }
syntax = { path = "./crates/syntax", version = "0.0.0" }
syntax-bridge = { path = "./crates/syntax-bridge", version = "0.0.0" }
text-edit = { path = "./crates/text-edit", version = "0.0.0" }
toolchain = { path = "./crates/toolchain", version = "0.0.0" }
tt = { path = "./crates/tt", version = "0.0.0" }
vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" }
vfs = { path = "./crates/vfs", version = "0.0.0" }
ra-ap-rustc_lexer = { version = "0.53.0", default-features = false }
ra-ap-rustc_parse_format = { version = "0.53.0", default-features = false }
ra-ap-rustc_index = { version = "0.53.0", default-features = false }
ra-ap-rustc_abi = { version = "0.53.0", default-features = false }
ra-ap-rustc_pattern_analysis = { version = "0.53.0", default-features = false }
ra-ap-rustc_lexer = { version = "0.63.0", default-features = false }
ra-ap-rustc_parse_format = { version = "0.63.0", default-features = false }
ra-ap-rustc_index = { version = "0.63.0", default-features = false }
ra-ap-rustc_abi = { version = "0.63.0", default-features = false }
ra-ap-rustc_pattern_analysis = { version = "0.63.0", default-features = false }
# local crates that aren't published to crates.io. These should not have versions.
test-fixture = { path = "./crates/test-fixture" }
@ -124,11 +125,11 @@ memmap2 = "0.5.4"
nohash-hasher = "0.2.0"
oorandom = "11.1.3"
object = { version = "0.33.0", default-features = false, features = [
"std",
"read_core",
"elf",
"macho",
"pe",
"std",
"read_core",
"elf",
"macho",
"pe",
] }
process-wrap = { version = "8.0.2", features = ["std"] }
pulldown-cmark-to-cmark = "10.0.4"
@ -158,7 +159,6 @@ url = "2.3.1"
xshell = "0.2.5"
# We need to freeze the version of the crate, as the raw-api feature is considered unstable
dashmap = { version = "=5.5.3", features = ["raw-api"] }

View file

@ -1,7 +1,8 @@
[package]
name = "base-db"
version = "0.0.0"
description = "TBD"
repository.workspace = true
description = "Basic database traits for rust-analyzer. The concrete DB is defined by `ide` (aka `ra_ap_ide`)."
authors.workspace = true
edition.workspace = true

View file

@ -7,7 +7,7 @@ use salsa::Durability;
use triomphe::Arc;
use vfs::FileId;
use crate::{CrateGraph, SourceDatabaseExt, SourceDatabaseExt2, SourceRoot, SourceRootId};
use crate::{CrateGraph, SourceDatabaseFileInputExt, SourceRoot, SourceRootDatabase, SourceRootId};
/// Encapsulate a bunch of raw `.set` calls on the database.
#[derive(Default)]
@ -50,7 +50,7 @@ impl FileChange {
self.crate_graph = Some(graph);
}
pub fn apply(self, db: &mut dyn SourceDatabaseExt) {
pub fn apply(self, db: &mut dyn SourceRootDatabase) {
let _p = tracing::info_span!("FileChange::apply").entered();
if let Some(roots) = self.roots {
for (idx, root) in roots.into_iter().enumerate() {

View file

@ -690,6 +690,14 @@ impl Env {
pub fn extend_from_other(&mut self, other: &Env) {
self.entries.extend(other.entries.iter().map(|(x, y)| (x.to_owned(), y.to_owned())));
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn insert(&mut self, k: impl Into<String>, v: impl Into<String>) -> Option<String> {
self.entries.insert(k.into(), v.into())
}
}
impl From<Env> for Vec<(String, String)> {
@ -700,6 +708,15 @@ impl From<Env> for Vec<(String, String)> {
}
}
impl<'a> IntoIterator for &'a Env {
type Item = (&'a String, &'a String);
type IntoIter = std::collections::hash_map::Iter<'a, String, String>;
fn into_iter(self) -> Self::IntoIter {
self.entries.iter()
}
}
#[derive(Debug)]
pub struct CyclicDependenciesError {
path: Vec<(CrateId, Option<CrateDisplayName>)>,

View file

@ -1,5 +1,5 @@
//! base_db defines basic database traits. The concrete DB is defined by ide.
// FIXME: Rename this crate, base db is non descriptive
mod change;
mod input;
@ -47,8 +47,6 @@ pub const DEFAULT_PARSE_LRU_CAP: u16 = 128;
pub const DEFAULT_BORROWCK_LRU_CAP: u16 = 2024;
pub trait FileLoader {
/// Text of the file.
fn file_text(&self, file_id: FileId) -> Arc<str>;
fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId>;
/// Crates whose root's source root is the same as the source root of `file_id`
fn relevant_crates(&self, file_id: FileId) -> Arc<[CrateId]>;
@ -58,6 +56,13 @@ pub trait FileLoader {
/// model. Everything else in rust-analyzer is derived from these queries.
#[salsa::query_group(SourceDatabaseStorage)]
pub trait SourceDatabase: FileLoader + std::fmt::Debug {
#[salsa::input]
fn compressed_file_text(&self, file_id: FileId) -> Arc<[u8]>;
/// Text of the file.
#[salsa::lru]
fn file_text(&self, file_id: FileId) -> Arc<str>;
/// Parses the file into the syntax tree.
#[salsa::lru]
fn parse(&self, file_id: EditionedFileId) -> Parse<ast::SourceFile>;
@ -99,16 +104,18 @@ fn parse_errors(db: &dyn SourceDatabase, file_id: EditionedFileId) -> Option<Arc
}
}
fn file_text(db: &dyn SourceDatabase, file_id: FileId) -> Arc<str> {
let bytes = db.compressed_file_text(file_id);
let bytes =
lz4_flex::decompress_size_prepended(&bytes).expect("lz4 decompression should not fail");
let text = std::str::from_utf8(&bytes).expect("file contents should be valid UTF-8");
Arc::from(text)
}
/// We don't want to give HIR knowledge of source roots, hence we extract these
/// methods into a separate DB.
#[salsa::query_group(SourceDatabaseExtStorage)]
pub trait SourceDatabaseExt: SourceDatabase {
#[salsa::input]
fn compressed_file_text(&self, file_id: FileId) -> Arc<[u8]>;
#[salsa::lru]
fn file_text(&self, file_id: FileId) -> Arc<str>;
#[salsa::query_group(SourceRootDatabaseStorage)]
pub trait SourceRootDatabase: SourceDatabase {
/// Path to a file, relative to the root of its source root.
/// Source root of the file.
#[salsa::input]
@ -121,15 +128,7 @@ pub trait SourceDatabaseExt: SourceDatabase {
fn source_root_crates(&self, id: SourceRootId) -> Arc<[CrateId]>;
}
fn file_text(db: &dyn SourceDatabaseExt, file_id: FileId) -> Arc<str> {
let bytes = db.compressed_file_text(file_id);
let bytes =
lz4_flex::decompress_size_prepended(&bytes).expect("lz4 decompression should not fail");
let text = std::str::from_utf8(&bytes).expect("file contents should be valid UTF-8");
Arc::from(text)
}
pub trait SourceDatabaseExt2 {
pub trait SourceDatabaseFileInputExt {
fn set_file_text(&mut self, file_id: FileId, text: &str) {
self.set_file_text_with_durability(file_id, text, Durability::LOW);
}
@ -142,7 +141,7 @@ pub trait SourceDatabaseExt2 {
);
}
impl<Db: ?Sized + SourceDatabaseExt> SourceDatabaseExt2 for Db {
impl<Db: ?Sized + SourceRootDatabase> SourceDatabaseFileInputExt for Db {
fn set_file_text_with_durability(
&mut self,
file_id: FileId,
@ -159,7 +158,7 @@ impl<Db: ?Sized + SourceDatabaseExt> SourceDatabaseExt2 for Db {
}
}
fn source_root_crates(db: &dyn SourceDatabaseExt, id: SourceRootId) -> Arc<[CrateId]> {
fn source_root_crates(db: &dyn SourceRootDatabase, id: SourceRootId) -> Arc<[CrateId]> {
let graph = db.crate_graph();
let mut crates = graph
.iter()
@ -173,13 +172,12 @@ fn source_root_crates(db: &dyn SourceDatabaseExt, id: SourceRootId) -> Arc<[Crat
crates.into_iter().collect()
}
/// Silly workaround for cyclic deps between the traits
// FIXME: Would be nice to get rid of this somehow
/// Silly workaround for cyclic deps due to the SourceRootDatabase and SourceDatabase split
/// regarding FileLoader
pub struct FileLoaderDelegate<T>(pub T);
impl<T: SourceDatabaseExt> FileLoader for FileLoaderDelegate<&'_ T> {
fn file_text(&self, file_id: FileId) -> Arc<str> {
SourceDatabaseExt::file_text(self.0, file_id)
}
impl<T: SourceRootDatabase> FileLoader for FileLoaderDelegate<&'_ T> {
fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> {
// FIXME: this *somehow* should be platform agnostic...
let source_root = self.0.file_source_root(path.anchor);

View file

@ -1,7 +1,8 @@
[package]
name = "cfg"
version = "0.0.0"
description = "TBD"
repository.workspace = true
description = "Conditional compiling options, `cfg` attribute parser and evaluator for rust-analyzer."
authors.workspace = true
edition.workspace = true
@ -28,7 +29,7 @@ arbitrary = "1.3.2"
derive_arbitrary = "1.3.2"
# local deps
mbe.workspace = true
syntax-bridge.workspace = true
syntax.workspace = true
[lints]

View file

@ -108,6 +108,14 @@ impl<'a> IntoIterator for &'a CfgOptions {
}
}
impl FromIterator<CfgAtom> for CfgOptions {
fn from_iter<T: IntoIterator<Item = CfgAtom>>(iter: T) -> Self {
let mut options = CfgOptions::default();
options.extend(iter);
options
}
}
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct CfgDiff {
// Invariants: No duplicates, no atom that's both in `enable` and `disable`.

View file

@ -1,8 +1,11 @@
use arbitrary::{Arbitrary, Unstructured};
use expect_test::{expect, Expect};
use intern::Symbol;
use mbe::{syntax_node_to_token_tree, DocCommentDesugarMode, DummyTestSpanMap, DUMMY};
use syntax::{ast, AstNode, Edition};
use syntax_bridge::{
dummy_test_span_utils::{DummyTestSpanMap, DUMMY},
syntax_node_to_token_tree, DocCommentDesugarMode,
};
use crate::{CfgAtom, CfgExpr, CfgOptions, DnfExpr};

View file

@ -1,30 +0,0 @@
[package]
name = "flycheck"
version = "0.0.0"
description = "TBD"
authors.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
[lib]
doctest = false
[dependencies]
cargo_metadata.workspace = true
crossbeam-channel.workspace = true
tracing.workspace = true
rustc-hash.workspace = true
serde_json.workspace = true
serde.workspace = true
process-wrap.workspace = true
# local deps
paths.workspace = true
stdx.workspace = true
toolchain.workspace = true
project-model.workspace = true
[lints]
workspace = true

View file

@ -1,7 +1,8 @@
[package]
name = "hir-def"
version = "0.0.0"
description = "TBD"
repository.workspace = true
description = "RPC Api for the `proc-macro-srv` crate of rust-analyzer."
authors.workspace = true
edition.workspace = true
@ -52,7 +53,7 @@ expect-test.workspace = true
# local deps
test-utils.workspace = true
test-fixture.workspace = true
syntax-bridge.workspace = true
[features]
in-rust-tree = ["hir-expand/in-rust-tree"]

View file

@ -657,9 +657,9 @@ mod tests {
use triomphe::Arc;
use hir_expand::span_map::{RealSpanMap, SpanMap};
use mbe::{syntax_node_to_token_tree, DocCommentDesugarMode};
use span::FileId;
use syntax::{ast, AstNode, TextRange};
use syntax_bridge::{syntax_node_to_token_tree, DocCommentDesugarMode};
use crate::attr::{DocAtom, DocExpr};

View file

@ -118,6 +118,7 @@ pub enum BodyDiagnostic {
MacroError { node: InFile<AstPtr<ast::MacroCall>>, err: ExpandError },
UnresolvedMacroCall { node: InFile<AstPtr<ast::MacroCall>>, path: ModPath },
UnreachableLabel { node: InFile<AstPtr<ast::Lifetime>>, name: Name },
AwaitOutsideOfAsync { node: InFile<AstPtr<ast::AwaitExpr>>, location: String },
UndeclaredLabel { node: InFile<AstPtr<ast::Lifetime>>, name: Name },
}
@ -157,7 +158,7 @@ impl Body {
}),
)
});
is_async_fn = data.has_async_kw();
is_async_fn = data.is_async();
src.map(|it| it.body().map(ast::Expr::from))
}
DefWithBodyId::ConstId(c) => {

View file

@ -72,6 +72,7 @@ pub(super) fn lower(
is_lowering_coroutine: false,
label_ribs: Vec::new(),
current_binding_owner: None,
awaitable_context: None,
}
.collect(params, body, is_async_fn)
}
@ -100,6 +101,8 @@ struct ExprCollector<'a> {
// resolution
label_ribs: Vec<LabelRib>,
current_binding_owner: Option<ExprId>,
awaitable_context: Option<Awaitable>,
}
#[derive(Clone, Debug)]
@ -135,6 +138,11 @@ impl RibKind {
}
}
enum Awaitable {
Yes,
No(&'static str),
}
#[derive(Debug, Default)]
struct BindingList {
map: FxHashMap<Name, BindingId>,
@ -180,6 +188,18 @@ impl ExprCollector<'_> {
body: Option<ast::Expr>,
is_async_fn: bool,
) -> (Body, BodySourceMap) {
self.awaitable_context.replace(if is_async_fn {
Awaitable::Yes
} else {
match self.owner {
DefWithBodyId::FunctionId(..) => Awaitable::No("non-async function"),
DefWithBodyId::StaticId(..) => Awaitable::No("static"),
DefWithBodyId::ConstId(..) | DefWithBodyId::InTypeConstId(..) => {
Awaitable::No("constant")
}
DefWithBodyId::VariantId(..) => Awaitable::No("enum variant"),
}
});
if let Some((param_list, mut attr_enabled)) = param_list {
let mut params = vec![];
if let Some(self_param) =
@ -280,31 +300,40 @@ impl ExprCollector<'_> {
}
Some(ast::BlockModifier::Async(_)) => {
self.with_label_rib(RibKind::Closure, |this| {
this.collect_block_(e, |id, statements, tail| Expr::Async {
id,
statements,
tail,
this.with_awaitable_block(Awaitable::Yes, |this| {
this.collect_block_(e, |id, statements, tail| Expr::Async {
id,
statements,
tail,
})
})
})
}
Some(ast::BlockModifier::Const(_)) => {
self.with_label_rib(RibKind::Constant, |this| {
let (result_expr_id, prev_binding_owner) =
this.initialize_binding_owner(syntax_ptr);
let inner_expr = this.collect_block(e);
let it = this.db.intern_anonymous_const(ConstBlockLoc {
parent: this.owner,
root: inner_expr,
});
this.body.exprs[result_expr_id] = Expr::Const(it);
this.current_binding_owner = prev_binding_owner;
result_expr_id
this.with_awaitable_block(Awaitable::No("constant block"), |this| {
let (result_expr_id, prev_binding_owner) =
this.initialize_binding_owner(syntax_ptr);
let inner_expr = this.collect_block(e);
let it = this.db.intern_anonymous_const(ConstBlockLoc {
parent: this.owner,
root: inner_expr,
});
this.body.exprs[result_expr_id] = Expr::Const(it);
this.current_binding_owner = prev_binding_owner;
result_expr_id
})
})
}
// FIXME
Some(ast::BlockModifier::AsyncGen(_)) | Some(ast::BlockModifier::Gen(_)) | None => {
self.collect_block(e)
Some(ast::BlockModifier::AsyncGen(_)) => {
self.with_awaitable_block(Awaitable::Yes, |this| this.collect_block(e))
}
Some(ast::BlockModifier::Gen(_)) => self
.with_awaitable_block(Awaitable::No("non-async gen block"), |this| {
this.collect_block(e)
}),
None => self.collect_block(e),
},
ast::Expr::LoopExpr(e) => {
let label = e.label().map(|label| self.collect_label(label));
@ -469,6 +498,12 @@ impl ExprCollector<'_> {
}
ast::Expr::AwaitExpr(e) => {
let expr = self.collect_expr_opt(e.expr());
if let Awaitable::No(location) = self.is_lowering_awaitable_block() {
self.source_map.diagnostics.push(BodyDiagnostic::AwaitOutsideOfAsync {
node: InFile::new(self.expander.current_file_id(), AstPtr::new(&e)),
location: location.to_string(),
});
}
self.alloc_expr(Expr::Await { expr }, syntax_ptr)
}
ast::Expr::TryExpr(e) => self.collect_try_operator(syntax_ptr, e),
@ -527,7 +562,13 @@ impl ExprCollector<'_> {
let prev_is_lowering_coroutine = mem::take(&mut this.is_lowering_coroutine);
let prev_try_block_label = this.current_try_block_label.take();
let body = this.collect_expr_opt(e.body());
let awaitable = if e.async_token().is_some() {
Awaitable::Yes
} else {
Awaitable::No("non-async closure")
};
let body =
this.with_awaitable_block(awaitable, |this| this.collect_expr_opt(e.body()));
let closure_kind = if this.is_lowering_coroutine {
let movability = if e.static_token().is_some() {
@ -2082,6 +2123,21 @@ impl ExprCollector<'_> {
fn alloc_label_desugared(&mut self, label: Label) -> LabelId {
self.body.labels.alloc(label)
}
fn is_lowering_awaitable_block(&self) -> &Awaitable {
self.awaitable_context.as_ref().unwrap_or(&Awaitable::No("unknown"))
}
fn with_awaitable_block<T>(
&mut self,
awaitable: Awaitable,
f: impl FnOnce(&mut Self) -> T,
) -> T {
let orig = self.awaitable_context.replace(awaitable);
let res = f(self);
self.awaitable_context = orig;
res
}
}
fn comma_follows_token(t: Option<syntax::SyntaxToken>) -> bool {

View file

@ -94,6 +94,12 @@ impl FunctionData {
.filter(|it| !it.is_empty())
.map(Box::new);
let rustc_allow_incoherent_impl = attrs.by_key(&sym::rustc_allow_incoherent_impl).exists();
if flags.contains(FnFlags::HAS_UNSAFE_KW)
&& !crate_graph[krate].edition.at_least_2024()
&& attrs.by_key(&sym::rustc_deprecated_safe_2024).exists()
{
flags.remove(FnFlags::HAS_UNSAFE_KW);
}
Arc::new(FunctionData {
name: func.name.clone(),
@ -126,19 +132,19 @@ impl FunctionData {
self.flags.contains(FnFlags::HAS_SELF_PARAM)
}
pub fn has_default_kw(&self) -> bool {
pub fn is_default(&self) -> bool {
self.flags.contains(FnFlags::HAS_DEFAULT_KW)
}
pub fn has_const_kw(&self) -> bool {
pub fn is_const(&self) -> bool {
self.flags.contains(FnFlags::HAS_CONST_KW)
}
pub fn has_async_kw(&self) -> bool {
pub fn is_async(&self) -> bool {
self.flags.contains(FnFlags::HAS_ASYNC_KW)
}
pub fn has_unsafe_kw(&self) -> bool {
pub fn is_unsafe(&self) -> bool {
self.flags.contains(FnFlags::HAS_UNSAFE_KW)
}

View file

@ -160,7 +160,7 @@ pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast<dyn ExpandDataba
fn const_data(&self, konst: ConstId) -> Arc<ConstData>;
#[salsa::invoke(StaticData::static_data_query)]
fn static_data(&self, konst: StaticId) -> Arc<StaticData>;
fn static_data(&self, statik: StaticId) -> Arc<StaticData>;
#[salsa::invoke(Macro2Data::macro2_data_query)]
fn macro2_data(&self, makro: Macro2Id) -> Arc<Macro2Data>;
@ -240,14 +240,14 @@ pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast<dyn ExpandDataba
fn crate_supports_no_std(&self, crate_id: CrateId) -> bool;
fn include_macro_invoc(&self, crate_id: CrateId) -> Vec<(MacroCallId, EditionedFileId)>;
fn include_macro_invoc(&self, crate_id: CrateId) -> Arc<[(MacroCallId, EditionedFileId)]>;
}
// return: macro call id and include file id
fn include_macro_invoc(
db: &dyn DefDatabase,
krate: CrateId,
) -> Vec<(MacroCallId, EditionedFileId)> {
) -> Arc<[(MacroCallId, EditionedFileId)]> {
db.crate_def_map(krate)
.modules
.values()

View file

@ -50,13 +50,13 @@ pub fn find_path(
prefix: prefix_kind,
cfg,
ignore_local_imports,
is_std_item: db.crate_graph()[item_module.krate()].origin.is_lang(),
from,
from_def_map: &from.def_map(db),
fuel: Cell::new(FIND_PATH_FUEL),
},
item,
MAX_PATH_LEN,
db.crate_graph()[item_module.krate()].origin.is_lang(),
)
}
@ -98,20 +98,16 @@ struct FindPathCtx<'db> {
prefix: PrefixKind,
cfg: ImportPathConfig,
ignore_local_imports: bool,
is_std_item: bool,
from: ModuleId,
from_def_map: &'db DefMap,
fuel: Cell<usize>,
}
/// Attempts to find a path to refer to the given `item` visible from the `from` ModuleId
fn find_path_inner(
ctx: &FindPathCtx<'_>,
item: ItemInNs,
max_len: usize,
is_std_item: bool,
) -> Option<ModPath> {
fn find_path_inner(ctx: &FindPathCtx<'_>, item: ItemInNs, max_len: usize) -> Option<ModPath> {
// - if the item is a module, jump straight to module search
if !is_std_item {
if !ctx.is_std_item {
if let ItemInNs::Types(ModuleDefId::ModuleId(module_id)) = item {
return find_path_for_module(ctx, &mut FxHashSet::default(), module_id, true, max_len)
.map(|choice| choice.path);
@ -138,12 +134,9 @@ fn find_path_inner(
if let Some(ModuleDefId::EnumVariantId(variant)) = item.as_module_def_id() {
// - if the item is an enum variant, refer to it via the enum
if let Some(mut path) = find_path_inner(
ctx,
ItemInNs::Types(variant.lookup(ctx.db).parent.into()),
max_len,
is_std_item,
) {
if let Some(mut path) =
find_path_inner(ctx, ItemInNs::Types(variant.lookup(ctx.db).parent.into()), max_len)
{
path.push_segment(ctx.db.enum_variant_data(variant).name.clone());
return Some(path);
}
@ -152,16 +145,6 @@ fn find_path_inner(
// variant somewhere
}
if is_std_item {
// The item we are searching for comes from the sysroot libraries, so skip prefer looking in
// the sysroot libraries directly.
// We do need to fallback as the item in question could be re-exported by another crate
// while not being a transitive dependency of the current crate.
if let Some(choice) = find_in_sysroot(ctx, &mut FxHashSet::default(), item, max_len) {
return Some(choice.path);
}
}
let mut best_choice = None;
calculate_best_path(ctx, &mut FxHashSet::default(), item, max_len, &mut best_choice);
best_choice.map(|choice| choice.path)
@ -366,6 +349,12 @@ fn calculate_best_path(
// Item was defined in the same crate that wants to import it. It cannot be found in any
// dependency in this case.
calculate_best_path_local(ctx, visited_modules, item, max_len, best_choice)
} else if ctx.is_std_item {
// The item we are searching for comes from the sysroot libraries, so skip prefer looking in
// the sysroot libraries directly.
// We do need to fallback as the item in question could be re-exported by another crate
// while not being a transitive dependency of the current crate.
find_in_sysroot(ctx, visited_modules, item, max_len, best_choice)
} else {
// Item was defined in some upstream crate. This means that it must be exported from one,
// too (unless we can't name it at all). It could *also* be (re)exported by the same crate
@ -382,10 +371,10 @@ fn find_in_sysroot(
visited_modules: &mut FxHashSet<(ItemInNs, ModuleId)>,
item: ItemInNs,
max_len: usize,
) -> Option<Choice> {
best_choice: &mut Option<Choice>,
) {
let crate_graph = ctx.db.crate_graph();
let dependencies = &crate_graph[ctx.from.krate].dependencies;
let mut best_choice = None;
let mut search = |lang, best_choice: &mut _| {
if let Some(dep) = dependencies.iter().filter(|it| it.is_sysroot()).find(|dep| {
match crate_graph[dep.crate_id].origin {
@ -397,29 +386,31 @@ fn find_in_sysroot(
}
};
if ctx.cfg.prefer_no_std {
search(LangCrateOrigin::Core, &mut best_choice);
search(LangCrateOrigin::Core, best_choice);
if matches!(best_choice, Some(Choice { stability: Stable, .. })) {
return best_choice;
return;
}
search(LangCrateOrigin::Std, &mut best_choice);
search(LangCrateOrigin::Std, best_choice);
if matches!(best_choice, Some(Choice { stability: Stable, .. })) {
return best_choice;
return;
}
} else {
search(LangCrateOrigin::Std, &mut best_choice);
search(LangCrateOrigin::Std, best_choice);
if matches!(best_choice, Some(Choice { stability: Stable, .. })) {
return best_choice;
return;
}
search(LangCrateOrigin::Core, &mut best_choice);
search(LangCrateOrigin::Core, best_choice);
if matches!(best_choice, Some(Choice { stability: Stable, .. })) {
return best_choice;
return;
}
}
let mut best_choice = None;
dependencies.iter().filter(|it| it.is_sysroot()).for_each(|dep| {
find_in_dep(ctx, visited_modules, item, max_len, &mut best_choice, dep.crate_id);
});
best_choice
dependencies
.iter()
.filter(|it| it.is_sysroot())
.chain(dependencies.iter().filter(|it| !it.is_sysroot()))
.for_each(|dep| {
find_in_dep(ctx, visited_modules, item, max_len, best_choice, dep.crate_id);
});
}
fn find_in_dep(
@ -491,6 +482,7 @@ fn calculate_best_path_local(
);
}
#[derive(Debug)]
struct Choice {
path: ModPath,
/// The length in characters of the path
@ -676,6 +668,7 @@ mod tests {
path: &str,
prefer_prelude: bool,
prefer_absolute: bool,
prefer_no_std: bool,
expect: Expect,
) {
let (db, pos) = TestDB::with_position(ra_fixture);
@ -717,7 +710,7 @@ mod tests {
module,
prefix,
ignore_local_imports,
ImportPathConfig { prefer_no_std: false, prefer_prelude, prefer_absolute },
ImportPathConfig { prefer_no_std, prefer_prelude, prefer_absolute },
);
format_to!(
res,
@ -732,15 +725,19 @@ mod tests {
}
fn check_found_path(ra_fixture: &str, path: &str, expect: Expect) {
check_found_path_(ra_fixture, path, false, false, expect);
check_found_path_(ra_fixture, path, false, false, false, expect);
}
fn check_found_path_prelude(ra_fixture: &str, path: &str, expect: Expect) {
check_found_path_(ra_fixture, path, true, false, expect);
check_found_path_(ra_fixture, path, true, false, false, expect);
}
fn check_found_path_absolute(ra_fixture: &str, path: &str, expect: Expect) {
check_found_path_(ra_fixture, path, false, true, expect);
check_found_path_(ra_fixture, path, false, true, false, expect);
}
fn check_found_path_prefer_no_std(ra_fixture: &str, path: &str, expect: Expect) {
check_found_path_(ra_fixture, path, false, false, true, expect);
}
#[test]
@ -1361,9 +1358,66 @@ pub mod sync {
"#]],
);
}
#[test]
fn prefer_core_paths_over_std_for_mod_reexport() {
check_found_path_prefer_no_std(
r#"
//- /main.rs crate:main deps:core,std
$0
//- /stdlib.rs crate:std deps:core
pub use core::pin;
//- /corelib.rs crate:core
pub mod pin {
pub struct Pin;
}
"#,
"std::pin::Pin",
expect![[r#"
Plain (imports ): core::pin::Pin
Plain (imports ): core::pin::Pin
ByCrate(imports ): core::pin::Pin
ByCrate(imports ): core::pin::Pin
BySelf (imports ): core::pin::Pin
BySelf (imports ): core::pin::Pin
"#]],
);
}
#[test]
fn prefer_core_paths_over_std() {
check_found_path_prefer_no_std(
r#"
//- /main.rs crate:main deps:core,std
$0
//- /std.rs crate:std deps:core
pub mod fmt {
pub use core::fmt::Error;
}
//- /zzz.rs crate:core
pub mod fmt {
pub struct Error;
}
"#,
"core::fmt::Error",
expect![[r#"
Plain (imports ): core::fmt::Error
Plain (imports ): core::fmt::Error
ByCrate(imports ): core::fmt::Error
ByCrate(imports ): core::fmt::Error
BySelf (imports ): core::fmt::Error
BySelf (imports ): core::fmt::Error
"#]],
);
check_found_path(
r#"
//- /main.rs crate:main deps:core,std
@ -1878,10 +1932,9 @@ pub mod ops {
#[test]
fn respect_unstable_modules() {
check_found_path(
check_found_path_prefer_no_std(
r#"
//- /main.rs crate:main deps:std,core
#![no_std]
extern crate std;
$0
//- /longer.rs crate:std deps:core

View file

@ -105,7 +105,7 @@ use crate::{
type FxIndexMap<K, V> =
indexmap::IndexMap<K, V, std::hash::BuildHasherDefault<rustc_hash::FxHasher>>;
/// A wrapper around two booleans, [`ImportPathConfig::prefer_no_std`] and [`ImportPathConfig::prefer_prelude`].
/// A wrapper around three booleans
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
pub struct ImportPathConfig {
/// If true, prefer to unconditionally use imports of the `core` and `alloc` crate

View file

@ -1201,7 +1201,6 @@ macro_rules! m {
#[test]
fn test_meta_doc_comments() {
cov_mark::check!(test_meta_doc_comments);
check(
r#"
macro_rules! m {

View file

@ -317,9 +317,9 @@ impl ProcMacroExpander for IdentityWhenValidProcMacroExpander {
_: Span,
_: Span,
) -> Result<Subtree, ProcMacroExpansionError> {
let (parse, _) = ::mbe::token_tree_to_syntax_node(
let (parse, _) = syntax_bridge::token_tree_to_syntax_node(
subtree,
::mbe::TopEntryPoint::MacroItems,
syntax_bridge::TopEntryPoint::MacroItems,
span::Edition::CURRENT,
);
if parse.errors().is_empty() {

View file

@ -56,7 +56,6 @@ use crate::{
};
static GLOB_RECURSION_LIMIT: Limit = Limit::new(100);
static EXPANSION_DEPTH_LIMIT: Limit = Limit::new(128);
static FIXED_POINT_LIMIT: Limit = Limit::new(8192);
pub(super) fn collect_defs(db: &dyn DefDatabase, def_map: DefMap, tree_id: TreeId) -> DefMap {
@ -1440,7 +1439,14 @@ impl DefCollector<'_> {
depth: usize,
container: ItemContainerId,
) {
if EXPANSION_DEPTH_LIMIT.check(depth).is_err() {
let recursion_limit = self.def_map.recursion_limit() as usize;
let recursion_limit = Limit::new(if cfg!(test) {
// Without this, `body::tests::your_stack_belongs_to_me` stack-overflows in debug
std::cmp::min(32, recursion_limit)
} else {
recursion_limit
});
if recursion_limit.check(depth).is_err() {
cov_mark::hit!(macro_expansion_overflow);
tracing::warn!("macro expansion is too deep");
return;
@ -2003,7 +2009,7 @@ impl ModCollector<'_, '_> {
Err(cfg) => {
self.emit_unconfigured_diagnostic(
self.tree_id,
AttrOwner::TopLevel,
AttrOwner::ModItem(module_id.into()),
&cfg,
);
}

View file

@ -1,4 +1,4 @@
use base_db::{SourceDatabase, SourceDatabaseExt2 as _};
use base_db::{SourceDatabase, SourceDatabaseFileInputExt as _};
use test_fixture::WithFixture;
use crate::{db::DefDatabase, nameres::tests::TestDB, AdtId, ModuleDefId};

View file

@ -194,6 +194,11 @@ pub(super) fn lower_generic_args(
match generic_arg {
ast::GenericArg::TypeArg(type_arg) => {
let type_ref = TypeRef::from_ast_opt(lower_ctx, type_arg.ty());
type_ref.walk(&mut |tr| {
if let TypeRef::ImplTrait(bounds) = tr {
lower_ctx.update_impl_traits_bounds(bounds.clone());
}
});
args.push(GenericArg::Type(type_ref));
}
ast::GenericArg::AssocTypeArg(assoc_type_arg) => {

View file

@ -19,7 +19,7 @@ use crate::{
};
#[salsa::database(
base_db::SourceDatabaseExtStorage,
base_db::SourceRootDatabaseStorage,
base_db::SourceDatabaseStorage,
hir_expand::db::ExpandDatabaseStorage,
crate::db::InternDatabaseStorage,
@ -69,9 +69,6 @@ impl fmt::Debug for TestDB {
impl panic::RefUnwindSafe for TestDB {}
impl FileLoader for TestDB {
fn file_text(&self, file_id: FileId) -> Arc<str> {
FileLoaderDelegate(self).file_text(file_id)
}
fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> {
FileLoaderDelegate(self).resolve_path(path)
}

View file

@ -1,7 +1,8 @@
[package]
name = "hir-expand"
version = "0.0.0"
description = "TBD"
repository.workspace = true
description = "Macro expansion for rust-analyzer."
authors.workspace = true
edition.workspace = true
@ -33,6 +34,7 @@ mbe.workspace = true
limit.workspace = true
span.workspace = true
parser.workspace = true
syntax-bridge.workspace = true
[dev-dependencies]
expect-test = "1.4.0"

View file

@ -6,14 +6,12 @@ use cfg::CfgExpr;
use either::Either;
use intern::{sym, Interned, Symbol};
use mbe::{
desugar_doc_comment_text, syntax_node_to_token_tree, DelimiterKind, DocCommentDesugarMode,
Punct,
};
use mbe::{DelimiterKind, Punct};
use smallvec::{smallvec, SmallVec};
use span::{Span, SyntaxContextId};
use syntax::unescape;
use syntax::{ast, match_ast, AstNode, AstToken, SyntaxNode};
use syntax_bridge::{desugar_doc_comment_text, syntax_node_to_token_tree, DocCommentDesugarMode};
use triomphe::ThinArc;
use crate::name::Name;

View file

@ -2,10 +2,10 @@
use intern::sym;
use itertools::izip;
use mbe::DocCommentDesugarMode;
use rustc_hash::FxHashSet;
use span::{MacroCallId, Span};
use stdx::never;
use syntax_bridge::DocCommentDesugarMode;
use tracing::debug;
use crate::{
@ -209,9 +209,9 @@ struct BasicAdtInfo {
}
fn parse_adt(tt: &tt::Subtree, call_site: Span) -> Result<BasicAdtInfo, ExpandError> {
let (parsed, tm) = &mbe::token_tree_to_syntax_node(
let (parsed, tm) = &syntax_bridge::token_tree_to_syntax_node(
tt,
mbe::TopEntryPoint::MacroItems,
syntax_bridge::TopEntryPoint::MacroItems,
parser::Edition::CURRENT_FIXME,
);
let macro_items = ast::MacroItems::cast(parsed.syntax_node())
@ -268,7 +268,7 @@ fn parse_adt(tt: &tt::Subtree, call_site: Span) -> Result<BasicAdtInfo, ExpandEr
match this {
Some(it) => {
param_type_set.insert(it.as_name());
mbe::syntax_node_to_token_tree(
syntax_bridge::syntax_node_to_token_tree(
it.syntax(),
tm,
call_site,
@ -282,7 +282,7 @@ fn parse_adt(tt: &tt::Subtree, call_site: Span) -> Result<BasicAdtInfo, ExpandEr
};
let bounds = match &param {
ast::TypeOrConstParam::Type(it) => it.type_bound_list().map(|it| {
mbe::syntax_node_to_token_tree(
syntax_bridge::syntax_node_to_token_tree(
it.syntax(),
tm,
call_site,
@ -295,7 +295,7 @@ fn parse_adt(tt: &tt::Subtree, call_site: Span) -> Result<BasicAdtInfo, ExpandEr
let ty = param
.ty()
.map(|ty| {
mbe::syntax_node_to_token_tree(
syntax_bridge::syntax_node_to_token_tree(
ty.syntax(),
tm,
call_site,
@ -316,7 +316,7 @@ fn parse_adt(tt: &tt::Subtree, call_site: Span) -> Result<BasicAdtInfo, ExpandEr
let where_clause = if let Some(w) = where_clause {
w.predicates()
.map(|it| {
mbe::syntax_node_to_token_tree(
syntax_bridge::syntax_node_to_token_tree(
it.syntax(),
tm,
call_site,
@ -353,7 +353,7 @@ fn parse_adt(tt: &tt::Subtree, call_site: Span) -> Result<BasicAdtInfo, ExpandEr
param_type_set.contains(&name).then_some(p)
})
.map(|it| {
mbe::syntax_node_to_token_tree(
syntax_bridge::syntax_node_to_token_tree(
it.syntax(),
tm,
call_site,

View file

@ -4,13 +4,14 @@ use base_db::AnchoredPath;
use cfg::CfgExpr;
use either::Either;
use intern::{sym, Symbol};
use mbe::{parse_exprs_with_sep, parse_to_token_tree, DelimiterKind};
use mbe::{expect_fragment, DelimiterKind};
use span::{Edition, EditionedFileId, Span, SpanAnchor, SyntaxContextId, ROOT_ERASED_FILE_AST_ID};
use stdx::format_to;
use syntax::{
format_smolstr,
unescape::{unescape_byte, unescape_char, unescape_unicode, Mode},
};
use syntax_bridge::parse_to_token_tree;
use crate::{
builtin::quote::{dollar_crate, quote},
@ -228,20 +229,22 @@ fn assert_expand(
span: Span,
) -> ExpandResult<tt::Subtree> {
let call_site_span = span_with_call_site_ctxt(db, span, id);
let args = parse_exprs_with_sep(tt, ',', call_site_span, Edition::CURRENT_FIXME);
let mut iter = ::tt::iter::TtIter::new(tt);
let cond = expect_fragment(
&mut iter,
parser::PrefixEntryPoint::Expr,
db.crate_graph()[id.lookup(db).krate].edition,
tt::DelimSpan { open: tt.delimiter.open, close: tt.delimiter.close },
);
_ = iter.expect_char(',');
let rest = iter.as_slice();
let dollar_crate = dollar_crate(span);
let expanded = match &*args {
[cond, panic_args @ ..] => {
let comma = tt::Subtree {
delimiter: tt::Delimiter::invisible_spanned(call_site_span),
token_trees: Box::new([tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct {
char: ',',
spacing: tt::Spacing::Alone,
span: call_site_span,
}))]),
};
let cond = cond.clone();
let panic_args = itertools::Itertools::intersperse(panic_args.iter().cloned(), comma);
let expanded = match cond.value {
Some(cond) => {
let panic_args = rest.iter().cloned();
let mac = if use_panic_2021(db, span) {
quote! {call_site_span => #dollar_crate::panic::panic_2021!(##panic_args) }
} else {
@ -253,10 +256,13 @@ fn assert_expand(
}
}}
}
[] => quote! {call_site_span =>{}},
None => quote! {call_site_span =>{}},
};
ExpandResult::ok(expanded)
match cond.err {
Some(err) => ExpandResult::new(expanded, err.into()),
None => ExpandResult::ok(expanded),
}
}
fn file_expand(

View file

@ -1,7 +1,7 @@
//! Defines a unit of change that can applied to the database to get the next
//! state. Changes are transactional.
use base_db::{
salsa::Durability, CrateGraph, CrateId, FileChange, SourceDatabaseExt, SourceRoot,
salsa::Durability, CrateGraph, CrateId, FileChange, SourceRoot, SourceRootDatabase,
TargetLayoutLoadResult, Version,
};
use la_arena::RawIdx;
@ -23,7 +23,7 @@ impl ChangeWithProcMacros {
Self::default()
}
pub fn apply(self, db: &mut (impl ExpandDatabase + SourceDatabaseExt)) {
pub fn apply(self, db: &mut (impl ExpandDatabase + SourceRootDatabase)) {
self.source_change.apply(db);
if let Some(proc_macros) = self.proc_macros {
db.set_proc_macros_with_durability(Arc::new(proc_macros), Durability::HIGH);

View file

@ -3,10 +3,11 @@
use base_db::{salsa, CrateId, SourceDatabase};
use either::Either;
use limit::Limit;
use mbe::{syntax_node_to_token_tree, DocCommentDesugarMode, MatchedArmIndex};
use mbe::MatchedArmIndex;
use rustc_hash::FxHashSet;
use span::{AstIdMap, EditionedFileId, Span, SyntaxContextData, SyntaxContextId};
use syntax::{ast, AstNode, Parse, SyntaxElement, SyntaxError, SyntaxNode, SyntaxToken, T};
use syntax_bridge::{syntax_node_to_token_tree, DocCommentDesugarMode};
use triomphe::Arc;
use crate::{
@ -165,7 +166,7 @@ pub fn expand_speculative(
// Build the subtree and token mapping for the speculative args
let (mut tt, undo_info) = match loc.kind {
MacroCallKind::FnLike { .. } => (
mbe::syntax_node_to_token_tree(
syntax_bridge::syntax_node_to_token_tree(
speculative_args,
span_map,
span,
@ -178,7 +179,7 @@ pub fn expand_speculative(
SyntaxFixupUndoInfo::NONE,
),
MacroCallKind::Attr { .. } if loc.def.is_attribute_derive() => (
mbe::syntax_node_to_token_tree(
syntax_bridge::syntax_node_to_token_tree(
speculative_args,
span_map,
span,
@ -213,7 +214,7 @@ pub fn expand_speculative(
fixups.remove.extend(censor_cfg);
(
mbe::syntax_node_to_token_tree_modified(
syntax_bridge::syntax_node_to_token_tree_modified(
speculative_args,
span_map,
fixups.append,
@ -459,7 +460,7 @@ fn macro_arg(db: &dyn ExpandDatabase, id: MacroCallId) -> MacroArgResult {
return dummy_tt(kind);
}
let mut tt = mbe::syntax_node_to_token_tree(
let mut tt = syntax_bridge::syntax_node_to_token_tree(
tt.syntax(),
map.as_ref(),
span,
@ -515,7 +516,7 @@ fn macro_arg(db: &dyn ExpandDatabase, id: MacroCallId) -> MacroArgResult {
fixups.remove.extend(censor_cfg);
(
mbe::syntax_node_to_token_tree_modified(
syntax_bridge::syntax_node_to_token_tree_modified(
syntax,
map,
fixups.append,
@ -720,13 +721,13 @@ fn token_tree_to_syntax_node(
edition: parser::Edition,
) -> (Parse<SyntaxNode>, ExpansionSpanMap) {
let entry_point = match expand_to {
ExpandTo::Statements => mbe::TopEntryPoint::MacroStmts,
ExpandTo::Items => mbe::TopEntryPoint::MacroItems,
ExpandTo::Pattern => mbe::TopEntryPoint::Pattern,
ExpandTo::Type => mbe::TopEntryPoint::Type,
ExpandTo::Expr => mbe::TopEntryPoint::Expr,
ExpandTo::Statements => syntax_bridge::TopEntryPoint::MacroStmts,
ExpandTo::Items => syntax_bridge::TopEntryPoint::MacroItems,
ExpandTo::Pattern => syntax_bridge::TopEntryPoint::Pattern,
ExpandTo::Type => syntax_bridge::TopEntryPoint::Type,
ExpandTo::Expr => syntax_bridge::TopEntryPoint::Expr,
};
mbe::token_tree_to_syntax_node(tt, entry_point, edition)
syntax_bridge::token_tree_to_syntax_node(tt, entry_point, edition)
}
fn check_tt_count(tt: &tt::Subtree) -> Result<(), ExpandResult<()>> {

View file

@ -2,10 +2,10 @@
use base_db::CrateId;
use intern::sym;
use mbe::DocCommentDesugarMode;
use span::{Edition, MacroCallId, Span, SyntaxContextId};
use stdx::TupleExt;
use syntax::{ast, AstNode};
use syntax_bridge::DocCommentDesugarMode;
use triomphe::Arc;
use crate::{
@ -112,7 +112,7 @@ impl DeclarativeMacroExpander {
ast::Macro::MacroRules(macro_rules) => (
match macro_rules.token_tree() {
Some(arg) => {
let tt = mbe::syntax_node_to_token_tree(
let tt = syntax_bridge::syntax_node_to_token_tree(
arg.syntax(),
map.as_ref(),
map.span_for_range(
@ -135,14 +135,14 @@ impl DeclarativeMacroExpander {
let span =
map.span_for_range(macro_def.macro_token().unwrap().text_range());
let args = macro_def.args().map(|args| {
mbe::syntax_node_to_token_tree(
syntax_bridge::syntax_node_to_token_tree(
args.syntax(),
map.as_ref(),
span,
DocCommentDesugarMode::Mbe,
)
});
let body = mbe::syntax_node_to_token_tree(
let body = syntax_bridge::syntax_node_to_token_tree(
body.syntax(),
map.as_ref(),
span,

View file

@ -19,9 +19,9 @@
//!
//! See the full discussion : <https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/Eager.20expansion.20of.20built-in.20macros>
use base_db::CrateId;
use mbe::DocCommentDesugarMode;
use span::SyntaxContextId;
use syntax::{ted, Parse, SyntaxElement, SyntaxNode, TextSize, WalkEvent};
use syntax_bridge::DocCommentDesugarMode;
use triomphe::Arc;
use crate::{
@ -82,7 +82,7 @@ pub fn expand_eager_macro_input(
return ExpandResult { value: None, err };
};
let mut subtree = mbe::syntax_node_to_token_tree(
let mut subtree = syntax_bridge::syntax_node_to_token_tree(
&expanded_eager_input,
arg_map,
span,

View file

@ -2,7 +2,6 @@
//! fix up syntax errors in the code we're passing to them.
use intern::sym;
use mbe::DocCommentDesugarMode;
use rustc_hash::{FxHashMap, FxHashSet};
use smallvec::SmallVec;
use span::{
@ -14,6 +13,7 @@ use syntax::{
ast::{self, AstNode, HasLoopBody},
match_ast, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, TextSize,
};
use syntax_bridge::DocCommentDesugarMode;
use triomphe::Arc;
use tt::Spacing;
@ -76,7 +76,8 @@ pub(crate) fn fixup_syntax(
if can_handle_error(&node) && has_error_to_handle(&node) {
remove.insert(node.clone().into());
// the node contains an error node, we have to completely replace it by something valid
let original_tree = mbe::syntax_node_to_token_tree(&node, span_map, call_site, mode);
let original_tree =
syntax_bridge::syntax_node_to_token_tree(&node, span_map, call_site, mode);
let idx = original.len() as u32;
original.push(original_tree);
let span = span_map.span_for_range(node_range);
@ -434,9 +435,9 @@ fn reverse_fixups_(tt: &mut Subtree, undo_info: &[Subtree]) {
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};
use mbe::DocCommentDesugarMode;
use span::{Edition, EditionedFileId, FileId};
use syntax::TextRange;
use syntax_bridge::DocCommentDesugarMode;
use triomphe::Arc;
use crate::{
@ -483,7 +484,7 @@ mod tests {
span_map.span_for_range(TextRange::empty(0.into())),
DocCommentDesugarMode::Mbe,
);
let mut tt = mbe::syntax_node_to_token_tree_modified(
let mut tt = syntax_bridge::syntax_node_to_token_tree_modified(
&parsed.syntax_node(),
span_map.as_ref(),
fixups.append,
@ -498,9 +499,9 @@ mod tests {
expect.assert_eq(&actual);
// the fixed-up tree should be syntactically valid
let (parse, _) = mbe::token_tree_to_syntax_node(
let (parse, _) = syntax_bridge::token_tree_to_syntax_node(
&tt,
::mbe::TopEntryPoint::MacroItems,
syntax_bridge::TopEntryPoint::MacroItems,
parser::Edition::CURRENT,
);
assert!(
@ -513,7 +514,7 @@ mod tests {
// the fixed-up + reversed version should be equivalent to the original input
// modulo token IDs and `Punct`s' spacing.
let original_as_tt = mbe::syntax_node_to_token_tree(
let original_as_tt = syntax_bridge::syntax_node_to_token_tree(
&parsed.syntax_node(),
span_map.as_ref(),
span_map.span_for_range(TextRange::empty(0.into())),

View file

@ -176,7 +176,12 @@ impl ExpandErrorKind {
&ExpandErrorKind::MissingProcMacroExpander(def_crate) => {
match db.proc_macros().get_error_for_crate(def_crate) {
Some((e, hard_err)) => (e.to_owned(), hard_err),
None => ("missing expander".to_owned(), true),
None => (
format!(
"internal error: proc-macro map is missing error entry for crate {def_crate:?}"
),
true,
),
}
}
ExpandErrorKind::MacroDefinition => {

View file

@ -28,13 +28,13 @@ pub enum SpanMapRef<'a> {
RealSpanMap(&'a RealSpanMap),
}
impl mbe::SpanMapper<Span> for SpanMap {
impl syntax_bridge::SpanMapper<Span> for SpanMap {
fn span_for(&self, range: TextRange) -> Span {
self.span_for_range(range)
}
}
impl mbe::SpanMapper<Span> for SpanMapRef<'_> {
impl syntax_bridge::SpanMapper<Span> for SpanMapRef<'_> {
fn span_for(&self, range: TextRange) -> Span {
self.span_for_range(range)
}

View file

@ -1,7 +1,8 @@
[package]
name = "hir-ty"
version = "0.0.0"
description = "TBD"
repository.workspace = true
description = "The type system for rust-analyzer."
authors.workspace = true
edition.workspace = true

View file

@ -275,7 +275,7 @@ impl chalk_solve::RustIrDatabase<Interner> for ChalkContext<'_> {
};
chalk_ir::Binders::new(binders, bound)
}
crate::ImplTraitId::AssociatedTypeImplTrait(alias, idx) => {
crate::ImplTraitId::TypeAliasImplTrait(alias, idx) => {
let datas = self
.db
.type_alias_impl_traits(alias)

Some files were not shown because too many files have changed in this diff Show more