From 17ee25d77550b771200d73533e3bcb2a9959122d Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Tue, 8 Nov 2022 12:27:49 +0100 Subject: [PATCH 01/19] add the build.reuse config option to choose the reuse binary --- config.toml.example | 7 +++++++ src/bootstrap/config.rs | 3 +++ src/bootstrap/sanity.rs | 7 +++++++ 3 files changed, 17 insertions(+) diff --git a/config.toml.example b/config.toml.example index c94a27b12a3a7..ffc6a330b9f60 100644 --- a/config.toml.example +++ b/config.toml.example @@ -255,6 +255,13 @@ changelog-seen = 2 # Defaults to the Python interpreter used to execute x.py #python = "python" +# The path to the REUSE executable to use. REUSE will be used to gather +# the licensing information of the codebase. +# +# If this is omitted, the cached licensing information present in the source +# tarball will have to be present. +#reuse = "reuse" + # Force Cargo to check that Cargo.lock describes the precise dependency # set that all the Cargo.toml files create, instead of updating it. #locked-deps = false diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index af004aa509854..34b631c2bcac8 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -212,6 +212,7 @@ pub struct Config { pub npm: Option, pub gdb: Option, pub python: Option, + pub reuse: Option, pub cargo_native_static: bool, pub configure_args: Vec, @@ -610,6 +611,7 @@ define_config! { nodejs: Option = "nodejs", npm: Option = "npm", python: Option = "python", + reuse: Option = "reuse", locked_deps: Option = "locked-deps", vendor: Option = "vendor", full_bootstrap: Option = "full-bootstrap", @@ -1003,6 +1005,7 @@ impl Config { config.npm = build.npm.map(PathBuf::from); config.gdb = build.gdb.map(PathBuf::from); config.python = build.python.map(PathBuf::from); + config.reuse = build.reuse.map(PathBuf::from); config.submodules = build.submodules; set(&mut config.low_priority, build.low_priority); set(&mut config.compiler_docs, build.compiler_docs); diff --git a/src/bootstrap/sanity.rs b/src/bootstrap/sanity.rs index 631d42acb93fc..8a40b0f64f4b6 100644 --- a/src/bootstrap/sanity.rs +++ b/src/bootstrap/sanity.rs @@ -140,6 +140,13 @@ than building it. .map(|p| cmd_finder.must_have(p)) .or_else(|| cmd_finder.maybe_have("gdb")); + build.config.reuse = build + .config + .reuse + .take() + .map(|p| cmd_finder.must_have(p)) + .or_else(|| cmd_finder.maybe_have("reuse")); + // We're gonna build some custom C code here and there, host triples // also build some C++ shims for LLVM so we need a C++ compiler. for target in &build.targets { From 13efb20846dc3b3916e44c67ebd4141a39c54d9b Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Tue, 15 Nov 2022 10:19:17 +0100 Subject: [PATCH 02/19] add tool to collect license metadata from REUSE --- Cargo.lock | 68 ++++ Cargo.toml | 1 + src/bootstrap/builder.rs | 1 + src/bootstrap/run.rs | 33 ++ src/bootstrap/tool.rs | 1 + src/tools/collect-license-metadata/Cargo.toml | 10 + .../collect-license-metadata/src/licenses.rs | 37 +++ .../collect-license-metadata/src/main.rs | 30 ++ .../collect-license-metadata/src/path_tree.rs | 292 ++++++++++++++++++ .../collect-license-metadata/src/reuse.rs | 49 +++ 10 files changed, 522 insertions(+) create mode 100644 src/tools/collect-license-metadata/Cargo.toml create mode 100644 src/tools/collect-license-metadata/src/licenses.rs create mode 100644 src/tools/collect-license-metadata/src/main.rs create mode 100644 src/tools/collect-license-metadata/src/path_tree.rs create mode 100644 src/tools/collect-license-metadata/src/reuse.rs diff --git a/Cargo.lock b/Cargo.lock index 9f64aa44314db..eadaf721f0210 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -563,6 +563,7 @@ dependencies = [ "libc", "num-integer", "num-traits", + "serde", "time", "winapi", ] @@ -712,6 +713,16 @@ dependencies = [ "rustc-semver", ] +[[package]] +name = "collect-license-metadata" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde", + "serde_json", + "spdx-rs", +] + [[package]] name = "color-eyre" version = "0.6.2" @@ -4628,6 +4639,35 @@ dependencies = [ "winapi", ] +[[package]] +name = "spdx-expression" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d7ac03c67c572d85049d6db815e20a4a19b41b3d5cca732ac582342021ad77" +dependencies = [ + "nom", + "serde", + "thiserror", + "tracing", +] + +[[package]] +name = "spdx-rs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3c02f6eb7e7b4100c272f685a9ccaccaab302324e8c7ec3e2ee72340fb29ff3" +dependencies = [ + "chrono", + "log", + "nom", + "serde", + "spdx-expression", + "strum", + "strum_macros", + "thiserror", + "uuid", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -4731,6 +4771,25 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "syn" version = "1.0.102" @@ -5357,6 +5416,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d" +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.0", +] + [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 13a98eedde867..ddaf9b41f860a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ members = [ "src/tools/bump-stage0", "src/tools/replace-version-placeholder", "src/tools/lld-wrapper", + "src/tools/collect-license-metadata", ] exclude = [ diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs index 8d999302a6d7b..803b1c266f3c0 100644 --- a/src/bootstrap/builder.rs +++ b/src/bootstrap/builder.rs @@ -753,6 +753,7 @@ impl<'a> Builder<'a> { run::BumpStage0, run::ReplaceVersionPlaceholder, run::Miri, + run::CollectLicenseMetadata, ), // These commands either don't use paths, or they're special-cased in Build::build() Kind::Clean | Kind::Format | Kind::Setup => vec![], diff --git a/src/bootstrap/run.rs b/src/bootstrap/run.rs index d49b41c513279..8cbfe3ebab555 100644 --- a/src/bootstrap/run.rs +++ b/src/bootstrap/run.rs @@ -1,3 +1,4 @@ +use std::path::PathBuf; use std::process::Command; use crate::builder::{Builder, RunConfig, ShouldRun, Step}; @@ -189,3 +190,35 @@ impl Step for Miri { builder.run(&mut miri); } } + +#[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)] +pub struct CollectLicenseMetadata; + +impl Step for CollectLicenseMetadata { + type Output = PathBuf; + const ONLY_HOSTS: bool = true; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.path("src/tools/collect-license-metadata") + } + + fn make_run(run: RunConfig<'_>) { + run.builder.ensure(CollectLicenseMetadata); + } + + fn run(self, builder: &Builder<'_>) -> Self::Output { + let Some(reuse) = &builder.config.reuse else { + panic!("REUSE is required to collect the license metadata"); + }; + + // Temporary location, it will be moved to src/etc once it's accurate. + let dest = builder.out.join("license-metadata.json"); + + let mut cmd = builder.tool_cmd(Tool::CollectLicenseMetadata); + cmd.env("REUSE_EXE", reuse); + cmd.env("DEST", &dest); + builder.run(&mut cmd); + + dest + } +} diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs index ba329ea6c7592..4dd9a40dcb3c3 100644 --- a/src/bootstrap/tool.rs +++ b/src/bootstrap/tool.rs @@ -380,6 +380,7 @@ bootstrap_tool!( HtmlChecker, "src/tools/html-checker", "html-checker"; BumpStage0, "src/tools/bump-stage0", "bump-stage0"; ReplaceVersionPlaceholder, "src/tools/replace-version-placeholder", "replace-version-placeholder"; + CollectLicenseMetadata, "src/tools/collect-license-metadata", "collect-license-metadata"; ); #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] diff --git a/src/tools/collect-license-metadata/Cargo.toml b/src/tools/collect-license-metadata/Cargo.toml new file mode 100644 index 0000000000000..d0820cfc2a0e4 --- /dev/null +++ b/src/tools/collect-license-metadata/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "collect-license-metadata" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.65" +serde = { version = "1.0.147", features = ["derive"] } +serde_json = "1.0.85" +spdx-rs = "0.5.1" diff --git a/src/tools/collect-license-metadata/src/licenses.rs b/src/tools/collect-license-metadata/src/licenses.rs new file mode 100644 index 0000000000000..34aabc8730149 --- /dev/null +++ b/src/tools/collect-license-metadata/src/licenses.rs @@ -0,0 +1,37 @@ +use std::collections::HashMap; + +pub(crate) struct LicensesInterner { + by_id: Vec, + by_struct: HashMap, +} + +impl LicensesInterner { + pub(crate) fn new() -> Self { + LicensesInterner { by_id: Vec::new(), by_struct: HashMap::new() } + } + + pub(crate) fn intern(&mut self, license: License) -> LicenseId { + if let Some(id) = self.by_struct.get(&license) { + LicenseId(*id) + } else { + let id = self.by_id.len(); + self.by_id.push(license.clone()); + self.by_struct.insert(license, id); + LicenseId(id) + } + } + + pub(crate) fn resolve(&self, id: LicenseId) -> &License { + &self.by_id[id.0] + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)] +#[serde(transparent)] +pub(crate) struct LicenseId(usize); + +#[derive(Clone, Hash, PartialEq, Eq, serde::Serialize)] +pub(crate) struct License { + pub(crate) spdx: String, + pub(crate) copyright: Vec, +} diff --git a/src/tools/collect-license-metadata/src/main.rs b/src/tools/collect-license-metadata/src/main.rs new file mode 100644 index 0000000000000..0864408fd5345 --- /dev/null +++ b/src/tools/collect-license-metadata/src/main.rs @@ -0,0 +1,30 @@ +mod licenses; +mod path_tree; +mod reuse; + +use crate::licenses::LicensesInterner; +use anyhow::Error; +use std::path::PathBuf; + +fn main() -> Result<(), Error> { + let reuse_exe: PathBuf = std::env::var_os("REUSE_EXE").expect("Missing REUSE_EXE").into(); + let dest: PathBuf = std::env::var_os("DEST").expect("Missing DEST").into(); + + let mut interner = LicensesInterner::new(); + let paths = crate::reuse::collect(&reuse_exe, &mut interner)?; + + let mut tree = crate::path_tree::build(paths); + tree.simplify(); + + if let Some(parent) = dest.parent() { + std::fs::create_dir_all(parent)?; + } + std::fs::write( + &dest, + &serde_json::to_vec_pretty(&serde_json::json!({ + "files": crate::path_tree::strip_interning(tree, &interner), + }))?, + )?; + + Ok(()) +} diff --git a/src/tools/collect-license-metadata/src/path_tree.rs b/src/tools/collect-license-metadata/src/path_tree.rs new file mode 100644 index 0000000000000..6278970d81365 --- /dev/null +++ b/src/tools/collect-license-metadata/src/path_tree.rs @@ -0,0 +1,292 @@ +//! Tools like REUSE output per-file licensing information, but we need to condense it in the +//! minimum amount of data that still represents the same licensing metadata. This module is +//! responsible for that, by turning the list of paths into a tree and executing simplification +//! passes over the tree to remove redundant information. + +use crate::licenses::{License, LicenseId, LicensesInterner}; +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; + +#[derive(serde::Serialize)] +#[serde(rename_all = "kebab-case", tag = "type")] +pub(crate) enum Node { + Root { childs: Vec> }, + Directory { name: PathBuf, childs: Vec>, license: Option }, + File { name: PathBuf, license: L }, + FileGroup { names: Vec, license: L }, + Empty, +} + +impl Node { + pub(crate) fn simplify(&mut self) { + self.merge_directories(); + self.collapse_in_licensed_directories(); + self.merge_directory_licenses(); + self.merge_file_groups(); + self.remove_empty(); + } + + /// Initially, trees are built by the build() function with each file practically having a + /// separate directory tree, like so: + /// + /// ```text + /// ┌─► ./ ──► compiler/ ──► rustc/ ──► src/ ──► main.rs + /// │ + /// ─┼─► ./ ──► compiler/ ──► rustc/ ──► Cargo.toml + /// │ + /// └─► ./ ──► library/ ───► std/ ──► Cargo.toml + /// ``` + /// + /// This pass is responsible for turning that into a proper directory tree: + /// + /// ```text + /// ┌─► compiler/ ──► rustc/ ──┬─► src/ ──► main.rs + /// │ │ + /// ──► ./ ──┤ └─► Cargo.toml + /// │ + /// └─► library/ ───► std/ ──► Cargo.toml + /// ``` + fn merge_directories(&mut self) { + match self { + Node::Root { childs } | Node::Directory { childs, license: None, .. } => { + let mut directories = BTreeMap::new(); + let mut files = Vec::new(); + + for child in childs.drain(..) { + match child { + Node::Directory { name, mut childs, license: None } => { + directories.entry(name).or_insert_with(Vec::new).append(&mut childs); + } + file @ Node::File { .. } => { + files.push(file); + } + Node::Empty => {} + Node::Root { .. } => { + panic!("can't have a root inside another element"); + } + Node::FileGroup { .. } => { + panic!("FileGroup should not be present at this stage"); + } + Node::Directory { license: Some(_), .. } => { + panic!("license should not be set at this stage"); + } + } + } + + childs.extend(directories.into_iter().map(|(name, childs)| Node::Directory { + name, + childs, + license: None, + })); + childs.append(&mut files); + + for child in &mut *childs { + child.merge_directories(); + } + } + Node::Empty => {} + Node::File { .. } => {} + Node::FileGroup { .. } => { + panic!("FileGroup should not be present at this stage"); + } + Node::Directory { license: Some(_), .. } => { + panic!("license should not be set at this stage"); + } + } + } + + /// In our codebase, most files in a directory have the same license as the other files in that + /// same directory, so it's redundant to store licensing metadata for all the files. Instead, + /// we can add a license for a whole directory, and only record the exceptions to a directory + /// licensing metadata. + /// + /// We cannot instead record only the difference to Rust's standard licensing, as the majority + /// of the files in our repository are *not* licensed under Rust's standard licensing due to + /// our inclusion of LLVM. + fn collapse_in_licensed_directories(&mut self) { + match self { + Node::Directory { childs, license, .. } => { + for child in &mut *childs { + child.collapse_in_licensed_directories(); + } + + let mut licenses_count = BTreeMap::new(); + for child in &*childs { + let Some(license) = child.license() else { continue }; + *licenses_count.entry(license).or_insert(0) += 1; + } + + let most_popular_license = licenses_count + .into_iter() + .max_by_key(|(_, count)| *count) + .map(|(license, _)| license); + + if let Some(most_popular_license) = most_popular_license { + childs.retain(|child| child.license() != Some(most_popular_license)); + *license = Some(most_popular_license); + } + } + Node::Root { childs } => { + for child in &mut *childs { + child.collapse_in_licensed_directories(); + } + } + Node::File { .. } => {} + Node::FileGroup { .. } => {} + Node::Empty => {} + } + } + + /// Reduce the depth of the tree by merging subdirectories with the same license as their + /// parent directory into their parent, and adjusting the paths of the childs accordingly. + fn merge_directory_licenses(&mut self) { + match self { + Node::Root { childs } => { + for child in &mut *childs { + child.merge_directory_licenses(); + } + } + Node::Directory { childs, license, .. } => { + let mut to_add = Vec::new(); + for child in &mut *childs { + child.merge_directory_licenses(); + + let Node::Directory { + name: child_name, + childs: child_childs, + license: child_license, + } = child else { continue }; + + if child_license != license { + continue; + } + for mut child_child in child_childs.drain(..) { + match &mut child_child { + Node::Root { .. } => { + panic!("can't have a root inside another element"); + } + Node::FileGroup { .. } => { + panic!("FileGroup should not be present at this stage"); + } + Node::Directory { name: child_child_name, .. } => { + *child_child_name = child_name.join(&child_child_name); + } + Node::File { name: child_child_name, .. } => { + *child_child_name = child_name.join(&child_child_name); + } + Node::Empty => {} + } + to_add.push(child_child); + } + + *child = Node::Empty; + } + childs.append(&mut to_add); + } + Node::Empty => {} + Node::File { .. } => {} + Node::FileGroup { .. } => {} + } + } + + /// This pass groups multiple files in a directory with the same license into a single + /// "FileGroup", so that the license of all those files can be reported as a group. + /// + /// Crucially this pass runs after collapse_in_licensed_directories, so the most common license + /// will already be marked as the directory's license and won't be turned into a group. + fn merge_file_groups(&mut self) { + match self { + Node::Root { childs } | Node::Directory { childs, .. } => { + let mut grouped = BTreeMap::new(); + + for child in &mut *childs { + child.merge_file_groups(); + if let Node::File { name, license } = child { + grouped.entry(*license).or_insert_with(Vec::new).push(name.clone()); + *child = Node::Empty; + } + } + + for (license, mut names) in grouped.into_iter() { + if names.len() == 1 { + childs.push(Node::File { license, name: names.pop().unwrap() }); + } else { + childs.push(Node::FileGroup { license, names }); + } + } + } + Node::File { .. } => {} + Node::FileGroup { .. } => panic!("FileGroup should not be present at this stage"), + Node::Empty => {} + } + } + + /// Some nodes were replaced with Node::Empty to mark them for deletion. As the last step, make + /// sure to remove them from the tree. + fn remove_empty(&mut self) { + match self { + Node::Root { childs } | Node::Directory { childs, .. } => { + for child in &mut *childs { + child.remove_empty(); + } + childs.retain(|child| !matches!(child, Node::Empty)); + } + Node::FileGroup { .. } => {} + Node::File { .. } => {} + Node::Empty => {} + } + } + + fn license(&self) -> Option { + match self { + Node::Directory { childs, license: Some(license), .. } if childs.is_empty() => { + Some(*license) + } + Node::File { license, .. } => Some(*license), + _ => None, + } + } +} + +pub(crate) fn build(mut input: Vec<(PathBuf, LicenseId)>) -> Node { + let mut childs = Vec::new(); + + // Ensure reproducibility of all future steps. + input.sort(); + + for (path, license) in input { + let mut node = Node::File { name: path.file_name().unwrap().into(), license }; + for component in path.parent().unwrap_or_else(|| Path::new(".")).components().rev() { + node = Node::Directory { + name: component.as_os_str().into(), + childs: vec![node], + license: None, + }; + } + + childs.push(node); + } + + Node::Root { childs } +} + +pub(crate) fn strip_interning( + node: Node, + interner: &LicensesInterner, +) -> Node<&License> { + match node { + Node::Root { childs } => Node::Root { + childs: childs.into_iter().map(|child| strip_interning(child, interner)).collect(), + }, + Node::Directory { name, childs, license } => Node::Directory { + childs: childs.into_iter().map(|child| strip_interning(child, interner)).collect(), + license: license.map(|license| interner.resolve(license)), + name, + }, + Node::File { name, license } => Node::File { name, license: interner.resolve(license) }, + Node::FileGroup { names, license } => { + Node::FileGroup { names, license: interner.resolve(license) } + } + Node::Empty => Node::Empty, + } +} diff --git a/src/tools/collect-license-metadata/src/reuse.rs b/src/tools/collect-license-metadata/src/reuse.rs new file mode 100644 index 0000000000000..d6b3772ba5159 --- /dev/null +++ b/src/tools/collect-license-metadata/src/reuse.rs @@ -0,0 +1,49 @@ +use crate::licenses::{License, LicenseId, LicensesInterner}; +use anyhow::Error; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; +use std::time::Instant; + +pub(crate) fn collect( + reuse_exe: &Path, + interner: &mut LicensesInterner, +) -> Result, Error> { + eprintln!("gathering license information from REUSE"); + let start = Instant::now(); + let raw = &obtain_spdx_document(reuse_exe)?; + eprintln!("finished gathering the license information from REUSE in {:.2?}", start.elapsed()); + + let document = spdx_rs::parsers::spdx_from_tag_value(&raw)?; + + let mut result = Vec::new(); + for file in document.file_information { + let license = interner.intern(License { + spdx: file.concluded_license.to_string(), + copyright: file.copyright_text.split('\n').map(|s| s.into()).collect(), + }); + + result.push((file.file_name.into(), license)); + } + + Ok(result) +} + +fn obtain_spdx_document(reuse_exe: &Path) -> Result { + let output = Command::new(reuse_exe) + .args(&["spdx", "--add-license-concluded", "--creator-person=bors"]) + .stdout(Stdio::piped()) + .spawn()? + .wait_with_output()?; + + if !output.status.success() { + eprintln!(); + eprintln!("Note that Rust requires some REUSE features that might not be present in the"); + eprintln!("release you're using. Make sure your REUSE release includes these PRs:"); + eprintln!(); + eprintln!(" - https://github.com/fsfe/reuse-tool/pull/623"); + eprintln!(); + anyhow::bail!("collecting licensing information with REUSE failed"); + } + + Ok(String::from_utf8(output.stdout)?) +} From 4af7de13d26aabe9bc459771bd5641c140407954 Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Tue, 15 Nov 2022 11:27:19 +0100 Subject: [PATCH 03/19] initial prototype of the tool to generate copyright notices --- Cargo.lock | 9 +++ Cargo.toml | 1 + src/bootstrap/builder.rs | 1 + src/bootstrap/run.rs | 30 ++++++++ src/bootstrap/tool.rs | 1 + src/tools/generate-copyright/Cargo.toml | 11 +++ src/tools/generate-copyright/src/main.rs | 94 ++++++++++++++++++++++++ 7 files changed, 147 insertions(+) create mode 100644 src/tools/generate-copyright/Cargo.toml create mode 100644 src/tools/generate-copyright/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index eadaf721f0210..8cde96b519f7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1498,6 +1498,15 @@ dependencies = [ "termcolor", ] +[[package]] +name = "generate-copyright" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde", + "serde_json", +] + [[package]] name = "generic-array" version = "0.14.4" diff --git a/Cargo.toml b/Cargo.toml index ddaf9b41f860a..000c10a1f906d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ members = [ "src/tools/replace-version-placeholder", "src/tools/lld-wrapper", "src/tools/collect-license-metadata", + "src/tools/generate-copyright", ] exclude = [ diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs index 803b1c266f3c0..0a311529bcaee 100644 --- a/src/bootstrap/builder.rs +++ b/src/bootstrap/builder.rs @@ -754,6 +754,7 @@ impl<'a> Builder<'a> { run::ReplaceVersionPlaceholder, run::Miri, run::CollectLicenseMetadata, + run::GenerateCopyright, ), // These commands either don't use paths, or they're special-cased in Build::build() Kind::Clean | Kind::Format | Kind::Setup => vec![], diff --git a/src/bootstrap/run.rs b/src/bootstrap/run.rs index 8cbfe3ebab555..05de51f8cc579 100644 --- a/src/bootstrap/run.rs +++ b/src/bootstrap/run.rs @@ -222,3 +222,33 @@ impl Step for CollectLicenseMetadata { dest } } + +#[derive(Debug, PartialOrd, Ord, Copy, Clone, Hash, PartialEq, Eq)] +pub struct GenerateCopyright; + +impl Step for GenerateCopyright { + type Output = PathBuf; + const ONLY_HOSTS: bool = true; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.path("src/tools/generate-copyright") + } + + fn make_run(run: RunConfig<'_>) { + run.builder.ensure(GenerateCopyright); + } + + fn run(self, builder: &Builder<'_>) -> Self::Output { + let license_metadata = builder.ensure(CollectLicenseMetadata); + + // Temporary location, it will be moved to the proper one once it's accurate. + let dest = builder.out.join("COPYRIGHT.md"); + + let mut cmd = builder.tool_cmd(Tool::GenerateCopyright); + cmd.env("LICENSE_METADATA", &license_metadata); + cmd.env("DEST", &dest); + builder.run(&mut cmd); + + dest + } +} diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs index 4dd9a40dcb3c3..e0be4c432f168 100644 --- a/src/bootstrap/tool.rs +++ b/src/bootstrap/tool.rs @@ -381,6 +381,7 @@ bootstrap_tool!( BumpStage0, "src/tools/bump-stage0", "bump-stage0"; ReplaceVersionPlaceholder, "src/tools/replace-version-placeholder", "replace-version-placeholder"; CollectLicenseMetadata, "src/tools/collect-license-metadata", "collect-license-metadata"; + GenerateCopyright, "src/tools/generate-copyright", "generate-copyright"; ); #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] diff --git a/src/tools/generate-copyright/Cargo.toml b/src/tools/generate-copyright/Cargo.toml new file mode 100644 index 0000000000000..899ef0f8a6c26 --- /dev/null +++ b/src/tools/generate-copyright/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "generate-copyright" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.65" +serde = { version = "1.0.147", features = ["derive"] } +serde_json = "1.0.85" diff --git a/src/tools/generate-copyright/src/main.rs b/src/tools/generate-copyright/src/main.rs new file mode 100644 index 0000000000000..d172c9e157bc8 --- /dev/null +++ b/src/tools/generate-copyright/src/main.rs @@ -0,0 +1,94 @@ +use anyhow::Error; +use std::io::Write; +use std::path::PathBuf; + +fn main() -> Result<(), Error> { + let dest = env_path("DEST")?; + let license_metadata = env_path("LICENSE_METADATA")?; + + let metadata: Metadata = serde_json::from_slice(&std::fs::read(&license_metadata)?)?; + + let mut buffer = Vec::new(); + render_recursive(&metadata.files, &mut buffer, 0)?; + + std::fs::write(&dest, &buffer)?; + + Ok(()) +} + +fn render_recursive(node: &Node, buffer: &mut Vec, depth: usize) -> Result<(), Error> { + let prefix = std::iter::repeat("> ").take(depth + 1).collect::(); + + match node { + Node::Root { childs } => { + for child in childs { + render_recursive(child, buffer, depth)?; + } + } + Node::Directory { name, childs, license } => { + render_license(&prefix, std::iter::once(name), license, buffer)?; + if !childs.is_empty() { + writeln!(buffer, "{prefix}")?; + writeln!(buffer, "{prefix}*Exceptions:*")?; + for child in childs { + writeln!(buffer, "{prefix}")?; + render_recursive(child, buffer, depth + 1)?; + } + } + } + Node::FileGroup { names, license } => { + render_license(&prefix, names.iter(), license, buffer)?; + } + Node::File { name, license } => { + render_license(&prefix, std::iter::once(name), license, buffer)?; + } + } + + Ok(()) +} + +fn render_license<'a>( + prefix: &str, + names: impl Iterator, + license: &License, + buffer: &mut Vec, +) -> Result<(), Error> { + for name in names { + writeln!(buffer, "{prefix}**`{name}`** ")?; + } + writeln!(buffer, "{prefix}License: `{}` ", license.spdx)?; + for (i, copyright) in license.copyright.iter().enumerate() { + let suffix = if i == license.copyright.len() - 1 { "" } else { " " }; + writeln!(buffer, "{prefix}Copyright: {copyright}{suffix}")?; + } + + Ok(()) +} + +#[derive(serde::Deserialize)] +struct Metadata { + files: Node, +} + +#[derive(serde::Deserialize)] +#[serde(rename_all = "kebab-case", tag = "type")] +pub(crate) enum Node { + Root { childs: Vec }, + Directory { name: String, childs: Vec, license: License }, + File { name: String, license: License }, + FileGroup { names: Vec, license: License }, +} + +#[derive(serde::Deserialize)] +struct License { + spdx: String, + copyright: Vec, +} + +fn env_path(var: &str) -> Result { + if let Some(var) = std::env::var_os(var) { + Ok(var.into()) + } else { + anyhow::bail!("missing environment variable {var}") + } +} From 51d5ae63d0845322cd274a67a504513df929b310 Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Thu, 17 Nov 2022 11:05:27 +0100 Subject: [PATCH 04/19] merge together similar copyright statements --- .../collect-license-metadata/src/licenses.rs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/tools/collect-license-metadata/src/licenses.rs b/src/tools/collect-license-metadata/src/licenses.rs index 34aabc8730149..1c95b1bc8e96c 100644 --- a/src/tools/collect-license-metadata/src/licenses.rs +++ b/src/tools/collect-license-metadata/src/licenses.rs @@ -1,5 +1,7 @@ use std::collections::HashMap; +const COPYRIGHT_PREFIXES: &[&str] = &["SPDX-FileCopyrightText:", "Copyright", "(c)", "(C)", "©"]; + pub(crate) struct LicensesInterner { by_id: Vec, by_struct: HashMap, @@ -10,7 +12,8 @@ impl LicensesInterner { LicensesInterner { by_id: Vec::new(), by_struct: HashMap::new() } } - pub(crate) fn intern(&mut self, license: License) -> LicenseId { + pub(crate) fn intern(&mut self, mut license: License) -> LicenseId { + license.simplify(); if let Some(id) = self.by_struct.get(&license) { LicenseId(*id) } else { @@ -35,3 +38,28 @@ pub(crate) struct License { pub(crate) spdx: String, pub(crate) copyright: Vec, } + +impl License { + fn simplify(&mut self) { + self.remove_copyright_prefixes(); + self.copyright.sort(); + self.copyright.dedup(); + } + + fn remove_copyright_prefixes(&mut self) { + for copyright in &mut self.copyright { + let mut stripped = copyright.trim(); + let mut previous_stripped; + loop { + previous_stripped = stripped; + for pattern in COPYRIGHT_PREFIXES { + stripped = stripped.trim_start_matches(pattern).trim_start(); + } + if stripped == previous_stripped { + break; + } + } + *copyright = stripped.into(); + } + } +} From f78e6468ba40b5d328cf282c92fc94521988000e Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Thu, 24 Nov 2022 16:02:55 +0100 Subject: [PATCH 05/19] make comment more clear Co-authored-by: Felix S Klock II --- src/tools/collect-license-metadata/src/path_tree.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/collect-license-metadata/src/path_tree.rs b/src/tools/collect-license-metadata/src/path_tree.rs index 6278970d81365..fa4ae87bdad00 100644 --- a/src/tools/collect-license-metadata/src/path_tree.rs +++ b/src/tools/collect-license-metadata/src/path_tree.rs @@ -26,8 +26,8 @@ impl Node { self.remove_empty(); } - /// Initially, trees are built by the build() function with each file practically having a - /// separate directory tree, like so: + /// Initially, the build() function constructs a list of separate paths from the file + /// system root down to each file, like so: /// /// ```text /// ┌─► ./ ──► compiler/ ──► rustc/ ──► src/ ──► main.rs From f8a7123b276ed18e6cdd488818a8f4e5e3e1cf94 Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Thu, 24 Nov 2022 17:25:35 +0100 Subject: [PATCH 06/19] address review feedback --- config.toml.example | 11 +++++++---- src/tools/collect-license-metadata/src/main.rs | 2 +- src/tools/collect-license-metadata/src/path_tree.rs | 4 +++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/config.toml.example b/config.toml.example index ffc6a330b9f60..ca54cbd2d68dc 100644 --- a/config.toml.example +++ b/config.toml.example @@ -255,11 +255,14 @@ changelog-seen = 2 # Defaults to the Python interpreter used to execute x.py #python = "python" -# The path to the REUSE executable to use. REUSE will be used to gather -# the licensing information of the codebase. +# The path to the REUSE executable to use. Note that REUSE is not required in +# most cases, as our tooling relies on a cached (and shrinked) copy of the +# REUSE output present in the git repository and in our source tarballs. # -# If this is omitted, the cached licensing information present in the source -# tarball will have to be present. +# REUSE is only needed if your changes caused the overral licensing of the +# repository to change, and the cached copy has to be regenerated. +# +# Defaults to the "reuse" command in the system path. #reuse = "reuse" # Force Cargo to check that Cargo.lock describes the precise dependency diff --git a/src/tools/collect-license-metadata/src/main.rs b/src/tools/collect-license-metadata/src/main.rs index 0864408fd5345..ca2a6f4b8c8a2 100644 --- a/src/tools/collect-license-metadata/src/main.rs +++ b/src/tools/collect-license-metadata/src/main.rs @@ -22,7 +22,7 @@ fn main() -> Result<(), Error> { std::fs::write( &dest, &serde_json::to_vec_pretty(&serde_json::json!({ - "files": crate::path_tree::strip_interning(tree, &interner), + "files": crate::path_tree::expand_interned_licenses(tree, &interner), }))?, )?; diff --git a/src/tools/collect-license-metadata/src/path_tree.rs b/src/tools/collect-license-metadata/src/path_tree.rs index fa4ae87bdad00..133ff6837378e 100644 --- a/src/tools/collect-license-metadata/src/path_tree.rs +++ b/src/tools/collect-license-metadata/src/path_tree.rs @@ -270,7 +270,9 @@ pub(crate) fn build(mut input: Vec<(PathBuf, LicenseId)>) -> Node { Node::Root { childs } } -pub(crate) fn strip_interning( +/// Convert a `Node` into a `Node<&License>`, expanding all interned license IDs with a +/// reference to the actual license metadata. +pub(crate) fn expand_interned_licenses( node: Node, interner: &LicensesInterner, ) -> Node<&License> { From 0e19fb92e1eb7f91aa5570b2ac782c9a42a6e329 Mon Sep 17 00:00:00 2001 From: Yiming Lei Date: Wed, 30 Nov 2022 12:01:00 -0800 Subject: [PATCH 07/19] While parsing enum variant, the error message always disappear Because the error message that emit out is from main error of parser The information of enum variant disappears while parsing enum variant with error We only check the syntax of expecting token, i.e, in case #103869 It will error it without telling the message that this error is from pasring enum variant. Propagate the sub-error from parsing enum variant to the main error of parser by chaining it with map_err Check the sub-error before emitting the main error of parser and attach it. Fix #103869 --- compiler/rustc_parse/src/parser/item.rs | 5 ++++- compiler/rustc_parse/src/parser/mod.rs | 4 ++++ src/test/ui/macros/syntax-error-recovery.stderr | 1 + src/test/ui/parser/issue-101477-enum.stderr | 2 ++ src/test/ui/parser/issue-103869.rs | 9 +++++++++ src/test/ui/parser/issue-103869.stderr | 16 ++++++++++++++++ src/test/ui/parser/macro/issue-37113.stderr | 1 + src/test/ui/structs/struct-fn-in-definition.rs | 1 + .../ui/structs/struct-fn-in-definition.stderr | 1 + 9 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 src/test/ui/parser/issue-103869.rs create mode 100644 src/test/ui/parser/issue-103869.stderr diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 767fb9378bef1..7ce89b5c3597b 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -1414,7 +1414,10 @@ impl<'a> Parser<'a> { Ok((Some(vr), TrailingToken::MaybeComma)) }, - ) + ).map_err(|mut err|{ + err.help("enum variants can be `Variant`, `Variant = `, `Variant(Type, ..., TypeN)` or `Variant { fields: Types }`"); + err + }) } /// Parses `struct Foo { ... }`. diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs index 4d8bff28b05aa..bebb012660a16 100644 --- a/compiler/rustc_parse/src/parser/mod.rs +++ b/compiler/rustc_parse/src/parser/mod.rs @@ -943,6 +943,10 @@ impl<'a> Parser<'a> { Err(e) => { // Parsing failed, therefore it must be something more serious // than just a missing separator. + for xx in &e.children { + // propagate the help message from sub error 'e' to main error 'expect_err; + expect_err.children.push(xx.clone()); + } expect_err.emit(); e.cancel(); diff --git a/src/test/ui/macros/syntax-error-recovery.stderr b/src/test/ui/macros/syntax-error-recovery.stderr index c153b3b910bbe..c42ee9b295e1d 100644 --- a/src/test/ui/macros/syntax-error-recovery.stderr +++ b/src/test/ui/macros/syntax-error-recovery.stderr @@ -7,6 +7,7 @@ LL | $token $($inner)? = $value, LL | values!(STRING(1) as (String) => cfg(test),); | -------------------------------------------- in this macro invocation | + = help: enum variants can be `Variant`, `Variant = `, `Variant(Type, ..., TypeN)` or `Variant { fields: Types }` = note: this error originates in the macro `values` (in Nightly builds, run with -Z macro-backtrace for more info) error: macro expansion ignores token `(String)` and any following diff --git a/src/test/ui/parser/issue-101477-enum.stderr b/src/test/ui/parser/issue-101477-enum.stderr index bffc881bdc84b..1edca391e8fd0 100644 --- a/src/test/ui/parser/issue-101477-enum.stderr +++ b/src/test/ui/parser/issue-101477-enum.stderr @@ -3,6 +3,8 @@ error: unexpected `==` | LL | B == 2 | ^^ help: try using `=` instead + | + = help: enum variants can be `Variant`, `Variant = `, `Variant(Type, ..., TypeN)` or `Variant { fields: Types }` error: expected item, found `==` --> $DIR/issue-101477-enum.rs:6:7 diff --git a/src/test/ui/parser/issue-103869.rs b/src/test/ui/parser/issue-103869.rs new file mode 100644 index 0000000000000..28c442bdd632d --- /dev/null +++ b/src/test/ui/parser/issue-103869.rs @@ -0,0 +1,9 @@ +enum VecOrMap{ + vec: Vec, + //~^ ERROR expected one of `(`, `,`, `=`, `{`, or `}`, found `:` + //~| HELP: enum variants can be `Variant`, `Variant = `, `Variant(Type, ..., TypeN)` or `Variant { fields: Types }` + //~| ERROR expected item, found `:` + map: HashMap +} + +fn main() {} diff --git a/src/test/ui/parser/issue-103869.stderr b/src/test/ui/parser/issue-103869.stderr new file mode 100644 index 0000000000000..0b8cd919a9de2 --- /dev/null +++ b/src/test/ui/parser/issue-103869.stderr @@ -0,0 +1,16 @@ +error: expected one of `(`, `,`, `=`, `{`, or `}`, found `:` + --> $DIR/issue-103869.rs:2:8 + | +LL | vec: Vec, + | ^ expected one of `(`, `,`, `=`, `{`, or `}` + | + = help: enum variants can be `Variant`, `Variant = `, `Variant(Type, ..., TypeN)` or `Variant { fields: Types }` + +error: expected item, found `:` + --> $DIR/issue-103869.rs:2:8 + | +LL | vec: Vec, + | ^ expected item + +error: aborting due to 2 previous errors + diff --git a/src/test/ui/parser/macro/issue-37113.stderr b/src/test/ui/parser/macro/issue-37113.stderr index b1f8674fbdf38..da9e743a0b44f 100644 --- a/src/test/ui/parser/macro/issue-37113.stderr +++ b/src/test/ui/parser/macro/issue-37113.stderr @@ -9,6 +9,7 @@ LL | $( $t, )* LL | test_macro!(String,); | -------------------- in this macro invocation | + = help: enum variants can be `Variant`, `Variant = `, `Variant(Type, ..., TypeN)` or `Variant { fields: Types }` = note: this error originates in the macro `test_macro` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to previous error diff --git a/src/test/ui/structs/struct-fn-in-definition.rs b/src/test/ui/structs/struct-fn-in-definition.rs index 5ae1b727dc772..7f48f55fec938 100644 --- a/src/test/ui/structs/struct-fn-in-definition.rs +++ b/src/test/ui/structs/struct-fn-in-definition.rs @@ -28,6 +28,7 @@ enum E { //~^ ERROR functions are not allowed in enum definitions //~| HELP unlike in C++, Java, and C#, functions are declared in `impl` blocks //~| HELP see https://doc.rust-lang.org/book/ch05-03-method-syntax.html for more information + //~| HELP enum variants can be `Variant`, `Variant = `, `Variant(Type, ..., TypeN)` or `Variant { fields: Types }` } fn main() {} diff --git a/src/test/ui/structs/struct-fn-in-definition.stderr b/src/test/ui/structs/struct-fn-in-definition.stderr index 472365c6ed739..439c86ec22b0e 100644 --- a/src/test/ui/structs/struct-fn-in-definition.stderr +++ b/src/test/ui/structs/struct-fn-in-definition.stderr @@ -33,6 +33,7 @@ LL | fn foo() {} | = help: unlike in C++, Java, and C#, functions are declared in `impl` blocks = help: see https://doc.rust-lang.org/book/ch05-03-method-syntax.html for more information + = help: enum variants can be `Variant`, `Variant = `, `Variant(Type, ..., TypeN)` or `Variant { fields: Types }` error: aborting due to 3 previous errors From c823dfa8b2f1b0cfb82d253e6d55efd6927bcb6d Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 4 Dec 2022 10:32:00 +0100 Subject: [PATCH 08/19] remove no-op 'let _ = ' --- library/std/src/sync/mpsc/mod.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/library/std/src/sync/mpsc/mod.rs b/library/std/src/sync/mpsc/mod.rs index 27fba761ada10..adb488d4378f0 100644 --- a/library/std/src/sync/mpsc/mod.rs +++ b/library/std/src/sync/mpsc/mod.rs @@ -629,9 +629,7 @@ impl Clone for Sender { #[stable(feature = "rust1", since = "1.0.0")] impl Drop for Sender { - fn drop(&mut self) { - let _ = self.inner; - } + fn drop(&mut self) {} } #[stable(feature = "mpsc_debug", since = "1.8.0")] @@ -751,9 +749,7 @@ impl Clone for SyncSender { #[stable(feature = "rust1", since = "1.0.0")] impl Drop for SyncSender { - fn drop(&mut self) { - let _ = self.inner; - } + fn drop(&mut self) {} } #[stable(feature = "mpsc_debug", since = "1.8.0")] @@ -1094,9 +1090,7 @@ impl IntoIterator for Receiver { #[stable(feature = "rust1", since = "1.0.0")] impl Drop for Receiver { - fn drop(&mut self) { - let _ = self.inner; - } + fn drop(&mut self) {} } #[stable(feature = "mpsc_debug", since = "1.8.0")] From 44948d1fdc97a4fd24594be77df673a2c3b40544 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Sun, 4 Dec 2022 13:54:41 +0000 Subject: [PATCH 09/19] Recurse into nested impl-trait when computing variance. --- .../rustc_hir_analysis/src/variance/mod.rs | 42 +++++++++++++++++-- src/test/ui/impl-trait/nested-return-type4.rs | 8 ++++ .../ui/impl-trait/nested-return-type4.stderr | 20 +++++++++ 3 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 src/test/ui/impl-trait/nested-return-type4.rs create mode 100644 src/test/ui/impl-trait/nested-return-type4.stderr diff --git a/compiler/rustc_hir_analysis/src/variance/mod.rs b/compiler/rustc_hir_analysis/src/variance/mod.rs index 9db05eedbde79..8b2719c2f8aaa 100644 --- a/compiler/rustc_hir_analysis/src/variance/mod.rs +++ b/compiler/rustc_hir_analysis/src/variance/mod.rs @@ -7,7 +7,8 @@ use rustc_arena::DroplessArena; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_middle::ty::query::Providers; -use rustc_middle::ty::{self, CrateVariancesMap, TyCtxt, TypeSuperVisitable, TypeVisitable}; +use rustc_middle::ty::{self, CrateVariancesMap, SubstsRef, Ty, TyCtxt}; +use rustc_middle::ty::{DefIdTree, TypeSuperVisitable, TypeVisitable}; use std::ops::ControlFlow; /// Defines the `TermsContext` basically houses an arena where we can @@ -75,11 +76,30 @@ fn variance_of_opaque(tcx: TyCtxt<'_>, item_def_id: LocalDefId) -> &[ty::Varianc // type Foo<'a, 'b, 'c> = impl Trait<'a> + 'b; // ``` // we may not use `'c` in the hidden type. - struct OpaqueTypeLifetimeCollector { + struct OpaqueTypeLifetimeCollector<'tcx> { + tcx: TyCtxt<'tcx>, + root_def_id: DefId, variances: Vec, } - impl<'tcx> ty::TypeVisitor<'tcx> for OpaqueTypeLifetimeCollector { + impl<'tcx> OpaqueTypeLifetimeCollector<'tcx> { + #[instrument(level = "trace", skip(self), ret)] + fn visit_opaque(&mut self, def_id: DefId, substs: SubstsRef<'tcx>) -> ControlFlow { + if def_id != self.root_def_id && self.tcx.is_descendant_of(def_id, self.root_def_id) { + let child_variances = self.tcx.variances_of(def_id); + for (a, v) in substs.iter().zip(child_variances) { + if *v != ty::Bivariant { + a.visit_with(self)?; + } + } + ControlFlow::CONTINUE + } else { + substs.visit_with(self) + } + } + } + + impl<'tcx> ty::TypeVisitor<'tcx> for OpaqueTypeLifetimeCollector<'tcx> { #[instrument(level = "trace", skip(self), ret)] fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow { if let ty::RegionKind::ReEarlyBound(ebr) = r.kind() { @@ -87,6 +107,19 @@ fn variance_of_opaque(tcx: TyCtxt<'_>, item_def_id: LocalDefId) -> &[ty::Varianc } r.super_visit_with(self) } + + #[instrument(level = "trace", skip(self), ret)] + fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow { + match t.kind() { + ty::Opaque(def_id, substs) => self.visit_opaque(*def_id, substs), + ty::Projection(proj) + if self.tcx.def_kind(proj.item_def_id) == DefKind::ImplTraitPlaceholder => + { + self.visit_opaque(proj.item_def_id, proj.substs) + } + _ => t.super_visit_with(self), + } + } } // By default, RPIT are invariant wrt type and const generics, but they are bivariant wrt @@ -111,7 +144,8 @@ fn variance_of_opaque(tcx: TyCtxt<'_>, item_def_id: LocalDefId) -> &[ty::Varianc } } - let mut collector = OpaqueTypeLifetimeCollector { variances }; + let mut collector = + OpaqueTypeLifetimeCollector { tcx, root_def_id: item_def_id.to_def_id(), variances }; let id_substs = ty::InternalSubsts::identity_for_item(tcx, item_def_id.to_def_id()); for pred in tcx.bound_explicit_item_bounds(item_def_id.to_def_id()).transpose_iter() { let pred = pred.map_bound(|(pred, _)| *pred).subst(tcx, id_substs); diff --git a/src/test/ui/impl-trait/nested-return-type4.rs b/src/test/ui/impl-trait/nested-return-type4.rs new file mode 100644 index 0000000000000..cec70bb1a0d9e --- /dev/null +++ b/src/test/ui/impl-trait/nested-return-type4.rs @@ -0,0 +1,8 @@ +// edition: 2021 + +fn test<'s: 's>(s: &'s str) -> impl std::future::Future { + async move { let _s = s; } + //~^ ERROR hidden type for `impl Future` captures lifetime that does not appear in bounds +} + +fn main() {} diff --git a/src/test/ui/impl-trait/nested-return-type4.stderr b/src/test/ui/impl-trait/nested-return-type4.stderr new file mode 100644 index 0000000000000..e761a60e79c27 --- /dev/null +++ b/src/test/ui/impl-trait/nested-return-type4.stderr @@ -0,0 +1,20 @@ +error[E0700]: hidden type for `impl Future` captures lifetime that does not appear in bounds + --> $DIR/nested-return-type4.rs:4:5 + | +LL | fn test<'s: 's>(s: &'s str) -> impl std::future::Future { + | -- hidden type `[async block@$DIR/nested-return-type4.rs:4:5: 4:31]` captures the lifetime `'s` as defined here +LL | async move { let _s = s; } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: to declare that `impl Future` captures `'s`, you can add an explicit `'s` lifetime bound + | +LL | fn test<'s: 's>(s: &'s str) -> impl std::future::Future + 's { + | ++++ +help: to declare that `impl Sized` captures `'s`, you can add an explicit `'s` lifetime bound + | +LL | fn test<'s: 's>(s: &'s str) -> impl std::future::Future { + | ++++ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0700`. From 19175e9b75234647963c84247db653f53c8c936a Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Mon, 5 Dec 2022 05:44:24 +0000 Subject: [PATCH 10/19] Synthesize generics for bad auto traits in dyn types --- compiler/rustc_middle/src/ty/sty.rs | 24 +++++++++++++++++-- .../ui/auto-traits/bad-generics-on-dyn.rs | 11 +++++++++ .../ui/auto-traits/bad-generics-on-dyn.stderr | 11 +++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 src/test/ui/auto-traits/bad-generics-on-dyn.rs create mode 100644 src/test/ui/auto-traits/bad-generics-on-dyn.stderr diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index 5984686044b73..8e69824e6c790 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -722,8 +722,28 @@ impl<'tcx> PolyExistentialPredicate<'tcx> { self.rebind(p.with_self_ty(tcx, self_ty)).to_predicate(tcx) } ExistentialPredicate::AutoTrait(did) => { - let trait_ref = self.rebind(tcx.mk_trait_ref(did, [self_ty])); - trait_ref.without_const().to_predicate(tcx) + let generics = tcx.generics_of(did); + let trait_ref = if generics.params.len() == 1 { + tcx.mk_trait_ref(did, [self_ty]) + } else { + // If this is an ill-formed auto trait, then synthesize + // new error substs for the missing generics. + let err_substs = ty::InternalSubsts::for_item(tcx, did, |def, substs| { + if def.index == 0 { + self_ty.into() + } else { + match &def.kind { + ty::GenericParamDefKind::Lifetime => tcx.lifetimes.re_static.into(), + ty::GenericParamDefKind::Type { .. } => tcx.ty_error().into(), + ty::GenericParamDefKind::Const { .. } => tcx + .const_error(tcx.bound_type_of(def.def_id).subst(tcx, substs)) + .into(), + } + } + }); + tcx.mk_trait_ref(did, err_substs) + }; + self.rebind(trait_ref).without_const().to_predicate(tcx) } } } diff --git a/src/test/ui/auto-traits/bad-generics-on-dyn.rs b/src/test/ui/auto-traits/bad-generics-on-dyn.rs new file mode 100644 index 0000000000000..3f8ac14c72d95 --- /dev/null +++ b/src/test/ui/auto-traits/bad-generics-on-dyn.rs @@ -0,0 +1,11 @@ +#![feature(auto_traits)] + +auto trait Trait1<'a> {} +//~^ ERROR auto traits cannot have generic parameters + +fn f<'a>(x: &dyn Trait1<'a>) +{} + +fn main() { + f(&1); +} diff --git a/src/test/ui/auto-traits/bad-generics-on-dyn.stderr b/src/test/ui/auto-traits/bad-generics-on-dyn.stderr new file mode 100644 index 0000000000000..ade69ced6060d --- /dev/null +++ b/src/test/ui/auto-traits/bad-generics-on-dyn.stderr @@ -0,0 +1,11 @@ +error[E0567]: auto traits cannot have generic parameters + --> $DIR/bad-generics-on-dyn.rs:3:18 + | +LL | auto trait Trait1<'a> {} + | ------^^^^ help: remove the parameters + | | + | auto trait cannot have generic parameters + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0567`. From f4c76b193d54eeb88b2c966612679d733536efaf Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Mon, 5 Dec 2022 08:15:30 -0800 Subject: [PATCH 11/19] Be more careful about unresolved exprs in suggestion --- .../rustc_hir_typeck/src/method/suggest.rs | 23 +++++++++++-------- .../path-to-method-sugg-unresolved-expr.rs | 4 ++++ ...path-to-method-sugg-unresolved-expr.stderr | 9 ++++++++ 3 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 src/test/ui/typeck/path-to-method-sugg-unresolved-expr.rs create mode 100644 src/test/ui/typeck/path-to-method-sugg-unresolved-expr.stderr diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 9ba4ddfd5cf7f..db93cfab2c0db 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -1482,15 +1482,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ident_name: Symbol, } + // FIXME: This really should be taking scoping, etc into account. impl<'v> Visitor<'v> for LetVisitor<'v> { fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) { - if let hir::StmtKind::Local(hir::Local { pat, init, .. }) = &ex.kind { - if let Binding(_, _, ident, ..) = pat.kind && - ident.name == self.ident_name { - self.result = *init; - } + if let hir::StmtKind::Local(hir::Local { pat, init, .. }) = &ex.kind + && let Binding(_, _, ident, ..) = pat.kind + && ident.name == self.ident_name + { + self.result = *init; + } else { + hir::intravisit::walk_stmt(self, ex); } - hir::intravisit::walk_stmt(self, ex); } } @@ -1498,9 +1500,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { visitor.visit_body(&body); let parent = self.tcx.hir().get_parent_node(seg1.hir_id); - if let Some(Node::Expr(call_expr)) = self.tcx.hir().find(parent) && - let Some(expr) = visitor.result { - let self_ty = self.node_ty(expr.hir_id); + if let Some(Node::Expr(call_expr)) = self.tcx.hir().find(parent) + && let Some(expr) = visitor.result + && let Some(self_ty) = self.node_ty_opt(expr.hir_id) + { let probe = self.lookup_probe( seg2.ident, self_ty, @@ -1513,7 +1516,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { sm.span_extend_while(seg1.ident.span.shrink_to_hi(), |c| c == ':').unwrap(), "you may have meant to call an instance method", ".".to_string(), - Applicability::MaybeIncorrect + Applicability::MaybeIncorrect, ); } } diff --git a/src/test/ui/typeck/path-to-method-sugg-unresolved-expr.rs b/src/test/ui/typeck/path-to-method-sugg-unresolved-expr.rs new file mode 100644 index 0000000000000..fb56b394493dc --- /dev/null +++ b/src/test/ui/typeck/path-to-method-sugg-unresolved-expr.rs @@ -0,0 +1,4 @@ +fn main() { + let page_size = page_size::get(); + //~^ ERROR failed to resolve: use of undeclared crate or module `page_size` +} diff --git a/src/test/ui/typeck/path-to-method-sugg-unresolved-expr.stderr b/src/test/ui/typeck/path-to-method-sugg-unresolved-expr.stderr new file mode 100644 index 0000000000000..b01e30be54de0 --- /dev/null +++ b/src/test/ui/typeck/path-to-method-sugg-unresolved-expr.stderr @@ -0,0 +1,9 @@ +error[E0433]: failed to resolve: use of undeclared crate or module `page_size` + --> $DIR/path-to-method-sugg-unresolved-expr.rs:2:21 + | +LL | let page_size = page_size::get(); + | ^^^^^^^^^ use of undeclared crate or module `page_size` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0433`. From da929fa63c7a9687de708587f170cbe7f874a495 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Mon, 5 Dec 2022 17:34:17 +0000 Subject: [PATCH 12/19] Make get_impl_future_output_ty work with AFIT --- .../src/infer/error_reporting/mod.rs | 10 +++++++- .../in-trait/return-type-suggestion.rs | 14 +++++++++++ .../in-trait/return-type-suggestion.stderr | 23 +++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 src/test/ui/async-await/in-trait/return-type-suggestion.rs create mode 100644 src/test/ui/async-await/in-trait/return-type-suggestion.stderr diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs index 662136ca18df6..3256ca1fb20fb 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs @@ -341,7 +341,15 @@ pub fn unexpected_hidden_region_diagnostic<'tcx>( impl<'tcx> InferCtxt<'tcx> { pub fn get_impl_future_output_ty(&self, ty: Ty<'tcx>) -> Option> { - let ty::Opaque(def_id, substs) = *ty.kind() else { return None; }; + let (def_id, substs) = match *ty.kind() { + ty::Opaque(def_id, substs) => (def_id, substs), + ty::Projection(data) + if self.tcx.def_kind(data.item_def_id) == DefKind::ImplTraitPlaceholder => + { + (data.item_def_id, data.substs) + } + _ => return None, + }; let future_trait = self.tcx.require_lang_item(LangItem::Future, None); let item_def_id = self.tcx.associated_item_def_ids(future_trait)[0]; diff --git a/src/test/ui/async-await/in-trait/return-type-suggestion.rs b/src/test/ui/async-await/in-trait/return-type-suggestion.rs new file mode 100644 index 0000000000000..3446761d119da --- /dev/null +++ b/src/test/ui/async-await/in-trait/return-type-suggestion.rs @@ -0,0 +1,14 @@ +// edition: 2021 + +#![feature(async_fn_in_trait)] +//~^ WARN the feature `async_fn_in_trait` is incomplete and may not be safe to use and/or cause compiler crashes + +trait A { + async fn e() { + Ok(()) + //~^ ERROR mismatched types + //~| HELP consider using a semicolon here + } +} + +fn main() {} diff --git a/src/test/ui/async-await/in-trait/return-type-suggestion.stderr b/src/test/ui/async-await/in-trait/return-type-suggestion.stderr new file mode 100644 index 0000000000000..5a9b15e54a008 --- /dev/null +++ b/src/test/ui/async-await/in-trait/return-type-suggestion.stderr @@ -0,0 +1,23 @@ +warning: the feature `async_fn_in_trait` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/return-type-suggestion.rs:3:12 + | +LL | #![feature(async_fn_in_trait)] + | ^^^^^^^^^^^^^^^^^ + | + = note: see issue #91611 for more information + = note: `#[warn(incomplete_features)]` on by default + +error[E0308]: mismatched types + --> $DIR/return-type-suggestion.rs:8:9 + | +LL | Ok(()) + | ^^^^^^- help: consider using a semicolon here: `;` + | | + | expected `()`, found enum `Result` + | + = note: expected unit type `()` + found enum `Result<(), _>` + +error: aborting due to previous error; 1 warning emitted + +For more information about this error, try `rustc --explain E0308`. From c9bab74fb2cb494b99edba2f8b999dfe9281c548 Mon Sep 17 00:00:00 2001 From: Boxy Date: Mon, 5 Dec 2022 23:17:55 +0000 Subject: [PATCH 13/19] support `Expr` in `is_const_evaluatable` and `compute` --- .../src/traits/const_evaluatable.rs | 58 +++++++++++++------ .../rustc_trait_selection/src/traits/wf.rs | 19 +++++- .../const_kind_expr/wf_obligation.rs | 22 +++++++ .../const_kind_expr/wf_obligation.stderr | 20 +++++++ 4 files changed, 99 insertions(+), 20 deletions(-) create mode 100644 src/test/ui/const-generics/generic_const_exprs/const_kind_expr/wf_obligation.rs create mode 100644 src/test/ui/const-generics/generic_const_exprs/const_kind_expr/wf_obligation.stderr diff --git a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs index e9e65336299e4..d01c6bac2963b 100644 --- a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs +++ b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs @@ -25,15 +25,13 @@ use crate::traits::ObligationCtxt; #[instrument(skip(infcx), level = "debug")] pub fn is_const_evaluatable<'tcx>( infcx: &InferCtxt<'tcx>, - ct: ty::Const<'tcx>, + unexpanded_ct: ty::Const<'tcx>, param_env: ty::ParamEnv<'tcx>, span: Span, ) -> Result<(), NotConstEvaluatable> { let tcx = infcx.tcx; - let uv = match ct.kind() { - ty::ConstKind::Unevaluated(uv) => uv, - // FIXME(generic_const_exprs): this seems wrong but I couldn't find a way to get this to trigger - ty::ConstKind::Expr(_) => bug!("unexpected expr in `is_const_evaluatable: {ct:?}"), + match unexpanded_ct.kind() { + ty::ConstKind::Unevaluated(_) | ty::ConstKind::Expr(_) => (), ty::ConstKind::Param(_) | ty::ConstKind::Bound(_, _) | ty::ConstKind::Placeholder(_) @@ -43,7 +41,7 @@ pub fn is_const_evaluatable<'tcx>( }; if tcx.features().generic_const_exprs { - let ct = tcx.expand_abstract_consts(ct); + let ct = tcx.expand_abstract_consts(unexpanded_ct); let is_anon_ct = if let ty::ConstKind::Unevaluated(uv) = ct.kind() { tcx.def_kind(uv.def.did) == DefKind::AnonConst @@ -62,18 +60,40 @@ pub fn is_const_evaluatable<'tcx>( } } - let concrete = infcx.const_eval_resolve(param_env, uv, Some(span)); - match concrete { - Err(ErrorHandled::TooGeneric) => Err(NotConstEvaluatable::Error( - infcx - .tcx - .sess - .delay_span_bug(span, "Missing value for constant, but no error reported?"), - )), - Err(ErrorHandled::Reported(e)) => Err(NotConstEvaluatable::Error(e)), - Ok(_) => Ok(()), + match unexpanded_ct.kind() { + ty::ConstKind::Expr(_) => { + // FIXME(generic_const_exprs): we have a `ConstKind::Expr` which is fully concrete, but + // currently it is not possible to evaluate `ConstKind::Expr` so we are unable to tell if it + // is evaluatable or not. For now we just ICE until this is implemented this. + Err(NotConstEvaluatable::Error(tcx.sess.delay_span_bug( + span, + "evaluating `ConstKind::Expr` is not currently supported", + ))) + } + ty::ConstKind::Unevaluated(uv) => { + let concrete = infcx.const_eval_resolve(param_env, uv, Some(span)); + match concrete { + Err(ErrorHandled::TooGeneric) => { + Err(NotConstEvaluatable::Error(infcx.tcx.sess.delay_span_bug( + span, + "Missing value for constant, but no error reported?", + ))) + } + Err(ErrorHandled::Reported(e)) => Err(NotConstEvaluatable::Error(e)), + Ok(_) => Ok(()), + } + } + _ => bug!("unexpected constkind in `is_const_evalautable: {unexpanded_ct:?}`"), } } else { + let uv = match unexpanded_ct.kind() { + ty::ConstKind::Unevaluated(uv) => uv, + ty::ConstKind::Expr(_) => { + bug!("`ConstKind::Expr` without `feature(generic_const_exprs)` enabled") + } + _ => bug!("unexpected constkind in `is_const_evalautable: {unexpanded_ct:?}`"), + }; + // FIXME: We should only try to evaluate a given constant here if it is fully concrete // as we don't want to allow things like `[u8; std::mem::size_of::<*mut T>()]`. // @@ -92,7 +112,7 @@ pub fn is_const_evaluatable<'tcx>( && satisfied_from_param_env( tcx, infcx, - tcx.expand_abstract_consts(ct), + tcx.expand_abstract_consts(unexpanded_ct), param_env, ) => { @@ -152,6 +172,7 @@ fn satisfied_from_param_env<'tcx>( impl<'a, 'tcx> TypeVisitor<'tcx> for Visitor<'a, 'tcx> { type BreakTy = (); fn visit_const(&mut self, c: ty::Const<'tcx>) -> ControlFlow { + debug!("is_const_evaluatable: candidate={:?}", c); if let Ok(()) = self.infcx.commit_if_ok(|_| { let ocx = ObligationCtxt::new_in_snapshot(self.infcx); if let Ok(()) = ocx.eq(&ObligationCause::dummy(), self.param_env, c.ty(), self.ct.ty()) @@ -187,7 +208,7 @@ fn satisfied_from_param_env<'tcx>( let result = b_ct.visit_with(&mut v); if let ControlFlow::Break(()) = result { - debug!("is_const_evaluatable: abstract_const ~~> ok"); + debug!("is_const_evaluatable: yes"); return true; } } @@ -195,5 +216,6 @@ fn satisfied_from_param_env<'tcx>( } } + debug!("is_const_evaluatable: no"); false } diff --git a/compiler/rustc_trait_selection/src/traits/wf.rs b/compiler/rustc_trait_selection/src/traits/wf.rs index 0855d6d19736f..e47ba64245f50 100644 --- a/compiler/rustc_trait_selection/src/traits/wf.rs +++ b/compiler/rustc_trait_selection/src/traits/wf.rs @@ -476,9 +476,24 @@ impl<'tcx> WfPredicates<'tcx> { ty::Binder::dummy(ty::PredicateKind::WellFormed(ct.into())), )); } - // FIXME(generic_const_exprs): This seems wrong but I could not find a way to get this to trigger ty::ConstKind::Expr(_) => { - bug!("checking wfness of `ConstKind::Expr` is unsupported") + // FIXME(generic_const_exprs): this doesnt verify that given `Expr(N + 1)` the + // trait bound `typeof(N): Add` holds. This is currently unnecessary + // as `ConstKind::Expr` is only produced via normalization of `ConstKind::Unevaluated` + // which means that the `DefId` would have been typeck'd elsewhere. However in + // the future we may allow directly lowering to `ConstKind::Expr` in which case + // we would not be proving bounds we should. + + let predicate = + ty::Binder::dummy(ty::PredicateKind::ConstEvaluatable(ct)); + let cause = self.cause(traits::WellFormed(None)); + self.out.push(traits::Obligation::with_depth( + self.tcx(), + cause, + self.recursion_depth, + self.param_env, + predicate, + )); } ty::ConstKind::Error(_) diff --git a/src/test/ui/const-generics/generic_const_exprs/const_kind_expr/wf_obligation.rs b/src/test/ui/const-generics/generic_const_exprs/const_kind_expr/wf_obligation.rs new file mode 100644 index 0000000000000..6093fc70b1696 --- /dev/null +++ b/src/test/ui/const-generics/generic_const_exprs/const_kind_expr/wf_obligation.rs @@ -0,0 +1,22 @@ +#![feature(generic_const_exprs, generic_arg_infer)] +#![allow(incomplete_features)] + +// minimized repro for #105205 +// +// the `foo::<_, L>` call results in a `WellFormed(_)` obligation and a +// `ConstEvaluatable(Unevaluated(_ + 1 + L))` obligation. Attempting to fulfill the latter +// unifies the `_` with `Expr(L - 1)` from the paramenv which turns the `WellFormed` +// obligation into `WellFormed(Expr(L - 1))` + +fn foo(_: [(); N + 1 + M]) {} + +fn ice() +where + [(); (L - 1) + 1 + L]:, +{ + foo::<_, L>([(); L + 1 + L]); + //~^ ERROR: mismatched types + //~^^ ERROR: unconstrained generic constant +} + +fn main() {} diff --git a/src/test/ui/const-generics/generic_const_exprs/const_kind_expr/wf_obligation.stderr b/src/test/ui/const-generics/generic_const_exprs/const_kind_expr/wf_obligation.stderr new file mode 100644 index 0000000000000..da5194696e657 --- /dev/null +++ b/src/test/ui/const-generics/generic_const_exprs/const_kind_expr/wf_obligation.stderr @@ -0,0 +1,20 @@ +error[E0308]: mismatched types + --> $DIR/wf_obligation.rs:17:17 + | +LL | foo::<_, L>([(); L + 1 + L]); + | ^^^^^^^^^^^^^^^ expected `N + 1 + M`, found `L + 1 + L` + | + = note: expected constant `N + 1 + M` + found constant `L + 1 + L` + +error: unconstrained generic constant + --> $DIR/wf_obligation.rs:17:22 + | +LL | foo::<_, L>([(); L + 1 + L]); + | ^^^^^^^^^ + | + = help: try adding a `where` bound using this expression: `where [(); L + 1 + L]:` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0308`. From e940f845be13ff37d4ba28df5f40d74e5b0895a0 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Tue, 6 Dec 2022 00:00:01 +0000 Subject: [PATCH 14/19] drive-by: Default param for ToPredicate --- .../rustc_borrowck/src/type_check/canonical.rs | 6 ++---- compiler/rustc_middle/src/ty/mod.rs | 18 +++++++++--------- .../rustc_trait_selection/src/traits/mod.rs | 2 +- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/canonical.rs b/compiler/rustc_borrowck/src/type_check/canonical.rs index 1aad6738bba7a..3617bf58be9dd 100644 --- a/compiler/rustc_borrowck/src/type_check/canonical.rs +++ b/compiler/rustc_borrowck/src/type_check/canonical.rs @@ -121,9 +121,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { pub(super) fn prove_predicates( &mut self, - predicates: impl IntoIterator< - Item = impl ToPredicate<'tcx, ty::Predicate<'tcx>> + std::fmt::Debug, - >, + predicates: impl IntoIterator + std::fmt::Debug>, locations: Locations, category: ConstraintCategory<'tcx>, ) { @@ -135,7 +133,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { #[instrument(skip(self), level = "debug")] pub(super) fn prove_predicate( &mut self, - predicate: impl ToPredicate<'tcx, ty::Predicate<'tcx>> + std::fmt::Debug, + predicate: impl ToPredicate<'tcx> + std::fmt::Debug, locations: Locations, category: ConstraintCategory<'tcx>, ) { diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index dd4ab3e8d30bf..81847e2e4cab6 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -1150,8 +1150,8 @@ impl<'tcx> ToPolyTraitRef<'tcx> for PolyTraitPredicate<'tcx> { } } -pub trait ToPredicate<'tcx, Predicate> { - fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate; +pub trait ToPredicate<'tcx, P = Predicate<'tcx>> { + fn to_predicate(self, tcx: TyCtxt<'tcx>) -> P; } impl<'tcx, T> ToPredicate<'tcx, T> for T { @@ -1160,21 +1160,21 @@ impl<'tcx, T> ToPredicate<'tcx, T> for T { } } -impl<'tcx> ToPredicate<'tcx, Predicate<'tcx>> for Binder<'tcx, PredicateKind<'tcx>> { +impl<'tcx> ToPredicate<'tcx> for Binder<'tcx, PredicateKind<'tcx>> { #[inline(always)] fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { tcx.mk_predicate(self) } } -impl<'tcx> ToPredicate<'tcx, Predicate<'tcx>> for Clause<'tcx> { +impl<'tcx> ToPredicate<'tcx> for Clause<'tcx> { #[inline(always)] fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { tcx.mk_predicate(ty::Binder::dummy(ty::PredicateKind::Clause(self))) } } -impl<'tcx> ToPredicate<'tcx, Predicate<'tcx>> for Binder<'tcx, TraitRef<'tcx>> { +impl<'tcx> ToPredicate<'tcx> for Binder<'tcx, TraitRef<'tcx>> { #[inline(always)] fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { let pred: PolyTraitPredicate<'tcx> = self.to_predicate(tcx); @@ -1193,25 +1193,25 @@ impl<'tcx> ToPredicate<'tcx, PolyTraitPredicate<'tcx>> for Binder<'tcx, TraitRef } } -impl<'tcx> ToPredicate<'tcx, Predicate<'tcx>> for PolyTraitPredicate<'tcx> { +impl<'tcx> ToPredicate<'tcx> for PolyTraitPredicate<'tcx> { fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { self.map_bound(|p| PredicateKind::Clause(Clause::Trait(p))).to_predicate(tcx) } } -impl<'tcx> ToPredicate<'tcx, Predicate<'tcx>> for PolyRegionOutlivesPredicate<'tcx> { +impl<'tcx> ToPredicate<'tcx> for PolyRegionOutlivesPredicate<'tcx> { fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { self.map_bound(|p| PredicateKind::Clause(Clause::RegionOutlives(p))).to_predicate(tcx) } } -impl<'tcx> ToPredicate<'tcx, Predicate<'tcx>> for PolyTypeOutlivesPredicate<'tcx> { +impl<'tcx> ToPredicate<'tcx> for PolyTypeOutlivesPredicate<'tcx> { fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { self.map_bound(|p| PredicateKind::Clause(Clause::TypeOutlives(p))).to_predicate(tcx) } } -impl<'tcx> ToPredicate<'tcx, Predicate<'tcx>> for PolyProjectionPredicate<'tcx> { +impl<'tcx> ToPredicate<'tcx> for PolyProjectionPredicate<'tcx> { fn to_predicate(self, tcx: TyCtxt<'tcx>) -> Predicate<'tcx> { self.map_bound(|p| PredicateKind::Clause(Clause::Projection(p))).to_predicate(tcx) } diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs index c6818a4e57d42..d3cfd61e1956d 100644 --- a/compiler/rustc_trait_selection/src/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/mod.rs @@ -150,7 +150,7 @@ pub fn type_known_to_meet_bound_modulo_regions<'tcx>( fn pred_known_to_hold_modulo_regions<'tcx>( infcx: &InferCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, - pred: impl ToPredicate<'tcx, ty::Predicate<'tcx>> + TypeVisitable<'tcx>, + pred: impl ToPredicate<'tcx> + TypeVisitable<'tcx>, span: Span, ) -> bool { let has_non_region_infer = pred.has_non_region_infer(); From d2a80c157145d0c1e6fd6669862358de6cd89185 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Tue, 6 Dec 2022 00:19:42 +0000 Subject: [PATCH 15/19] Avoid noting cause code (which is usually misc, b/c codegen) for opaque type reveal overflow --- .../src/traits/codegen.rs | 2 +- .../src/traits/error_reporting/mod.rs | 84 ++++++++++++++----- .../src/traits/error_reporting/suggestions.rs | 4 +- .../src/traits/project.rs | 12 ++- .../src/traits/query/normalize.rs | 14 ++-- .../src/traits/select/mod.rs | 13 +-- 6 files changed, 80 insertions(+), 49 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/codegen.rs b/compiler/rustc_trait_selection/src/traits/codegen.rs index 61743d78e9e80..0102d268b42e1 100644 --- a/compiler/rustc_trait_selection/src/traits/codegen.rs +++ b/compiler/rustc_trait_selection/src/traits/codegen.rs @@ -70,7 +70,7 @@ pub fn codegen_select_candidate<'tcx>( // `rustc_ty_utils::resolve_associated_item` doesn't return `None` post-monomorphization. for err in errors { if let FulfillmentErrorCode::CodeCycle(cycle) = err.code { - infcx.err_ctxt().report_overflow_error_cycle(&cycle); + infcx.err_ctxt().report_overflow_obligation_cycle(&cycle); } } return Err(CodegenObligationError::FulfillmentError); diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs index 56dea916b305f..80870d871d163 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs @@ -99,26 +99,36 @@ pub trait InferCtxtExt<'tcx> { } pub trait TypeErrCtxtExt<'tcx> { + fn report_overflow_error( + &self, + predicate: &T, + span: Span, + suggest_increasing_limit: bool, + mutate: impl FnOnce(&mut Diagnostic), + ) -> ! + where + T: fmt::Display + + TypeFoldable<'tcx> + + Print<'tcx, FmtPrinter<'tcx, 'tcx>, Output = FmtPrinter<'tcx, 'tcx>>, + >>::Error: std::fmt::Debug; + fn report_fulfillment_errors( &self, errors: &[FulfillmentError<'tcx>], body_id: Option, ) -> ErrorGuaranteed; - fn report_overflow_error( + fn report_overflow_obligation( &self, obligation: &Obligation<'tcx, T>, suggest_increasing_limit: bool, ) -> ! where - T: fmt::Display - + TypeFoldable<'tcx> - + Print<'tcx, FmtPrinter<'tcx, 'tcx>, Output = FmtPrinter<'tcx, 'tcx>>, - >>::Error: std::fmt::Debug; + T: ToPredicate<'tcx> + Clone; fn suggest_new_overflow_limit(&self, err: &mut Diagnostic); - fn report_overflow_error_cycle(&self, cycle: &[PredicateObligation<'tcx>]) -> !; + fn report_overflow_obligation_cycle(&self, cycle: &[PredicateObligation<'tcx>]) -> !; /// The `root_obligation` parameter should be the `root_obligation` field /// from a `FulfillmentError`. If no `FulfillmentError` is available, @@ -458,8 +468,10 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { /// occurrences in any case. fn report_overflow_error( &self, - obligation: &Obligation<'tcx, T>, + predicate: &T, + span: Span, suggest_increasing_limit: bool, + mutate: impl FnOnce(&mut Diagnostic), ) -> ! where T: fmt::Display @@ -467,8 +479,9 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { + Print<'tcx, FmtPrinter<'tcx, 'tcx>, Output = FmtPrinter<'tcx, 'tcx>>, >>::Error: std::fmt::Debug, { - let predicate = self.resolve_vars_if_possible(obligation.predicate.clone()); + let predicate = self.resolve_vars_if_possible(predicate.clone()); let mut pred_str = predicate.to_string(); + if pred_str.len() > 50 { // We don't need to save the type to a file, we will be talking about this type already // in a separate note when we explain the obligation, so it will be available that way. @@ -483,7 +496,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } let mut err = struct_span_err!( self.tcx.sess, - obligation.cause.span, + span, E0275, "overflow evaluating the requirement `{}`", pred_str, @@ -493,20 +506,46 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { self.suggest_new_overflow_limit(&mut err); } - self.note_obligation_cause_code( - &mut err, - &obligation.predicate, - obligation.param_env, - obligation.cause.code(), - &mut vec![], - &mut Default::default(), - ); + mutate(&mut err); err.emit(); self.tcx.sess.abort_if_errors(); bug!(); } + /// Reports that an overflow has occurred and halts compilation. We + /// halt compilation unconditionally because it is important that + /// overflows never be masked -- they basically represent computations + /// whose result could not be truly determined and thus we can't say + /// if the program type checks or not -- and they are unusual + /// occurrences in any case. + fn report_overflow_obligation( + &self, + obligation: &Obligation<'tcx, T>, + suggest_increasing_limit: bool, + ) -> ! + where + T: ToPredicate<'tcx> + Clone, + { + let predicate = obligation.predicate.clone().to_predicate(self.tcx); + let predicate = self.resolve_vars_if_possible(predicate); + self.report_overflow_error( + &predicate, + obligation.cause.span, + suggest_increasing_limit, + |err| { + self.note_obligation_cause_code( + err, + &predicate, + obligation.param_env, + obligation.cause.code(), + &mut vec![], + &mut Default::default(), + ); + }, + ); + } + fn suggest_new_overflow_limit(&self, err: &mut Diagnostic) { let suggested_limit = match self.tcx.recursion_limit() { Limit(0) => Limit(2), @@ -521,11 +560,11 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { } /// Reports that a cycle was detected which led to overflow and halts - /// compilation. This is equivalent to `report_overflow_error` except + /// compilation. This is equivalent to `report_overflow_obligation` except /// that we can give a more helpful error message (and, in particular, /// we do not suggest increasing the overflow limit, which is not /// going to help). - fn report_overflow_error_cycle(&self, cycle: &[PredicateObligation<'tcx>]) -> ! { + fn report_overflow_obligation_cycle(&self, cycle: &[PredicateObligation<'tcx>]) -> ! { let cycle = self.resolve_vars_if_possible(cycle.to_owned()); assert!(!cycle.is_empty()); @@ -533,7 +572,10 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { // The 'deepest' obligation is most likely to have a useful // cause 'backtrace' - self.report_overflow_error(cycle.iter().max_by_key(|p| p.recursion_depth).unwrap(), false); + self.report_overflow_obligation( + cycle.iter().max_by_key(|p| p.recursion_depth).unwrap(), + false, + ); } fn report_selection_error( @@ -1554,7 +1596,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { diag.emit(); } FulfillmentErrorCode::CodeCycle(ref cycle) => { - self.report_overflow_error_cycle(cycle); + self.report_overflow_obligation_cycle(cycle); } } } diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs index 1740128727a5a..6ea54b625bbc0 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs @@ -298,7 +298,7 @@ pub trait TypeErrCtxtExt<'tcx> { obligated_types: &mut Vec>, seen_requirements: &mut FxHashSet, ) where - T: fmt::Display + ToPredicate<'tcx, T>; + T: fmt::Display + ToPredicate<'tcx>; /// Suggest to await before try: future? => future.await? fn suggest_await_before_try( @@ -2353,7 +2353,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { obligated_types: &mut Vec>, seen_requirements: &mut FxHashSet, ) where - T: fmt::Display, + T: fmt::Display + ToPredicate<'tcx>, { let tcx = self.tcx; match *cause_code { diff --git a/compiler/rustc_trait_selection/src/traits/project.rs b/compiler/rustc_trait_selection/src/traits/project.rs index 051660be9c474..1790ef5b4814e 100644 --- a/compiler/rustc_trait_selection/src/traits/project.rs +++ b/compiler/rustc_trait_selection/src/traits/project.rs @@ -504,14 +504,12 @@ impl<'a, 'b, 'tcx> TypeFolder<'tcx> for AssocTypeNormalizer<'a, 'b, 'tcx> { Reveal::All => { let recursion_limit = self.tcx().recursion_limit(); if !recursion_limit.value_within_limit(self.depth) { - let obligation = Obligation::with_depth( - self.tcx(), - self.cause.clone(), - recursion_limit.0, - self.param_env, - ty, + self.selcx.infcx.err_ctxt().report_overflow_error( + &ty, + self.cause.span, + true, + |_| {}, ); - self.selcx.infcx.err_ctxt().report_overflow_error(&obligation, true); } let substs = substs.fold_with(self); diff --git a/compiler/rustc_trait_selection/src/traits/query/normalize.rs b/compiler/rustc_trait_selection/src/traits/query/normalize.rs index f899321fc01e1..7ad532d8a3464 100644 --- a/compiler/rustc_trait_selection/src/traits/query/normalize.rs +++ b/compiler/rustc_trait_selection/src/traits/query/normalize.rs @@ -7,7 +7,7 @@ use crate::infer::canonical::OriginalQueryValues; use crate::infer::{InferCtxt, InferOk}; use crate::traits::error_reporting::TypeErrCtxtExt; use crate::traits::project::{needs_normalization, BoundVarReplacer, PlaceholderReplacer}; -use crate::traits::{Obligation, ObligationCause, PredicateObligation, Reveal}; +use crate::traits::{ObligationCause, PredicateObligation, Reveal}; use rustc_data_structures::sso::SsoHashMap; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_infer::traits::Normalized; @@ -214,14 +214,12 @@ impl<'cx, 'tcx> FallibleTypeFolder<'tcx> for QueryNormalizer<'cx, 'tcx> { let substs = substs.try_fold_with(self)?; let recursion_limit = self.tcx().recursion_limit(); if !recursion_limit.value_within_limit(self.anon_depth) { - let obligation = Obligation::with_depth( - self.tcx(), - self.cause.clone(), - recursion_limit.0, - self.param_env, - ty, + self.infcx.err_ctxt().report_overflow_error( + &ty, + self.cause.span, + true, + |_| {}, ); - self.infcx.err_ctxt().report_overflow_error(&obligation, true); } let generic_ty = self.tcx().bound_type_of(def_id); diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs index 8835f2cc1b97a..035deb6163981 100644 --- a/compiler/rustc_trait_selection/src/traits/select/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs @@ -43,7 +43,6 @@ use rustc_middle::mir::interpret::ErrorHandled; use rustc_middle::ty::abstract_const::NotConstEvaluatable; use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; use rustc_middle::ty::fold::BottomUpFolder; -use rustc_middle::ty::print::{FmtPrinter, Print}; use rustc_middle::ty::relate::TypeRelation; use rustc_middle::ty::SubstsRef; use rustc_middle::ty::{self, EarlyBinder, PolyProjectionPredicate, ToPolyTraitRef, ToPredicate}; @@ -1313,10 +1312,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { error_obligation: &Obligation<'tcx, T>, ) -> Result<(), OverflowError> where - T: fmt::Display - + TypeFoldable<'tcx> - + Print<'tcx, FmtPrinter<'tcx, 'tcx>, Output = FmtPrinter<'tcx, 'tcx>>, - >>::Error: std::fmt::Debug, + T: ToPredicate<'tcx> + Clone, { if !self.infcx.tcx.recursion_limit().value_within_limit(depth) { match self.query_mode { @@ -1324,7 +1320,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { if let Some(e) = self.infcx.tainted_by_errors() { return Err(OverflowError::Error(e)); } - self.infcx.err_ctxt().report_overflow_error(error_obligation, true); + self.infcx.err_ctxt().report_overflow_obligation(error_obligation, true); } TraitQueryMode::Canonical => { return Err(OverflowError::Canonical); @@ -1345,10 +1341,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { error_obligation: &Obligation<'tcx, V>, ) -> Result<(), OverflowError> where - V: fmt::Display - + TypeFoldable<'tcx> - + Print<'tcx, FmtPrinter<'tcx, 'tcx>, Output = FmtPrinter<'tcx, 'tcx>>, - >>::Error: std::fmt::Debug, + V: ToPredicate<'tcx> + Clone, { self.check_recursion_depth(obligation.recursion_depth, error_obligation) } From 6dc2aa26759578f09974041f19767a75b8c6320e Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Tue, 6 Dec 2022 01:52:02 +0000 Subject: [PATCH 16/19] Add GenericParamDef::to_error and InternalSubsts::extend_with_error --- compiler/rustc_middle/src/ty/generics.rs | 14 ++++++++++++++ compiler/rustc_middle/src/ty/sty.rs | 15 ++------------- compiler/rustc_middle/src/ty/subst.rs | 16 ++++++++++++++++ 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_middle/src/ty/generics.rs b/compiler/rustc_middle/src/ty/generics.rs index a8da93e4c69b0..48329da3e6332 100644 --- a/compiler/rustc_middle/src/ty/generics.rs +++ b/compiler/rustc_middle/src/ty/generics.rs @@ -101,6 +101,20 @@ impl GenericParamDef { _ => None, } } + + pub fn to_error<'tcx>( + &self, + tcx: TyCtxt<'tcx>, + preceding_substs: &[ty::GenericArg<'tcx>], + ) -> ty::GenericArg<'tcx> { + match &self.kind { + ty::GenericParamDefKind::Lifetime => tcx.lifetimes.re_static.into(), + ty::GenericParamDefKind::Type { .. } => tcx.ty_error().into(), + ty::GenericParamDefKind::Const { .. } => { + tcx.const_error(tcx.bound_type_of(self.def_id).subst(tcx, preceding_substs)).into() + } + } + } } #[derive(Default)] diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index 8e69824e6c790..1eec119616ea7 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -728,19 +728,8 @@ impl<'tcx> PolyExistentialPredicate<'tcx> { } else { // If this is an ill-formed auto trait, then synthesize // new error substs for the missing generics. - let err_substs = ty::InternalSubsts::for_item(tcx, did, |def, substs| { - if def.index == 0 { - self_ty.into() - } else { - match &def.kind { - ty::GenericParamDefKind::Lifetime => tcx.lifetimes.re_static.into(), - ty::GenericParamDefKind::Type { .. } => tcx.ty_error().into(), - ty::GenericParamDefKind::Const { .. } => tcx - .const_error(tcx.bound_type_of(def.def_id).subst(tcx, substs)) - .into(), - } - } - }); + let err_substs = + ty::InternalSubsts::extend_with_error(tcx, did, &[self_ty.into()]); tcx.mk_trait_ref(did, err_substs) }; self.rebind(trait_ref).without_const().to_predicate(tcx) diff --git a/compiler/rustc_middle/src/ty/subst.rs b/compiler/rustc_middle/src/ty/subst.rs index 141c8354c183d..23507d2804592 100644 --- a/compiler/rustc_middle/src/ty/subst.rs +++ b/compiler/rustc_middle/src/ty/subst.rs @@ -352,6 +352,22 @@ impl<'tcx> InternalSubsts<'tcx> { } } + // Extend an `original_substs` list to the full number of substs expected by `def_id`, + // filling in the missing parameters with error ty/ct or 'static regions. + pub fn extend_with_error( + tcx: TyCtxt<'tcx>, + def_id: DefId, + original_substs: &[GenericArg<'tcx>], + ) -> SubstsRef<'tcx> { + ty::InternalSubsts::for_item(tcx, def_id, |def, substs| { + if let Some(subst) = original_substs.get(def.index as usize) { + *subst + } else { + def.to_error(tcx, substs) + } + }) + } + #[inline] pub fn types(&'tcx self) -> impl DoubleEndedIterator> + 'tcx { self.iter() From eff76455fd4061f268546a706d3dfb1cc34ecb48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 5 Dec 2022 16:13:32 -0800 Subject: [PATCH 17/19] Avoid ICE by accounting for missing type Fix #105330 --- .../src/traits/error_reporting/mod.rs | 2 +- src/test/ui/issues/issue-105330.rs | 21 ++++ src/test/ui/issues/issue-105330.stderr | 109 ++++++++++++++++++ 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 src/test/ui/issues/issue-105330.rs create mode 100644 src/test/ui/issues/issue-105330.stderr diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs index 3379279dd1544..fcf0a94462e10 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs @@ -1607,7 +1607,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> { | ObligationCauseCode::ObjectCastObligation(..) | ObligationCauseCode::OpaqueType ); - let expected_ty = data.term.ty().unwrap(); + let expected_ty = data.term.ty().unwrap_or_else(|| self.tcx.ty_error()); // constrain inference variables a bit more to nested obligations from normalize so // we can have more helpful errors. diff --git a/src/test/ui/issues/issue-105330.rs b/src/test/ui/issues/issue-105330.rs new file mode 100644 index 0000000000000..86e45f10b0e16 --- /dev/null +++ b/src/test/ui/issues/issue-105330.rs @@ -0,0 +1,21 @@ +pub trait TraitWAssocConst { + const A: usize; +} +pub struct Demo {} + +impl TraitWAssocConst for impl Demo { //~ ERROR E0404 + //~^ ERROR E0562 + pubconst A: str = 32; //~ ERROR expected one of +} + +fn foo>() { //~ ERROR E0658 + foo::()(); //~ ERROR E0271 + //~^ ERROR E0618 + //~| ERROR E0277 +} + +fn main>() { //~ ERROR E0131 + //~^ ERROR E0658 + foo::(); //~ ERROR E0277 + //~^ ERROR E0271 +} diff --git a/src/test/ui/issues/issue-105330.stderr b/src/test/ui/issues/issue-105330.stderr new file mode 100644 index 0000000000000..92f2ccb6544b1 --- /dev/null +++ b/src/test/ui/issues/issue-105330.stderr @@ -0,0 +1,109 @@ +error: expected one of `!` or `::`, found `A` + --> $DIR/issue-105330.rs:8:14 + | +LL | impl TraitWAssocConst for impl Demo { + | - while parsing this item list starting here +LL | +LL | pubconst A: str = 32; + | ^ expected one of `!` or `::` +LL | } + | - the item list ends here + +error[E0404]: expected trait, found struct `Demo` + --> $DIR/issue-105330.rs:6:32 + | +LL | impl TraitWAssocConst for impl Demo { + | ^^^^ not a trait + +error[E0658]: associated const equality is incomplete + --> $DIR/issue-105330.rs:11:28 + | +LL | fn foo>() { + | ^^^^ + | + = note: see issue #92827 for more information + = help: add `#![feature(associated_const_equality)]` to the crate attributes to enable + +error[E0658]: associated const equality is incomplete + --> $DIR/issue-105330.rs:17:29 + | +LL | fn main>() { + | ^^^^ + | + = note: see issue #92827 for more information + = help: add `#![feature(associated_const_equality)]` to the crate attributes to enable + +error[E0562]: `impl Trait` only allowed in function and inherent method return types, not in type + --> $DIR/issue-105330.rs:6:27 + | +LL | impl TraitWAssocConst for impl Demo { + | ^^^^^^^^^ + +error[E0277]: the trait bound `Demo: TraitWAssocConst` is not satisfied + --> $DIR/issue-105330.rs:12:11 + | +LL | foo::()(); + | ^^^^ the trait `TraitWAssocConst` is not implemented for `Demo` + | +note: required by a bound in `foo` + --> $DIR/issue-105330.rs:11:11 + | +LL | fn foo>() { + | ^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `foo` + +error[E0271]: type mismatch resolving `::A == 32` + --> $DIR/issue-105330.rs:12:11 + | +LL | foo::()(); + | ^^^^ types differ + | +note: required by a bound in `foo` + --> $DIR/issue-105330.rs:11:28 + | +LL | fn foo>() { + | ^^^^ required by this bound in `foo` + +error[E0618]: expected function, found `()` + --> $DIR/issue-105330.rs:12:5 + | +LL | fn foo>() { + | ----------------------------------- `foo::` defined here returns `()` +LL | foo::()(); + | ^^^^^^^^^^^^^-- + | | + | call expression requires function + +error[E0277]: the trait bound `Demo: TraitWAssocConst` is not satisfied + --> $DIR/issue-105330.rs:19:11 + | +LL | foo::(); + | ^^^^ the trait `TraitWAssocConst` is not implemented for `Demo` + | +note: required by a bound in `foo` + --> $DIR/issue-105330.rs:11:11 + | +LL | fn foo>() { + | ^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `foo` + +error[E0271]: type mismatch resolving `::A == 32` + --> $DIR/issue-105330.rs:19:11 + | +LL | foo::(); + | ^^^^ types differ + | +note: required by a bound in `foo` + --> $DIR/issue-105330.rs:11:28 + | +LL | fn foo>() { + | ^^^^ required by this bound in `foo` + +error[E0131]: `main` function is not allowed to have generic parameters + --> $DIR/issue-105330.rs:17:8 + | +LL | fn main>() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `main` cannot have generic parameters + +error: aborting due to 11 previous errors + +Some errors have detailed explanations: E0131, E0271, E0277, E0404, E0562, E0618, E0658. +For more information about an error, try `rustc --explain E0131`. From e802165dfe346e92a3ef9b0e49634943df862a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 28 Nov 2022 00:03:57 -0800 Subject: [PATCH 18/19] On E0195 point at where clause lifetime bounds Fix #104733 --- .../locales/en-US/hir_analysis.ftl | 2 + .../src/check/compare_method.rs | 51 +++++++++++++++---- compiler/rustc_hir_analysis/src/errors.rs | 4 ++ ...ssing-where-clause-lifetimes-from-trait.rs | 30 +++++++++++ ...g-where-clause-lifetimes-from-trait.stderr | 27 ++++++++++ 5 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 src/test/ui/trait-bounds/impl-missing-where-clause-lifetimes-from-trait.rs create mode 100644 src/test/ui/trait-bounds/impl-missing-where-clause-lifetimes-from-trait.stderr diff --git a/compiler/rustc_error_messages/locales/en-US/hir_analysis.ftl b/compiler/rustc_error_messages/locales/en-US/hir_analysis.ftl index 0894bbcaad474..a5cb8a88819df 100644 --- a/compiler/rustc_error_messages/locales/en-US/hir_analysis.ftl +++ b/compiler/rustc_error_messages/locales/en-US/hir_analysis.ftl @@ -17,6 +17,8 @@ hir_analysis_lifetimes_or_bounds_mismatch_on_trait = lifetime parameters or bounds on {$item_kind} `{$ident}` do not match the trait declaration .label = lifetimes do not match {$item_kind} in trait .generics_label = lifetimes in impl do not match this {$item_kind} in trait + .where_label = this `where` clause might not match the one in the trait + .bounds_label = this bound might be missing in the impl hir_analysis_drop_impl_on_wrong_item = the `Drop` trait may only be implemented for local structs, enums, and unions diff --git a/compiler/rustc_hir_analysis/src/check/compare_method.rs b/compiler/rustc_hir_analysis/src/check/compare_method.rs index 82a77416a190c..1d6f9b2917651 100644 --- a/compiler/rustc_hir_analysis/src/check/compare_method.rs +++ b/compiler/rustc_hir_analysis/src/check/compare_method.rs @@ -751,17 +751,45 @@ fn check_region_bounds_on_impl_item<'tcx>( .get_generics(impl_m.def_id.expect_local()) .expect("expected impl item to have generics or else we can't compare them") .span; - let generics_span = if let Some(local_def_id) = trait_m.def_id.as_local() { - Some( - tcx.hir() - .get_generics(local_def_id) - .expect("expected trait item to have generics or else we can't compare them") - .span, - ) - } else { - None - }; + let mut generics_span = None; + let mut bounds_span = vec![]; + let mut where_span = None; + if let Some(trait_node) = tcx.hir().get_if_local(trait_m.def_id) + && let Some(trait_generics) = trait_node.generics() + { + generics_span = Some(trait_generics.span); + // FIXME: we could potentially look at the impl's bounds to not point at bounds that + // *are* present in the impl. + for p in trait_generics.predicates { + if let hir::WherePredicate::BoundPredicate(pred) = p { + for b in pred.bounds { + if let hir::GenericBound::Outlives(lt) = b { + bounds_span.push(lt.ident.span); + } + } + } + } + if let Some(impl_node) = tcx.hir().get_if_local(impl_m.def_id) + && let Some(impl_generics) = impl_node.generics() + { + let mut impl_bounds = 0; + for p in impl_generics.predicates { + if let hir::WherePredicate::BoundPredicate(pred) = p { + for b in pred.bounds { + if let hir::GenericBound::Outlives(_) = b { + impl_bounds += 1; + } + } + } + } + if impl_bounds == bounds_span.len() { + bounds_span = vec![]; + } else if impl_generics.has_where_clause_predicates { + where_span = Some(impl_generics.where_clause_span); + } + } + } let reported = tcx .sess .create_err(LifetimesOrBoundsMismatchOnTrait { @@ -769,9 +797,10 @@ fn check_region_bounds_on_impl_item<'tcx>( item_kind: assoc_item_kind_str(impl_m), ident: impl_m.ident(tcx), generics_span, + bounds_span, + where_span, }) .emit_unless(delay); - return Err(reported); } diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs index afbb27155a2f5..5156d432b5b37 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -43,6 +43,10 @@ pub struct LifetimesOrBoundsMismatchOnTrait { pub span: Span, #[label(generics_label)] pub generics_span: Option, + #[label(where_label)] + pub where_span: Option, + #[label(bounds_label)] + pub bounds_span: Vec, pub item_kind: &'static str, pub ident: Ident, } diff --git a/src/test/ui/trait-bounds/impl-missing-where-clause-lifetimes-from-trait.rs b/src/test/ui/trait-bounds/impl-missing-where-clause-lifetimes-from-trait.rs new file mode 100644 index 0000000000000..1994120ce1466 --- /dev/null +++ b/src/test/ui/trait-bounds/impl-missing-where-clause-lifetimes-from-trait.rs @@ -0,0 +1,30 @@ +trait Trait { + fn foo<'a, K>(self, _: T, _: K) where T: 'a, K: 'a; +} + +impl Trait<()> for () { + fn foo<'a, K>(self, _: (), _: K) where { //~ ERROR E0195 + todo!(); + } +} + +struct State; + +trait Foo { + fn foo<'a>(&self, state: &'a State) -> &'a T + where + T: 'a; +} + +impl Foo for F +where + F: Fn(&State) -> &T, +{ + fn foo<'a>(&self, state: &'a State) -> &'a T { //~ ERROR E0195 + self(state) + } +} + +fn main() { + ().foo((), ()); +} diff --git a/src/test/ui/trait-bounds/impl-missing-where-clause-lifetimes-from-trait.stderr b/src/test/ui/trait-bounds/impl-missing-where-clause-lifetimes-from-trait.stderr new file mode 100644 index 0000000000000..3a02369e2ee8a --- /dev/null +++ b/src/test/ui/trait-bounds/impl-missing-where-clause-lifetimes-from-trait.stderr @@ -0,0 +1,27 @@ +error[E0195]: lifetime parameters or bounds on method `foo` do not match the trait declaration + --> $DIR/impl-missing-where-clause-lifetimes-from-trait.rs:6:11 + | +LL | fn foo<'a, K>(self, _: T, _: K) where T: 'a, K: 'a; + | ------- -- -- this bound might be missing in the impl + | | | + | | this bound might be missing in the impl + | lifetimes in impl do not match this method in trait +... +LL | fn foo<'a, K>(self, _: (), _: K) where { + | ^^^^^^^ lifetimes do not match method in trait + +error[E0195]: lifetime parameters or bounds on method `foo` do not match the trait declaration + --> $DIR/impl-missing-where-clause-lifetimes-from-trait.rs:23:11 + | +LL | fn foo<'a>(&self, state: &'a State) -> &'a T + | ---- lifetimes in impl do not match this method in trait +LL | where +LL | T: 'a; + | -- this bound might be missing in the impl +... +LL | fn foo<'a>(&self, state: &'a State) -> &'a T { + | ^^^^ lifetimes do not match method in trait + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0195`. From 9ffd0868653ad5113962c6f2a42d5061afbcf493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 28 Nov 2022 14:04:46 -0800 Subject: [PATCH 19/19] review comment: add test case --- .../impl-missing-where-clause-lifetimes-from-trait.rs | 8 ++++++++ ...l-missing-where-clause-lifetimes-from-trait.stderr | 11 ++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/test/ui/trait-bounds/impl-missing-where-clause-lifetimes-from-trait.rs b/src/test/ui/trait-bounds/impl-missing-where-clause-lifetimes-from-trait.rs index 1994120ce1466..dcdbd02287371 100644 --- a/src/test/ui/trait-bounds/impl-missing-where-clause-lifetimes-from-trait.rs +++ b/src/test/ui/trait-bounds/impl-missing-where-clause-lifetimes-from-trait.rs @@ -25,6 +25,14 @@ where } } +trait Bar { + fn foo<'a>(&'a self) {} +} + +impl Bar for () { + fn foo<'a: 'a>(&'a self) {} //~ ERROR E0195 +} + fn main() { ().foo((), ()); } diff --git a/src/test/ui/trait-bounds/impl-missing-where-clause-lifetimes-from-trait.stderr b/src/test/ui/trait-bounds/impl-missing-where-clause-lifetimes-from-trait.stderr index 3a02369e2ee8a..e26cb22163f1e 100644 --- a/src/test/ui/trait-bounds/impl-missing-where-clause-lifetimes-from-trait.stderr +++ b/src/test/ui/trait-bounds/impl-missing-where-clause-lifetimes-from-trait.stderr @@ -22,6 +22,15 @@ LL | T: 'a; LL | fn foo<'a>(&self, state: &'a State) -> &'a T { | ^^^^ lifetimes do not match method in trait -error: aborting due to 2 previous errors +error[E0195]: lifetime parameters or bounds on method `foo` do not match the trait declaration + --> $DIR/impl-missing-where-clause-lifetimes-from-trait.rs:33:11 + | +LL | fn foo<'a>(&'a self) {} + | ---- lifetimes in impl do not match this method in trait +... +LL | fn foo<'a: 'a>(&'a self) {} + | ^^^^^^^^ lifetimes do not match method in trait + +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0195`.