diff --git a/Cargo.lock b/Cargo.lock index c034cbae9912b..c410fb26fd8a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -649,6 +649,7 @@ dependencies = [ "semver", "serde", "serde_json", + "smallvec", "tempfile", "toml 0.7.8", "unicode-normalization", diff --git a/src/tools/clippy/.gitignore b/src/tools/clippy/.gitignore index 181b71a658b9a..df950a0bc103b 100644 --- a/src/tools/clippy/.gitignore +++ b/src/tools/clippy/.gitignore @@ -4,6 +4,8 @@ rustc-ice-* # Used by CI to be able to push: /.github/deploy_key out +output.txt +rustc-ice* # Compiled files *.o diff --git a/src/tools/clippy/CHANGELOG.md b/src/tools/clippy/CHANGELOG.md index 9c9ea11408143..bc522817c4e34 100644 --- a/src/tools/clippy/CHANGELOG.md +++ b/src/tools/clippy/CHANGELOG.md @@ -5171,6 +5171,7 @@ Released 2018-09-13 [`borrow_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_as_ptr [`borrow_deref_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_deref_ref [`borrow_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_interior_mutable_const +[`borrow_pats`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_pats [`borrowed_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrowed_box [`box_collection`]: https://rust-lang.github.io/rust-clippy/master/index.html#box_collection [`box_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#box_default diff --git a/src/tools/clippy/clippy_lints/Cargo.toml b/src/tools/clippy/clippy_lints/Cargo.toml index 5e3a119337ccd..fd91bc15c8d98 100644 --- a/src/tools/clippy/clippy_lints/Cargo.toml +++ b/src/tools/clippy/clippy_lints/Cargo.toml @@ -17,8 +17,8 @@ declare_clippy_lint = { path = "../declare_clippy_lint" } itertools = "0.12" quine-mc_cluskey = "0.2" regex-syntax = "0.8" -serde = { version = "1.0", features = ["derive"] } -serde_json = { version = "1.0", optional = true } +serde = { version = "1.0", features = ["derive", "std"] } +serde_json = { version = "1.0" } tempfile = { version = "3.3.0", optional = true } toml = "0.7.3" regex = { version = "1.5", optional = true } @@ -27,6 +27,7 @@ unicode-script = { version = "0.5", default-features = false } semver = "1.0" rustc-semver = "1.1" url = "2.2" +smallvec = { version = "1.8.1", features = ["union", "may_dangle"] } [dev-dependencies] walkdir = "2.3" @@ -34,7 +35,7 @@ walkdir = "2.3" [features] deny-warnings = ["clippy_config/deny-warnings", "clippy_utils/deny-warnings"] # build clippy with internal lints enabled, off by default -internal = ["serde_json", "tempfile", "regex"] +internal = ["tempfile", "regex"] [package.metadata.rust-analyzer] # This crate uses #[feature(rustc_private)] diff --git a/src/tools/clippy/clippy_lints/src/assigning_clones.rs b/src/tools/clippy/clippy_lints/src/assigning_clones.rs index f0dafb1ae0d52..64cf7755c878d 100644 --- a/src/tools/clippy/clippy_lints/src/assigning_clones.rs +++ b/src/tools/clippy/clippy_lints/src/assigning_clones.rs @@ -2,9 +2,9 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::HirNode; use clippy_utils::sugg::Sugg; -use clippy_utils::{is_trait_method, path_to_local}; +use clippy_utils::{is_trait_method, local_is_initialized, path_to_local}; use rustc_errors::Applicability; -use rustc_hir::{self as hir, Expr, ExprKind, Node}; +use rustc_hir::{self as hir, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Instance, Mutability}; use rustc_session::impl_lint_pass; @@ -36,6 +36,7 @@ declare_clippy_lint! { /// Use instead: /// ```rust /// struct Thing; + /// /// impl Clone for Thing { /// fn clone(&self) -> Self { todo!() } /// fn clone_from(&mut self, other: &Self) { todo!() } @@ -163,9 +164,7 @@ fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallC // TODO: This check currently bails if the local variable has no initializer. // That is overly conservative - the lint should fire even if there was no initializer, // but the variable has been initialized before `lhs` was evaluated. - if let Some(Node::LetStmt(local)) = cx.tcx.hir().parent_id_iter(local).next().map(|p| cx.tcx.hir_node(p)) - && local.init.is_none() - { + if !local_is_initialized(cx, local) { return false; } } diff --git a/src/tools/clippy/clippy_lints/src/borrow_pats/body.rs b/src/tools/clippy/clippy_lints/src/borrow_pats/body.rs new file mode 100644 index 0000000000000..3d218d00a7bf1 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/borrow_pats/body.rs @@ -0,0 +1,294 @@ +//! This module analyzes the relationship between the function signature and +//! the internal dataflow. Specifically, it checks for the following things: +//! +//! - Might an owned argument be returned +//! - Are arguments stored in `&mut` loans +//! - Are dependent loans returned +//! - Might a returned loan be `'static` +//! - Are all returned values const +//! - Is the unit type returned +//! - How often do `&mut self` refs need to be `&mut` +//! - Are all the dependencies from the function signature used. + +#![warn(unused)] + +use super::prelude::*; +use super::{calc_fn_arg_relations, has_mut_ref, visit_body, BodyStats}; + +use clippy_utils::ty::for_each_region; + +mod pattern; +pub use pattern::*; +mod flow; +use flow::DfWalker; +use rustc_middle::ty::Region; + +#[derive(Debug)] +pub struct BodyAnalysis<'a, 'tcx> { + info: &'a AnalysisInfo<'tcx>, + pats: BTreeSet, + data_flow: IndexVec>, + stats: BodyStats, +} + +/// This indicates an assignment to `to`. In most cases, there is also a `from`. +#[derive(Debug, Clone)] +enum MutInfo { + /// A different place was copied or moved into this one + Place(Local), + Const, + Arg, + Calc, + /// This is typical for loans and function calls. + Dep(Vec), + /// A value was constructed from this data + Ctor(Vec), + /// This is not an assignment, but the notification that a mut borrow was created + Loan(Local), + MutRef(Local), +} + +impl<'a, 'tcx> BodyAnalysis<'a, 'tcx> { + fn new(info: &'a AnalysisInfo<'tcx>, arg_ctn: usize) -> Self { + let mut data_flow: IndexVec> = IndexVec::default(); + data_flow.resize(info.locals.len(), SmallVec::default()); + + (0..arg_ctn).for_each(|idx| data_flow[Local::from_usize(idx + 1)].push(MutInfo::Arg)); + + let mut pats = BTreeSet::default(); + if arg_ctn == 0 { + pats.insert(BodyPat::NoArguments); + } + + Self { + info, + pats, + data_flow, + stats: Default::default(), + } + } + + pub fn run( + info: &'a AnalysisInfo<'tcx>, + def_id: LocalDefId, + hir_sig: &rustc_hir::FnSig<'_>, + context: BodyContext, + ) -> (BodyInfo, BTreeSet) { + let mut anly = Self::new(info, hir_sig.decl.inputs.len()); + + visit_body(&mut anly, info); + anly.check_fn_relations(def_id); + + let body_info = BodyInfo::from_sig(hir_sig, info, context); + + anly.stats.arg_relation_possibly_missed_due_generics = + info.stats.borrow().arg_relation_possibly_missed_due_generics; + anly.stats.arg_relation_possibly_missed_due_to_late_bounds = + info.stats.borrow().arg_relation_possibly_missed_due_to_late_bounds; + info.stats.replace(anly.stats); + (body_info, anly.pats) + } + + fn check_fn_relations(&mut self, def_id: LocalDefId) { + let mut rels = calc_fn_arg_relations(self.info.cx.tcx, def_id); + let return_rels = rels.remove(&RETURN_LOCAL).unwrap_or_default(); + + // Argument relations + for (child, maybe_parents) in &rels { + self.check_arg_relation(*child, maybe_parents); + } + + self.check_return_relations(&return_rels, def_id); + } + + fn check_return_relations(&mut self, sig_parents: &[Local], def_id: LocalDefId) { + self.stats.return_relations_signature = sig_parents.len(); + + let arg_ctn = self.info.body.arg_count; + let args: Vec<_> = (0..arg_ctn).map(|i| Local::from(i + 1)).collect(); + + let mut checker = DfWalker::new(self.info, &self.data_flow, RETURN_LOCAL, &args); + checker.walk(); + + for arg in &args { + if checker.found_connection(*arg) { + // These two branches are mutually exclusive: + if sig_parents.contains(arg) { + self.stats.return_relations_found += 1; + } + // FIXME: It would be nice if we can say, if an argument was + // returned, but it feels like all we can say is that there is an connection between + // this and the other thing else if !self.info.body.local_decls[* + // arg].ty.is_ref() { println!("Track owned argument returned"); + // } + } + } + + // check for static returns + let mut all_regions_static = true; + let mut region_count = 0; + let fn_sig = self.info.cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder(); + for_each_region(fn_sig.output(), &mut |region: Region<'_>| { + region_count += 1; + all_regions_static &= region.is_static(); + }); + + // Check if there can be static returns + if region_count >= 1 && !all_regions_static { + let mut pending_return_df = std::mem::take(&mut self.data_flow[RETURN_LOCAL]); + let mut checked_return_df = SmallVec::with_capacity(pending_return_df.len()); + // We check for every assignment, if it's constant and therefore static + while let Some(return_df) = pending_return_df.pop() { + self.data_flow[RETURN_LOCAL].push(return_df); + + let mut checker = DfWalker::new(self.info, &self.data_flow, RETURN_LOCAL, &args); + checker.walk(); + let all_const = checker.all_const(); + + checked_return_df.push(self.data_flow[RETURN_LOCAL].pop().unwrap()); + + if all_const { + self.pats.insert(BodyPat::ReturnedStaticLoanForNonStatic); + break; + } + } + + checked_return_df.append(&mut pending_return_df); + self.data_flow[RETURN_LOCAL] = checked_return_df; + } + } + + fn check_arg_relation(&mut self, child: Local, maybe_parents: &[Local]) { + let mut checker = DfWalker::new(self.info, &self.data_flow, child, maybe_parents); + checker.walk(); + + self.stats.arg_relations_signature += maybe_parents.len(); + self.stats.arg_relations_found += checker.connection_count(); + } +} + +impl<'a, 'tcx> Visitor<'tcx> for BodyAnalysis<'a, 'tcx> { + fn visit_assign(&mut self, target: &Place<'tcx>, rval: &Rvalue<'tcx>, _loc: mir::Location) { + match rval { + Rvalue::Ref(_reg, BorrowKind::Fake(_), _src) => { + #[allow(clippy::needless_return)] + return; + }, + Rvalue::Ref(_reg, kind, src) => { + self.stats.ref_stmt_ctn += 1; + + let is_mut = matches!(kind, BorrowKind::Mut { .. }); + if is_mut { + self.data_flow[src.local].push(MutInfo::MutRef(target.local)); + } + if matches!(src.projection.as_slice(), [mir::PlaceElem::Deref]) { + // &(*_1) => Copy + self.data_flow[target.local].push(MutInfo::Place(src.local)); + return; + } + + // _1 = &_2 => simple loan + self.data_flow[target.local].push(MutInfo::Loan(src.local)); + }, + Rvalue::Cast(_, op, _) | Rvalue::Use(op) => { + let event = match &op { + Operand::Constant(_) => MutInfo::Const, + Operand::Copy(from) | Operand::Move(from) => MutInfo::Place(from.local), + }; + self.data_flow[target.local].push(event); + }, + Rvalue::CopyForDeref(from) => { + self.data_flow[target.local].push(MutInfo::Place(from.local)); + }, + Rvalue::Repeat(op, _) => { + let event = match &op { + Operand::Constant(_) => MutInfo::Const, + Operand::Copy(from) | Operand::Move(from) => MutInfo::Ctor(vec![from.local]), + }; + self.data_flow[target.local].push(event); + }, + // Constructed Values + Rvalue::Aggregate(_, fields) => { + let args = fields + .iter() + .filter_map(rustc_middle::mir::Operand::place) + .map(|place| place.local) + .collect(); + self.data_flow[target.local].push(MutInfo::Ctor(args)); + }, + // Casts should depend on the input data + Rvalue::ThreadLocalRef(_) + | Rvalue::NullaryOp(_, _) + | Rvalue::AddressOf(_, _) + | Rvalue::Discriminant(_) + | Rvalue::ShallowInitBox(_, _) + | Rvalue::Len(_) + | Rvalue::BinaryOp(_, _) + | Rvalue::UnaryOp(_, _) + | Rvalue::CheckedBinaryOp(_, _) => { + self.data_flow[target.local].push(MutInfo::Calc); + }, + } + } + + fn visit_terminator(&mut self, term: &Terminator<'tcx>, loc: Location) { + let TerminatorKind::Call { + destination: dest, + args, + .. + } = &term.kind + else { + return; + }; + + for arg in args { + if let Some(place) = arg.node.place() { + let ty = self.info.body.local_decls[place.local].ty; + if has_mut_ref(ty) { + self.data_flow[place.local].push(MutInfo::Calc); + } + } + } + + assert!(dest.just_local()); + self.data_flow[dest.local].push(MutInfo::Calc); + + let rels = &self.info.terms[&loc.block]; + for (target, sources) in rels { + self.data_flow[*target].push(MutInfo::Dep(sources.clone())); + } + } +} + +pub(crate) fn update_pats_from_stats(pats: &mut BTreeSet, info: &AnalysisInfo<'_>) { + let stats = info.stats.borrow(); + + if stats.ref_stmt_ctn > 0 { + pats.insert(BodyPat::Borrow); + } + + if stats.owned.named_borrow_count > 0 { + pats.insert(BodyPat::OwnedNamedBorrow); + } + if stats.owned.named_borrow_mut_count > 0 { + pats.insert(BodyPat::OwnedNamedBorrowMut); + } + + if stats.owned.arg_borrow_count > 0 { + pats.insert(BodyPat::OwnedArgBorrow); + } + if stats.owned.arg_borrow_mut_count > 0 { + pats.insert(BodyPat::OwnedArgBorrowMut); + } + + if stats.owned.two_phased_borrows > 0 { + pats.insert(BodyPat::OwnedTwoPhaseBorrow); + } + + if stats.owned.borrowed_for_closure_count > 0 { + pats.insert(BodyPat::OwnedClosureBorrow); + } + if stats.owned.borrowed_mut_for_closure_count > 0 { + pats.insert(BodyPat::OwnedClosureBorrowMut); + } +} diff --git a/src/tools/clippy/clippy_lints/src/borrow_pats/body/flow.rs b/src/tools/clippy/clippy_lints/src/borrow_pats/body/flow.rs new file mode 100644 index 0000000000000..fa29e552ceab3 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/borrow_pats/body/flow.rs @@ -0,0 +1,78 @@ +use super::super::prelude::*; +use super::MutInfo; + +#[derive(Debug)] +pub struct DfWalker<'a, 'tcx> { + _info: &'a AnalysisInfo<'tcx>, + assignments: &'a IndexVec>, + child: Local, + maybe_parents: &'a [Local], + found_parents: Vec, + all_const: bool, +} + +impl<'a, 'tcx> DfWalker<'a, 'tcx> { + pub fn new( + info: &'a AnalysisInfo<'tcx>, + assignments: &'a IndexVec>, + child: Local, + maybe_parents: &'a [Local], + ) -> Self { + Self { + _info: info, + assignments, + child, + maybe_parents, + found_parents: vec![], + all_const: true, + } + } + + pub fn walk(&mut self) { + let mut seen = BitSet::new_empty(self.assignments.len()); + let mut stack = Vec::with_capacity(16); + stack.push(self.child); + + while let Some(parent) = stack.pop() { + if self.maybe_parents.contains(&parent) { + self.found_parents.push(parent); + } + + for assign in &self.assignments[parent] { + match assign { + MutInfo::Dep(sources) | MutInfo::Ctor(sources) => { + stack.extend(sources.iter().filter(|local| seen.insert(**local))); + }, + MutInfo::Place(from) | MutInfo::Loan(from) | MutInfo::MutRef(from) => { + if matches!(assign, MutInfo::MutRef(_)) { + self.all_const = false; + } + + if seen.insert(*from) { + stack.push(*from); + } + }, + MutInfo::Const => { + continue; + }, + MutInfo::Calc | MutInfo::Arg => { + self.all_const = false; + continue; + }, + } + } + } + } + + pub fn connection_count(&self) -> usize { + self.found_parents.len() + } + + pub fn found_connection(&self, maybe_parent: Local) -> bool { + self.found_parents.contains(&maybe_parent) + } + + pub fn all_const(&self) -> bool { + self.all_const + } +} diff --git a/src/tools/clippy/clippy_lints/src/borrow_pats/body/pattern.rs b/src/tools/clippy/clippy_lints/src/borrow_pats/body/pattern.rs new file mode 100644 index 0000000000000..a38554250c40a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/borrow_pats/body/pattern.rs @@ -0,0 +1,80 @@ +use rustc_hir::FnSig; + +use crate::borrow_pats::SimpleTyKind; + +use super::{AnalysisInfo, RETURN_LOCAL}; + +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, serde::Serialize)] +pub enum BodyPat { + Borrow, + /// This function doesn't take any arguments + NoArguments, + /// Indicates that a body contained an anonymous loan. These are usually + /// only used for temp borrows. + OwnedArgBorrow, + OwnedArgBorrowMut, + OwnedClosureBorrow, + OwnedClosureBorrowMut, + /// Indicates that a body contained a named loan. So cases like + /// `_2 = &_1`, where `_2` is a named variable. + OwnedNamedBorrow, + OwnedNamedBorrowMut, + /// This function uses a two phased borrow. This is different from rustc's + /// value tracked in `BorrowKind` as this this pattern is only added if a two + /// phase borrow actually happened (i.e. the code would be rejected without) + OwnedTwoPhaseBorrow, + ReturnedStaticLoanForNonStatic, +} + +#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, serde::Serialize)] +pub struct BodyInfo { + pub(super) return_ty: SimpleTyKind, + pub(super) is_const: bool, + pub(super) is_safe: bool, + pub(super) is_sync: bool, + pub(super) context: BodyContext, +} + +impl BodyInfo { + pub fn from_sig(hir_sig: &FnSig<'_>, info: &AnalysisInfo<'_>, context: BodyContext) -> Self { + Self { + return_ty: SimpleTyKind::from_ty(info.body.local_decls[RETURN_LOCAL].ty), + is_const: hir_sig.header.is_const(), + is_safe: !hir_sig.header.is_unsafe(), + is_sync: !hir_sig.header.is_async(), + context, + } + } +} + +impl std::fmt::Debug for BodyInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "BodyInfo({self})") + } +} + +impl std::fmt::Display for BodyInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let return_ty = format!("Output({:?})", self.return_ty); + let constness = if self.is_const { "Const" } else { "NotConst" }; + let safety = if self.is_safe { "Safe" } else { "Unsafe" }; + let sync = if self.is_sync { "Sync" } else { "Async" }; + let context = format!("{:?}", self.context); + write!( + f, + "{return_ty:<17}, {constness:<9}, {safety:<6}, {sync:<5}, {context:<10}" + ) + } +} + +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, serde::Serialize)] +pub enum BodyContext { + /// The function is simple and free. + Free, + /// The function is inside an `impl` block. + Impl, + /// The function is inside an `impl Trait` block. + TraitImpl, + /// The function is inside a trait definition. + TraitDef, +} diff --git a/src/tools/clippy/clippy_lints/src/borrow_pats/info.rs b/src/tools/clippy/clippy_lints/src/borrow_pats/info.rs new file mode 100644 index 0000000000000..e98007cb23c02 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/borrow_pats/info.rs @@ -0,0 +1,337 @@ +#![warn(unused)] + +use std::cell::RefCell; + +use hir::def_id::LocalDefId; +use meta::MetaAnalysis; +use rustc_data_structures::fx::FxHashMap; +use rustc_index::IndexVec; +use rustc_lint::LateContext; +use rustc_middle::mir; +use rustc_middle::mir::{BasicBlock, Local, Place}; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_span::Symbol; +use smallvec::SmallVec; + +use super::{BodyStats, PlaceMagic, VisitKind}; + +use {rustc_borrowck as borrowck, rustc_hir as hir}; + +mod meta; + +pub struct AnalysisInfo<'tcx> { + pub cx: &'tcx LateContext<'tcx>, + pub tcx: TyCtxt<'tcx>, + pub body: &'tcx mir::Body<'tcx>, + pub def_id: LocalDefId, + pub cfg: IndexVec, + /// The set defines the loop bbs, and the basic block determines the end of the loop + pub terms: FxHashMap>>, + pub locals: IndexVec>, + pub preds: IndexVec>, + pub preds_unlooped: IndexVec>, + pub visit_order: Vec, + pub stats: RefCell, +} + +impl<'tcx> std::fmt::Debug for AnalysisInfo<'tcx> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AnalysisInfo") + .field("body", &self.body) + .field("def_id", &self.def_id) + .field("cfg", &self.cfg) + .field("terms", &self.terms) + .field("locals", &self.locals) + .field("preds", &self.preds) + .field("preds_unlooped", &self.preds_unlooped) + .field("visit_order", &self.visit_order) + .field("stats", &self.stats) + .finish() + } +} + +impl<'tcx> AnalysisInfo<'tcx> { + pub fn new(cx: &LateContext<'tcx>, def_id: LocalDefId) -> Self { + // This is totally unsafe and I will not pretend it isn't. + // In this context it is safe, this struct will never outlive `cx` + let cx = unsafe { core::mem::transmute::<&LateContext<'tcx>, &'tcx LateContext<'tcx>>(cx) }; + + let body = cx.tcx.optimized_mir(def_id); + + let meta_analysis = MetaAnalysis::from_body(cx, body); + + // This needs deconstruction to kill the loan of self + let MetaAnalysis { + cfg, + terms, + locals, + preds, + preds_unlooped, + visit_order, + stats, + .. + } = meta_analysis; + + let stats = RefCell::new(stats); + // Maybe check: borrowck::dataflow::calculate_borrows_out_of_scope_at_location + + Self { + cx, + tcx: cx.tcx, + body, + def_id, + cfg, + terms, + locals, + preds, + preds_unlooped, + visit_order, + stats, + } + } + + pub fn places_conflict(&self, a: Place<'tcx>, b: Place<'tcx>) -> bool { + borrowck::consumers::places_conflict( + self.tcx, + self.body, + a, + b, + borrowck::consumers::PlaceConflictBias::NoOverlap, + ) + } +} + +#[derive(Debug, Clone)] +pub enum CfgInfo { + /// The basic block is linear or almost linear. This is also the + /// value used for function calls which could result in unwinds. + Linear(BasicBlock), + /// The control flow can diverge after this block and will join + /// together at `join` + Condition { branches: SmallVec<[BasicBlock; 2]> }, + /// This branch doesn't have any successors + None, + /// Let's see if we can detect this + Return, +} + +#[derive(Debug, Clone)] +pub struct LocalInfo<'tcx> { + pub kind: LocalKind, + pub assign_count: u32, + pub data: DataInfo<'tcx>, +} + +impl<'tcx> LocalInfo<'tcx> { + pub fn new(kind: LocalKind) -> Self { + Self { + kind, + assign_count: 0, + data: DataInfo::Unresolved, + } + } + + pub fn add_assign(&mut self, place: mir::Place<'tcx>, assign: DataInfo<'tcx>) { + if place.is_part() { + self.data.part_assign(); + } else if place.just_local() { + self.assign_count += 1; + self.data.mix(assign); + } else if place.is_indirect() { + // FIXME: Assignment to owner, not tracked for my thesis since we + // only follow anon borrows + } else { + todo!("Handle weird assign {place:#?}\n{self:#?}"); + } + } +} + +#[derive(Debug, Clone)] +pub enum LocalKind { + /// The return local + Return, + /// User defined variable + UserVar(Symbol, VarInfo), + /// Generated variable, i.e. unnamed + AnonVar, +} + +impl LocalKind { + pub fn name(&self) -> Option { + match self { + Self::UserVar(name, _) => Some(*name), + _ => None, + } + } + + pub fn is_arg(&self) -> bool { + match self { + Self::UserVar(_, info) => info.argument, + _ => false, + } + } +} + +#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, serde::Serialize)] +#[expect(clippy::struct_excessive_bools)] +pub struct VarInfo { + pub argument: bool, + /// Indicates if this is mutable + pub mutable: bool, + /// Indicates if the value is owned or a reference. + pub owned: bool, + /// Indicates if the value is copy + pub copy: bool, + /// Indicates if this type needs to be dropped + pub drop: DropKind, + pub ty: SimpleTyKind, +} + +impl std::fmt::Debug for VarInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "VarInfo({self})") + } +} +impl std::fmt::Display for VarInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mutable = if self.mutable { "Mutable" } else { "Immutable" }; + let owned = if self.owned { "Owned" } else { "Reference" }; + let argument = if self.argument { "Argument" } else { "Local" }; + let copy = if self.copy { "Copy" } else { "NonCopy" }; + let dropable = format!("{:?}", self.drop); + let ty = format!("{:?}", self.ty); + write!( + f, + "{mutable:<9}, {owned:<9}, {argument:<8}, {copy:<7}, {dropable:<8}, {ty:<9}" + ) + } +} + +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, serde::Serialize)] +pub enum DropKind { + NonDrop, + PartDrop, + SelfDrop, +} + +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, serde::Serialize)] +pub enum SimpleTyKind { + /// The pretty unit type + Unit, + /// A primitive type. + Primitive, + /// A tuple + Tuple, + /// A sequence type, this will either be an array or a slice + Sequence, + /// A user defined typed + UserDef, + /// Functions, function pointers, and closures + Fn, + Closure, + TraitObj, + RawPtr, + Foreign, + Reference, + Never, + Generic, + Idk, +} + +impl SimpleTyKind { + pub fn from_ty(ty: Ty<'_>) -> Self { + match ty.kind() { + ty::Tuple(tys) if tys.is_empty() => SimpleTyKind::Unit, + ty::Tuple(_) => SimpleTyKind::Tuple, + + ty::Never => SimpleTyKind::Never, + + ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => SimpleTyKind::Primitive, + + ty::Adt(_, _) => SimpleTyKind::UserDef, + + ty::Array(_, _) | ty::Slice(_) => SimpleTyKind::Sequence, + + ty::Ref(_, _, _) => SimpleTyKind::Reference, + + ty::Foreign(_) => SimpleTyKind::Foreign, + ty::RawPtr(_, _) => SimpleTyKind::RawPtr, + + ty::FnDef(_, _) | ty::FnPtr(_) => SimpleTyKind::Fn, + ty::Closure(_, _) => SimpleTyKind::Closure, + + ty::Alias(ty::AliasKind::Opaque, _) | ty::Dynamic(_, _, _) => SimpleTyKind::TraitObj, + + ty::Param(_) => SimpleTyKind::Generic, + ty::Bound(_, _) | ty::Alias(_, _) => SimpleTyKind::Idk, + + ty::CoroutineClosure(_, _) + | ty::Coroutine(_, _) + | ty::CoroutineWitness(_, _) + | ty::Placeholder(_) + | ty::Infer(_) + | ty::Error(_) + | ty::Pat(_, _) => unreachable!("{ty:#?}"), + } + } +} + +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +pub enum DataInfo<'tcx> { + /// The default value, before it has been resolved. This should never be the + /// final version. + Unresolved, + /// The value is an argument. This will add a *fake* mutation to the mut count + Argument, + /// The value has several assignment spots with different data types + Mixed, + /// The local is a strait copy from another local, this includes moves + Local(mir::Local), + /// A part of a place was moved or copied into this + Part(mir::Place<'tcx>), + /// The value is constant, this includes static loans + Const, + /// The value is the result of a computation, usually done by function calls + Computed, + /// A loan of a place + Loan(mir::Place<'tcx>), + /// This value is the result of a cast of a different local. The data + /// state depends on the case source + Cast(mir::Local), + /// The Ctor of a transparent type. This Ctor can be constant, so the + /// content depends on the used locals + Ctor(Vec), +} + +impl<'tcx> DataInfo<'tcx> { + pub fn mix(&mut self, new_value: Self) { + if *self == Self::Unresolved { + *self = new_value; + } else if *self != new_value { + *self = Self::Mixed; + } + } + + /// This is used to indicate, that a part of this value was mutated via a projection + fn part_assign(&mut self) { + *self = Self::Mixed; + } +} + +/// This struct is an over simplification since places might have projections. +#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] +pub enum LocalOrConst { + Local(mir::Local), + Const, +} + +impl From<&mir::Operand<'_>> for LocalOrConst { + fn from(value: &mir::Operand<'_>) -> Self { + if let Some(place) = value.place() { + // assert!(place.just_local()); + Self::Local(place.local) + } else { + Self::Const + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/borrow_pats/info/meta.rs b/src/tools/clippy/clippy_lints/src/borrow_pats/info/meta.rs new file mode 100644 index 0000000000000..715d2a9d83d36 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/borrow_pats/info/meta.rs @@ -0,0 +1,271 @@ +use crate::borrow_pats::info::VarInfo; +use crate::borrow_pats::{ + construct_visit_order, unloop_preds, BodyStats, DropKind, PlaceMagic, PrintPrevent, SimpleTyKind, VisitKind, +}; + +use super::super::{calc_call_local_relations, CfgInfo, DataInfo, LocalInfo, LocalOrConst}; +use super::LocalKind; + +use clippy_utils::ty::{has_drop, is_copy}; +use mid::mir::visit::Visitor; +use mid::mir::{Body, Terminator}; +use mid::ty::TyCtxt; +use rustc_data_structures::fx::FxHashMap; +use rustc_index::IndexVec; +use rustc_lint::LateContext; +use rustc_middle as mid; +use rustc_middle::mir; +use rustc_middle::mir::{BasicBlock, Local, Place, Rvalue}; +use smallvec::SmallVec; + +/// This analysis is special as it is always the first one to run. It collects +/// information about the control flow etc, which will be used by future analysis. +/// +/// For better construction and value tracking, it uses reverse order depth search +#[derive(Debug)] +pub struct MetaAnalysis<'a, 'tcx> { + body: &'a Body<'tcx>, + tcx: PrintPrevent>, + cx: PrintPrevent<&'tcx LateContext<'tcx>>, + pub cfg: IndexVec, + pub terms: FxHashMap>>, + pub return_block: BasicBlock, + pub locals: IndexVec>, + pub preds: IndexVec>, + pub preds_unlooped: IndexVec>, + pub visit_order: Vec, + pub stats: BodyStats, +} + +impl<'a, 'tcx> MetaAnalysis<'a, 'tcx> { + pub fn from_body(cx: &'tcx LateContext<'tcx>, body: &'a Body<'tcx>) -> Self { + let mut anly = Self::new(cx, body); + anly.visit_body(body); + anly.unloop_preds(); + anly.visit_order = construct_visit_order(body, &anly.cfg, &anly.preds_unlooped); + + anly + } + + pub fn new(cx: &'tcx LateContext<'tcx>, body: &'a Body<'tcx>) -> Self { + let locals = Self::setup_local_infos(body); + let bb_len = body.basic_blocks.len(); + + let mut preds = IndexVec::with_capacity(bb_len); + preds.resize(bb_len, SmallVec::new()); + + let mut cfg = IndexVec::with_capacity(bb_len); + cfg.resize(bb_len, CfgInfo::None); + + Self { + body, + tcx: PrintPrevent(cx.tcx), + cx: PrintPrevent(cx), + cfg, + terms: Default::default(), + return_block: BasicBlock::from_u32(0), + locals, + preds, + preds_unlooped: IndexVec::with_capacity(bb_len), + visit_order: Default::default(), + stats: Default::default(), + } + } + + fn setup_local_infos(body: &mir::Body<'tcx>) -> IndexVec> { + let local_info_iter = body.local_decls.indices().map(|_| LocalInfo::new(LocalKind::AnonVar)); + let mut local_infos = IndexVec::new(); + local_infos.extend(local_info_iter); + + local_infos[super::super::RETURN].kind = LocalKind::Return; + + local_infos + } + + fn unloop_preds(&mut self) { + self.preds_unlooped = unloop_preds(&self.cfg, &self.preds); + } + + fn visit_terminator_for_cfg(&mut self, term: &Terminator<'tcx>, bb: BasicBlock) { + let cfg_info = match &term.kind { + #[rustfmt::skip] + mir::TerminatorKind::FalseEdge { real_target: target, .. } + | mir::TerminatorKind::FalseUnwind { real_target: target, .. } + | mir::TerminatorKind::Assert { target, .. } + | mir::TerminatorKind::Call { target: Some(target), .. } + | mir::TerminatorKind::Drop { target, .. } + | mir::TerminatorKind::Goto { target } => { + self.preds[*target].push(bb); + CfgInfo::Linear(*target) + }, + mir::TerminatorKind::InlineAsm { targets, .. } => { + let mut branches = SmallVec::new(); + branches.extend(targets.iter().copied()); + + for target in &branches { + self.preds[*target].push(bb); + } + + CfgInfo::Condition { branches } + }, + mir::TerminatorKind::SwitchInt { targets, .. } => { + let mut branches = SmallVec::new(); + branches.extend_from_slice(targets.all_targets()); + + for target in &branches { + self.preds[*target].push(bb); + } + + CfgInfo::Condition { branches } + }, + #[rustfmt::skip] + mir::TerminatorKind::UnwindResume + | mir::TerminatorKind::UnwindTerminate(_) + | mir::TerminatorKind::Unreachable + | mir::TerminatorKind::CoroutineDrop + | mir::TerminatorKind::Call { .. } => { + CfgInfo::None + }, + mir::TerminatorKind::Return => { + self.return_block = bb; + CfgInfo::Return + }, + mir::TerminatorKind::Yield { .. } => unreachable!(), + }; + + self.cfg[bb] = cfg_info; + } + + fn visit_terminator_for_terms(&mut self, term: &Terminator<'tcx>, bb: BasicBlock) { + if let mir::TerminatorKind::Call { + func, + args, + destination, + .. + } = &term.kind + { + assert!(destination.projection.is_empty()); + let dest = destination.local; + self.terms.insert( + bb, + calc_call_local_relations(self.tcx.0, self.body, func, dest, args, &mut self.stats), + ); + } + } + + fn visit_terminator_for_locals(&mut self, term: &Terminator<'tcx>, _bb: BasicBlock) { + if let mir::TerminatorKind::Call { destination, .. } = &term.kind { + // TODO: Should mut arguments be handled? + assert!(destination.projection.is_empty()); + let local = destination.local; + self.locals + .get_mut(local) + .unwrap() + .add_assign(*destination, DataInfo::Computed); + } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for MetaAnalysis<'a, 'tcx> { + fn visit_var_debug_info(&mut self, info: &mir::VarDebugInfo<'tcx>) { + if let mir::VarDebugInfoContents::Place(place) = info.value { + assert!(place.just_local()); + let local = place.local; + if let Some(local_info) = self.locals.get_mut(local) { + let decl = &self.body.local_decls[local]; + let drop = if !decl.ty.needs_drop(self.tcx.0, self.cx.0.param_env) { + DropKind::NonDrop + // } else if decl.ty.has_significant_drop(self.tcx.0, self.cx.0.param_env) { + } else if has_drop(self.cx.0, decl.ty) { + DropKind::SelfDrop + } else { + DropKind::PartDrop + }; + let var_info = VarInfo { + argument: info.argument_index.is_some(), + mutable: decl.mutability.is_mut(), + owned: !decl.ty.is_ref(), + copy: is_copy(self.cx.0, decl.ty), + // Turns out that both `has_significant_drop` and `has_drop` + // return false if only fields require drops. Strings are a + // good testing example for this. + drop, + ty: SimpleTyKind::from_ty(decl.ty), + }; + + local_info.kind = LocalKind::UserVar(info.name, var_info); + + if local_info.kind.is_arg() { + // +1 since it's assigned outside of the body + local_info.assign_count += 1; + local_info.add_assign(place, DataInfo::Argument); + } + } + } else { + todo!("How should this be handled? {info:#?}"); + } + } + + fn visit_terminator(&mut self, term: &Terminator<'tcx>, loc: mir::Location) { + self.visit_terminator_for_cfg(term, loc.block); + self.visit_terminator_for_terms(term, loc.block); + self.visit_terminator_for_locals(term, loc.block); + } + + fn visit_assign(&mut self, place: &Place<'tcx>, rval: &Rvalue<'tcx>, _loc: mir::Location) { + let local = place.local; + + let assign_info = match rval { + mir::Rvalue::Ref(_reg, _kind, src) => { + match src.projection.as_slice() { + [mir::PlaceElem::Deref] => { + // &(*_1) = Copy + DataInfo::Local(src.local) + }, + _ => DataInfo::Loan(*src), + } + }, + mir::Rvalue::Use(op) => match &op { + mir::Operand::Copy(other) | mir::Operand::Move(other) => { + if other.is_part() { + DataInfo::Part(*other) + } else { + DataInfo::Local(other.local) + } + }, + mir::Operand::Constant(_) => DataInfo::Const, + }, + + // Constructed Values + Rvalue::Aggregate(_, fields) => { + let parts = fields.iter().map(LocalOrConst::from).collect(); + DataInfo::Ctor(parts) + }, + Rvalue::Repeat(op, _) => DataInfo::Ctor(vec![op.into()]), + + // Casts should depend on the input data + Rvalue::Cast(_kind, op, _target) => { + if let Some(place) = op.place() { + assert!(place.just_local()); + DataInfo::Cast(place.local) + } else { + DataInfo::Const + } + }, + + Rvalue::NullaryOp(_, _) => DataInfo::Const, + + Rvalue::ThreadLocalRef(_) + | Rvalue::AddressOf(_, _) + | Rvalue::Len(_) + | Rvalue::BinaryOp(_, _) + | Rvalue::CheckedBinaryOp(_, _) + | Rvalue::UnaryOp(_, _) + | Rvalue::Discriminant(_) + | Rvalue::ShallowInitBox(_, _) + | Rvalue::CopyForDeref(_) => DataInfo::Computed, + }; + + self.locals.get_mut(local).unwrap().add_assign(*place, assign_info); + } +} diff --git a/src/tools/clippy/clippy_lints/src/borrow_pats/mod.rs b/src/tools/clippy/clippy_lints/src/borrow_pats/mod.rs new file mode 100644 index 0000000000000..219fd9ebf15c9 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/borrow_pats/mod.rs @@ -0,0 +1,309 @@ +#![expect(unused)] +#![allow( + clippy::module_name_repetitions, + clippy::default_trait_access, + clippy::wildcard_imports, + clippy::enum_variant_names +)] +//! # TODOs +//! - [ ] Update meta analysis +//! - [ ] Handle loops by partially retraverse them +//! - [ ] Handle loop overwrites for !drop +//! - [ ] Anonymous stuff should be more unified and not broken into borrows etc like it is now. +//! +//! # Done +//! - [x] Determine value reachability (This might only be needed for returns) +//! - [x] Track properties separatly and uniformly (Argument, Mutable, Owned, Dropable) +//! - [x] Handle or abort on feature use +//! - [x] Consider HIR based visitor `rustc_hir_typeck::expr_use_visitor::Delegate` +//! - [-] Update Return analysis (Removed) +//! - [-] Add Computed to Return +//! - [-] Check if the relations implied by the function signature aline with usage +//! - [x] Output and summary +//! - [x] Collect and summarize all data per crate +//! - [x] The output states need to be sorted... OH NO +//! +//! # Insights: +//! - Loans are basically just special dependent typed +//! - Binary Assign Ops on primitive types result in overwrites instead of `&mut` borrows +//! +//! # Report +//! - Mention Crater Blacklist: (170) +//! - Write about fricking named borrows... +//! - MIR can violate rust semantics: +//! - `(_1 = &(*_2))` +//! - Two Phased Borrows +//! - Analysis only tracks named values. Anonymous values are generated by Rustc but might change +//! and are also MIR version specific. +//! - `impl Drop` types behave differently, as fields need to be valid until the `drop()` +//! +//! # Optional and good todos: +//! - [ ] Analysis for named references +//! - Investigate the `explicit_outlives_requirements` lint + +use std::borrow::Borrow; +use std::cell::RefCell; +use std::collections::{BTreeMap, VecDeque}; +use std::ops::ControlFlow; + +use self::body::BodyContext; +use borrowck::borrow_set::BorrowSet; +use borrowck::consumers::calculate_borrows_out_of_scope_at_location; +use clippy_config::msrvs::Msrv; +use clippy_utils::ty::{for_each_ref_region, for_each_region, for_each_top_level_late_bound_region}; +use clippy_utils::{fn_has_unsatisfiable_preds, is_lint_allowed}; +use hir::def_id::{DefId, LocalDefId}; +use hir::{BodyId, HirId, Mutability}; +use mid::mir::visit::Visitor; +use mid::mir::Location; +use rustc_borrowck::consumers::{get_body_with_borrowck_facts, ConsumerOptions}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_index::bit_set::BitSet; +use rustc_index::IndexVec; +use rustc_lint::{LateContext, LateLintPass, Level}; +use rustc_middle::mir; +use rustc_middle::mir::{BasicBlock, FakeReadCause, Local, Place, Rvalue}; +use rustc_middle::ty::{Clause, List, TyCtxt}; +use rustc_session::{declare_lint_pass, impl_lint_pass}; +use rustc_span::source_map::Spanned; +use rustc_span::Symbol; + +use {rustc_borrowck as borrowck, rustc_hir as hir, rustc_middle as mid}; + +mod body; +mod owned; + +mod info; +mod prelude; +mod rustc_extention; +mod stats; +mod util; +pub use info::*; +pub use rustc_extention::*; +pub use stats::*; +pub use util::*; + +const RETURN: Local = Local::from_u32(0); + +const OUTPUT_MARKER: &str = "[THESIS-ANALYSIS-OUTPUT]"; + +declare_clippy_lint! { + /// ### What it does + /// + /// ### Why is this bad? + /// + /// ### Example + /// ```ignore + /// // example code where clippy issues a warning + /// ``` + /// Use instead: + /// ```ignore + /// // example code which does not raise clippy warning + /// ``` + #[clippy::version = "1.78.0"] + pub BORROW_PATS, + nursery, + "non-default lint description" +} + +impl_lint_pass!(BorrowPats => [BORROW_PATS]); + +#[expect(clippy::struct_excessive_bools)] +pub struct BorrowPats { + msrv: Msrv, + /// Indicates if this analysis is enabled. It may be disabled in the following cases: + /// * Nightly features are enabled + enabled: bool, + /// Indicates if the collected patterns should be printed for each pattern. + print_pats: bool, + print_call_relations: bool, + print_locals: bool, + print_stats: bool, + print_mir: bool, + print_fns: bool, + debug_func_name: Symbol, + stats: CrateStats, +} + +impl BorrowPats { + pub fn new(msrv: Msrv) -> Self { + // Determined by `check_crate` + let enabled = true; + let print_pats = std::env::var("CLIPPY_PETS_PRINT").is_ok(); + let print_call_relations = std::env::var("CLIPPY_PETS_TEST_RELATIONS").is_ok(); + let print_locals = std::env::var("CLIPPY_LOCALS_PRINT").is_ok(); + let print_stats = std::env::var("CLIPPY_STATS_PRINT").is_ok(); + let print_mir = std::env::var("CLIPPY_PRINT_MIR").is_ok(); + let print_fns = std::env::var("CLIPPY_PRINT_FNS").is_ok(); + + Self { + msrv, + enabled, + print_pats, + print_call_relations, + print_locals, + print_stats, + print_mir, + print_fns, + debug_func_name: Symbol::intern("super-weird"), + stats: Default::default(), + } + } + + fn check_fn_body<'tcx>( + &mut self, + cx: &LateContext<'tcx>, + def_id: LocalDefId, + body_id: BodyId, + hir_sig: &hir::FnSig<'tcx>, + context: BodyContext, + ) { + if !self.enabled { + return; + } + + if fn_has_unsatisfiable_preds(cx, def_id.into()) { + return; + } + + let Some(body_name) = cx.tcx.opt_item_name(def_id.into()) else { + return; + }; + + let lint_level = cx + .tcx + .lint_level_at_node(BORROW_PATS, cx.tcx.local_def_id_to_hir_id(def_id)) + .0; + let body = cx.tcx.hir().body(body_id); + + if self.print_fns { + println!("# {body_name:?}"); + } + if lint_level != Level::Allow && self.print_pats { + println!("# {body_name:?}"); + } + + if self.print_mir && lint_level == Level::Forbid || body_name == self.debug_func_name { + print_debug_info(cx, body, def_id); + } + + let mut info = AnalysisInfo::new(cx, def_id); + + if body_name == self.debug_func_name { + dbg!(&info); + } + let (body_info, mut body_pats) = body::BodyAnalysis::run(&info, def_id, hir_sig, context); + + if lint_level != Level::Allow { + if self.print_call_relations { + println!("# Relations for {body_name:?}"); + println!("- Incompltete Stats: {:#?}", info.stats.borrow()); + println!("- Called function relations: {:#?}", info.terms); + println!("- Incompltete Body: {body_info} {body_pats:?}"); + println!(); + return; + } + + if self.print_locals { + println!("# Locals"); + println!("{:#?}", info.locals); + } + } + + for (local, local_info) in info.locals.iter_enumerated().skip(1) { + match &local_info.kind { + LocalKind::Return => unreachable!("Skipped before"), + LocalKind::UserVar(name, var_info) => { + if var_info.owned { + let pats = owned::OwnedAnalysis::run(&info, local); + if self.print_pats && lint_level != Level::Allow { + println!("- {:<15}: ({var_info}) {pats:?}", name.as_str()); + } + + self.stats.add_pat(*var_info, pats); + } else { + // eprintln!("TODO: implement analysis for named refs"); + } + }, + LocalKind::AnonVar => continue, + } + } + + body::update_pats_from_stats(&mut body_pats, &info); + + if lint_level != Level::Allow { + if self.print_pats { + // Return must be printed at the end, as it might be modified by + // the following analysis thingies + println!("- Body: {body_info} {body_pats:?}"); + } + if self.print_stats { + println!("- Stats: {:#?}", info.stats.borrow()); + } + + if self.print_pats || self.print_stats { + println!(); + } + } + + self.stats.add_body(info.body, info.stats.take(), body_info, body_pats); + } +} + +#[expect(clippy::missing_msrv_attr_impl)] +impl<'tcx> LateLintPass<'tcx> for BorrowPats { + fn check_crate(&mut self, cx: &LateContext<'tcx>) { + self.enabled = cx.tcx.features().all_features().iter().all(|x| *x == 0); + + if !self.enabled && self.print_pats { + println!("Disabled due to feature use"); + } + } + + fn check_crate_post(&mut self, _: &LateContext<'tcx>) { + if self.enabled { + let stats = std::mem::take(&mut self.stats); + let serde = stats.into_serde(); + println!("{OUTPUT_MARKER} {}", serde_json::to_string(&serde).unwrap()); + } else { + println!(r#"{OUTPUT_MARKER} {{"Disabled due to feature usage"}} "#); + } + } + + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { + match &item.kind { + hir::ItemKind::Fn(sig, _generics, body_id) => { + self.check_fn_body(cx, item.owner_id.def_id, *body_id, sig, BodyContext::Free); + }, + hir::ItemKind::Impl(impl_item) => { + let context = match impl_item.of_trait { + Some(_) => BodyContext::TraitImpl, + None => BodyContext::Impl, + }; + + for sub_item_ref in impl_item.items { + if matches!(sub_item_ref.kind, hir::AssocItemKind::Fn { .. }) { + let sub_item = cx.tcx.hir().impl_item(sub_item_ref.id); + let (sig, body_id) = sub_item.expect_fn(); + self.check_fn_body(cx, sub_item.owner_id.def_id, body_id, sig, context); + } + } + }, + _ => {}, + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'tcx>) { + if let hir::TraitItemKind::Fn(sig, hir::TraitFn::Provided(body_id)) = &item.kind { + self.check_fn_body(cx, item.owner_id.def_id, *body_id, sig, BodyContext::TraitDef); + } + } +} + +fn print_debug_info<'tcx>(cx: &LateContext<'tcx>, body: &hir::Body<'tcx>, def: hir::def_id::LocalDefId) { + println!("====="); + println!("Body for: {def:#?}"); + let body = cx.tcx.optimized_mir(def); + print_body(body); + println!("====="); +} diff --git a/src/tools/clippy/clippy_lints/src/borrow_pats/owned.rs b/src/tools/clippy/clippy_lints/src/borrow_pats/owned.rs new file mode 100644 index 0000000000000..862a82156ff44 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/borrow_pats/owned.rs @@ -0,0 +1,880 @@ +#![warn(unused)] + +use super::prelude::*; +use super::{visit_body_with_state, MyStateInfo, MyVisitor, VarInfo}; + +mod state; +use state::*; + +#[derive(Debug)] +pub struct OwnedAnalysis<'a, 'tcx> { + info: &'a AnalysisInfo<'tcx>, + /// The name of the local, used for debugging + name: Symbol, + local: Local, + states: IndexVec>, + /// The kind can diviate from the kind in info, in cases where we determine + /// that this is most likely a deconstructed argument. + local_kind: &'a LocalKind, + local_info: &'a VarInfo, + /// This should be a `BTreeSet` to have it ordered and consistent. + pats: BTreeSet, +} + +impl<'a, 'tcx> OwnedAnalysis<'a, 'tcx> { + pub fn new(info: &'a AnalysisInfo<'tcx>, local: Local) -> Self { + let local_kind = &info.locals[local].kind; + let LocalKind::UserVar(_name, local_info) = local_kind else { + unreachable!(); + }; + let name = local_kind.name().unwrap(); + + let bbs_ctn = info.body.basic_blocks.len(); + let mut states = IndexVec::with_capacity(bbs_ctn); + for bb in 0..bbs_ctn { + states.push(StateInfo::new(BasicBlock::from_usize(bb))); + } + + Self { + info, + local, + name, + states, + local_kind, + local_info, + pats: Default::default(), + } + } + + pub fn run(info: &'a AnalysisInfo<'tcx>, local: Local) -> BTreeSet { + let mut anly = Self::new(info, local); + visit_body_with_state(&mut anly, info); + + anly.pats + } + + fn add_borrow( + &mut self, + bb: BasicBlock, + borrow: Place<'tcx>, + broker: Place<'tcx>, + kind: BorrowKind, + bro: Option, + ) { + self.states[bb].add_borrow(borrow, broker, kind, bro, self.info, &mut self.pats); + } +} + +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, serde::Serialize)] +pub enum OwnedPat { + /// The owned value might be returned + /// + /// The return pattern collection should also be informed of this. White box *tesing* + #[expect(unused, reason = "Either this needs to be detected consistency or not at all")] + Returned, + /// The value is only assigned once and never read afterwards. + #[expect(unused, reason = "This can't be reliably detected with MIR")] + Unused, + /// The value is dynamically dropped, meaning if it's still valid at a given location. + /// See RFC: #320 + DynamicDrop, + /// Only a part of the struct is being dropped + PartDrop, + /// The value was moved + Moved, + /// This value was moved into a different function. This also delegates the drop + MovedToFn, + /// This value was moved to a different local. `_other = _self` + MovedToVar, + /// This value was moved to `_0` + MovedToReturn, + MovedToClosure, + MovedToCtor, + /// A part was moved. + PartMoved, + /// This value was moved info a different local. `_other.field = _self` + PartMovedToVar, + /// This value was moved info a different local. `_other.field = _self` + PartMovedToFn, + /// A part was mvoed to `_0` + PartMovedToReturn, + PartMovedToClosure, + PartMovedToCtor, + /// This value was moved to a different local. `_other = _self` + CopiedToVar, + /// This value was moved info a different local. `_other.field = _self` + CopiedToVarPart, + /// This value was manually dropped by calling `std::mem::drop()` + ManualDrop, + ManualDropPart, + /// The entire local is being borrowed + Borrow, + ClosureBorrow, + ClosureBorrowMut, + CtorBorrow, + CtorBorrowMut, + ArgBorrow, + ArgBorrowExtended, + ArgBorrowMut, + ArgBorrowMutExtended, + /// A part of the local is being borrowed + PartBorrow, + PartClosureBorrow, + PartClosureBorrowMut, + PartCtorBorrow, + PartCtorBorrowMut, + PartArgBorrow, + PartArgBorrowExtended, + PartArgBorrowMut, + PartArgBorrowMutExtended, + /// Two temp borrows might alias each other, for example like this: + /// ```ignore + /// take_2(&self.field, &self.field); + /// ``` + /// This also includes fields and sub fields + /// ```ignore + /// take_2(&self.field, &self.field.sub_field); + /// ``` + AliasedBorrow, + /// A function takes mutliple `&mut` references to different parts of the object + /// ```ignore + /// take_2(&mut self.field_a, &mut self.field_b); + /// ``` + /// Mutable borrows can't be aliased. + MultipleMutBorrowsInArgs, + /// A function takes both a mutable and an immutable loan as the function input. + /// ```ignore + /// take_2(&self.field_a, &mut self.field_b); + /// ``` + /// The places can not be aliased. + MixedBorrowsInArgs, + /// The value has been overwritten + Overwrite, + /// A part of the value is being overwritten + OverwritePart, + /// The value will be overwritten in a loop + // + // FIXME: Move this pattern detection into state loop merging thingy + #[expect(unused, reason = "TODO, handle loops properly")] + OverwriteInLoop, + /// This value is involved in a two phased borrow. Meaning that an argument is calculated + /// using the value itself. Example: + /// + /// ```ignore + /// fn two_phase_borrow_1(mut vec: Vec) { + /// vec.push(vec.len()); + /// } + /// ``` + /// + /// See: + /// + /// This case is special, since MIR for some reason creates an aliased mut reference. + /// + /// ```text + /// bb0: + /// _3 = &mut _1 + /// _5 = &_1 + /// _4 = std::vec::Vec::::len(move _5) -> [return: bb1, unwind: bb4] + /// bb1: + /// _2 = std::vec::Vec::::push(move _3, move _4) -> [return: bb2, unwind: bb4] + /// bb2: + /// drop(_1) -> [return: bb3, unwind: bb5] + /// bb3: + /// return + /// ``` + /// + /// I really don't understand why. Creating the refernce later would be totally valid, at + /// least in all cases I looked at. This just creates a complete mess, but at this point + /// I'm giving up on asking questions. MIR is an inconsitent pain end of story. + /// + /// This pattern is only added, if the two phased borrows was actually used, so if the + /// code wouldn't work without it. + TwoPhasedBorrow, + /// A value is first mutably initilized and then moved into an unmut value. + /// + /// ```ignore + /// fn mut_and_shadow_immut() { + /// let mut x = "Hello World".to_string(); + /// x.push('x'); + /// x.clear(); + /// let x2 = x; + /// let _ = x2.len(); + /// } + /// ``` + /// + /// For `Copy` types this is only tracked, if the values have the same name. + /// as the value is otherwise still accessible. + ModMutShadowUnmut, + /// A loan of this value was assigned to a named place + NamedBorrow, + NamedBorrowMut, + PartNamedBorrow, + PartNamedBorrowMut, + ConditionalInit, + ConditionalOverwride, + ConditionalMove, + ConditionalDrop, + /// It turns out, that the `?` operator potentually adds named values which are + /// then moved into anons and dropped right after + OwningAnonDrop, + PartOwningAnonDrop, + /// This value is being dropped (by rustc) early to be replaced. + /// + /// ```ignore + /// let data = String::new(); + /// + /// // Rustc will first drop the old value of `data` + /// // This is a drop to replacement + /// data = String::from("Example"); + /// ``` + DropForReplacement, + /// A pointer to this value was created. This is "mostly uninteresting" as + /// these can only be used in unsafe code. + AddressOf, + AddressOfMut, + AddressOfPart, + AddressOfMutPart, + /// The value is being used for a switch. This probably doesn't say too much + /// since only ints can be used directly. + Switch, + SwitchPart, +} + +impl<'a, 'tcx> MyVisitor<'tcx> for OwnedAnalysis<'a, 'tcx> { + type State = StateInfo<'tcx>; + + fn init_start_block_state(&mut self) { + if self.local_kind.is_arg() { + self.states[START_BLOCK].set_state(State::Filled); + } else { + self.states[START_BLOCK].set_state(State::Empty); + } + } + + fn set_state(&mut self, bb: BasicBlock, state: Self::State) { + self.states[bb] = state; + } +} + +impl<'a, 'tcx> Visitor<'tcx> for OwnedAnalysis<'a, 'tcx> { + // Note: visit_place sounds perfect, with the mild inconvinience, that it doesn't + // provice any information about the result of the usage. Knowing that X was moved + // is nice but context is better. Imagine `_0 = move X`. So at last, I need + // to write these things with other visitors. + + fn visit_statement(&mut self, stmt: &Statement<'tcx>, loc: Location) { + if let StatementKind::StorageDead(local) = &stmt.kind { + self.states[loc.block].kill_local(*local); + } + self.super_statement(stmt, loc); + } + + fn visit_assign(&mut self, target: &Place<'tcx>, rvalue: &Rvalue<'tcx>, loc: Location) { + if let Rvalue::Ref(_region, BorrowKind::Fake(_), _place) = &rvalue { + return; + } + + if target.local == self.local { + if target.is_part() { + // It should be enough, to only track the pattern. Since the borrowck is already + // happy, we know that any borrows of this part are never used again. Removing them + // would just be extra work. + self.pats.insert(OwnedPat::OverwritePart); + } else { + self.visit_assign_to_self(target, rvalue, loc.block); + } + } + + self.visit_assign_for_self_in_args(target, rvalue, loc.block); + self.visit_assign_for_anon(target, rvalue, loc.block); + + self.super_assign(target, rvalue, loc); + } + + fn visit_terminator(&mut self, term: &Terminator<'tcx>, loc: Location) { + self.visit_terminator_for_args(term, loc.block); + self.visit_terminator_for_anons(term, loc.block); + self.super_terminator(term, loc); + } +} + +impl<'a, 'tcx> OwnedAnalysis<'a, 'tcx> { + #[expect(clippy::too_many_lines)] + fn visit_assign_for_self_in_args(&mut self, target: &Place<'tcx>, rval: &Rvalue<'tcx>, bb: BasicBlock) { + if let Rvalue::Use(op) = &rval + && let Some(place) = op.place() + && place.local == self.local + { + let is_move = op.is_move(); + if is_move { + if place.just_local() { + self.pats.insert(OwnedPat::Moved); + self.states[bb].clear(State::Moved); + } else if place.is_part() { + self.pats.insert(OwnedPat::PartMoved); + } else { + unreachable!("{target:#?} = {place:#?}"); + } + } + + if target.local.as_u32() == 0 { + if is_move { + if place.just_local() { + self.pats.insert(OwnedPat::MovedToReturn); + } else if place.is_part() { + self.pats.insert(OwnedPat::PartMovedToReturn); + } else { + unreachable!("{target:#?} = {place:#?}"); + } + } + } else if is_move { + match &self.info.locals[target.local].kind { + LocalKind::AnonVar => { + assert!(target.just_local()); + self.states[bb].add_anon(target.local, place); + }, + LocalKind::UserVar(_name, other_info) => { + if self.local_info.mutable && !other_info.mutable && target.just_local() && place.just_local() { + self.pats.insert(OwnedPat::ModMutShadowUnmut); + } + + if place.just_local() { + self.pats.insert(OwnedPat::MovedToVar); + } else { + self.pats.insert(OwnedPat::PartMovedToVar); + } + }, + LocalKind::Return => { + unreachable!("{target:#?} = {rval:#?} (at {bb:#?})\n{self:#?}"); + }, + } + } else { + match &self.info.locals[target.local].kind { + LocalKind::UserVar(other_name, other_info) => { + if self.local_info.mutable + && !other_info.mutable + && self.name == *other_name + && target.just_local() + && place.just_local() + { + self.pats.insert(OwnedPat::ModMutShadowUnmut); + } + + if target.just_local() { + self.pats.insert(OwnedPat::CopiedToVar); + } else { + self.pats.insert(OwnedPat::CopiedToVarPart); + } + }, + LocalKind::AnonVar | LocalKind::Return => { + // This is probably really interesting + }, + } + // Copies are uninteresting to me + } + } + + if let Rvalue::Ref(_region, kind, place) = &rval + && place.local == self.local + { + if place.just_local() { + self.pats.insert(OwnedPat::Borrow); + } else if place.is_indirect() { + return; + } else if place.is_part() { + self.pats.insert(OwnedPat::PartBorrow); + } else { + unreachable!( + "{target:#?} = {rval:#?} (at {bb:#?}) [{:#?}]\n{self:#?}", + place.projection + ); + } + + if target.just_local() { + self.add_borrow(bb, *target, *place, *kind, None); + } else { + // Example _5.1 = &(_1.8) + todo!("{target:#?} = {rval:#?} (at {bb:#?})\n{self:#?}"); + } + } + + if let Rvalue::Aggregate(box agg_kind, fields) = rval { + for field in fields { + let Operand::Move(place) = field else { + continue; + }; + if place.local != self.local { + continue; + } + + if place.just_local() { + self.pats.insert(OwnedPat::Moved); + self.states[bb].clear(State::Moved); + } else if place.is_part() { + self.pats.insert(OwnedPat::PartMoved); + } else { + unreachable!("{target:#?} = {place:#?}"); + } + + match agg_kind { + mir::AggregateKind::Array(_) + | mir::AggregateKind::Tuple + | mir::AggregateKind::Adt(_, _, _, _, _) => { + if place.just_local() { + self.pats.insert(OwnedPat::MovedToCtor); + } else if place.is_part() { + self.pats.insert(OwnedPat::PartMovedToCtor); + } else { + unreachable!("{target:#?} = {place:#?}"); + } + }, + mir::AggregateKind::Closure(_, _) => { + if place.just_local() { + self.pats.insert(OwnedPat::MovedToClosure); + } else if place.is_part() { + self.pats.insert(OwnedPat::PartMovedToClosure); + } else { + unreachable!("{target:#?} = {place:#?}"); + } + }, + mir::AggregateKind::Coroutine(_, _) | mir::AggregateKind::CoroutineClosure(_, _) => unreachable!(), + mir::AggregateKind::RawPtr(_, _) => {}, + } + } + } + + if let Rvalue::AddressOf(muta, place) = rval + && place.local == self.local + { + if place.just_local() { + if matches!(muta, Mutability::Not) { + self.pats.insert(OwnedPat::AddressOf); + } else { + self.pats.insert(OwnedPat::AddressOfMut); + } + } else if place.is_part() { + if matches!(muta, Mutability::Not) { + self.pats.insert(OwnedPat::AddressOfPart); + } else { + self.pats.insert(OwnedPat::AddressOfMutPart); + } + } else { + unreachable!("{self:#?} + {rval:#?}"); + } + } + } + fn visit_assign_to_self(&mut self, target: &Place<'tcx>, _rval: &Rvalue<'tcx>, bb: BasicBlock) { + assert!(target.just_local()); + + self.states[bb].add_assign(*target, &mut self.pats); + } + #[expect(clippy::too_many_lines)] + fn visit_assign_for_anon(&mut self, target: &Place<'tcx>, rval: &Rvalue<'tcx>, bb: BasicBlock) { + if let Rvalue::Use(op) = &rval + && let Operand::Move(place) = op + { + if let Some(anon_places) = self.states[bb].remove_anon(place) { + match self.info.locals[target.local].kind { + LocalKind::Return => { + let (is_all, is_part) = anon_places.place_props(); + + if is_all { + self.pats.insert(OwnedPat::MovedToReturn); + } + if is_part { + self.pats.insert(OwnedPat::PartMovedToReturn); + } + }, + LocalKind::UserVar(_, _) => { + if place.is_part() { + self.pats.insert(OwnedPat::PartMovedToVar); + } else { + self.pats.insert(OwnedPat::MovedToVar); + } + }, + LocalKind::AnonVar => { + assert!(place.just_local()); + self.states[bb].add_anon_places(target.local, anon_places); + }, + } + } + + self.states[bb].add_ref_copy(*target, *place, self.info, &mut self.pats); + } + + if let Rvalue::Ref(_, _, src) | Rvalue::CopyForDeref(src) = &rval { + match src.projection.as_slice() { + // &(*_1) = Copy + [PlaceElem::Deref] => { + // This will surely fail at one point. It was correct while this was only + // for anon vars. But let's fail for now, to handle it later. + assert!(target.just_local()); + self.states[bb].add_ref_copy(*target, *src, self.info, &mut self.pats); + }, + [PlaceElem::Deref, ..] | [] => { + self.states[bb].add_ref_ref(*target, *src, self.info, &mut self.pats); + }, + _ => { + if self.states[bb].has_bro(src).is_some() { + // FIXME: Is this correct? + self.states[bb].add_ref_ref(*target, *src, self.info, &mut self.pats); + + // unreachable!( + // "Handle {:#?} for {target:#?} = {rval:#?} (at {bb:#?})", + // src.projection.as_slice() + // ); + } + }, + } + } + + if let Rvalue::Aggregate(box agg_kind, fields) = rval { + for field in fields { + let state = &mut self.states[bb]; + let Operand::Move(place) = field else { + continue; + }; + let mut parts: SmallVec<[ContainerContent; 1]> = SmallVec::new(); + if let Some(bro_info) = state.has_bro(place) { + parts.extend(bro_info.as_content()); + } + if let Some(anon) = state.remove_anon(place) { + let (is_all, is_part) = anon.place_props(); + if is_all { + parts.push(ContainerContent::Owned); + } + if is_part { + parts.push(ContainerContent::Part); + } + } + if parts.is_empty() { + continue; + }; + + match agg_kind { + mir::AggregateKind::Array(_) + | mir::AggregateKind::Tuple + | mir::AggregateKind::Adt(_, _, _, _, _) => { + if parts.contains(&ContainerContent::Loan) { + self.pats.insert(OwnedPat::CtorBorrow); + } else if parts.contains(&ContainerContent::LoanMut) { + self.pats.insert(OwnedPat::CtorBorrowMut); + } + + if parts.contains(&ContainerContent::PartLoan) { + self.pats.insert(OwnedPat::PartCtorBorrow); + } else if parts.contains(&ContainerContent::PartLoanMut) { + self.pats.insert(OwnedPat::PartCtorBorrowMut); + } + + if parts.contains(&ContainerContent::Owned) { + self.pats.insert(OwnedPat::MovedToCtor); + } else if parts.contains(&ContainerContent::Part) { + self.pats.insert(OwnedPat::PartMovedToCtor); + } + + // let target_info = &self.info.locals[&target.local]; + // if matches!(target_info.kind, LocalKind::AnonVar) { + + // } + }, + mir::AggregateKind::Closure(_, _) => { + if parts + .iter() + .any(|part| matches!(part, ContainerContent::Loan | ContainerContent::PartLoan)) + { + self.info.stats.borrow_mut().owned.borrowed_for_closure_count += 1; + } else if parts + .iter() + .any(|part| matches!(part, ContainerContent::LoanMut | ContainerContent::PartLoanMut)) + { + self.info.stats.borrow_mut().owned.borrowed_mut_for_closure_count += 1; + } + + if parts.contains(&ContainerContent::Loan) { + self.pats.insert(OwnedPat::ClosureBorrow); + } else if parts.contains(&ContainerContent::LoanMut) { + self.pats.insert(OwnedPat::ClosureBorrowMut); + } + + if parts.contains(&ContainerContent::PartLoan) { + self.pats.insert(OwnedPat::PartClosureBorrow); + } else if parts.contains(&ContainerContent::PartLoanMut) { + self.pats.insert(OwnedPat::PartClosureBorrowMut); + } + + if parts.contains(&ContainerContent::Owned) { + self.pats.insert(OwnedPat::MovedToClosure); + } else if parts.contains(&ContainerContent::Part) { + self.pats.insert(OwnedPat::PartMovedToClosure); + } + }, + mir::AggregateKind::RawPtr(_, _) => {}, + mir::AggregateKind::Coroutine(_, _) | mir::AggregateKind::CoroutineClosure(_, _) => unreachable!(), + } + } + } + } + + fn visit_terminator_for_args(&mut self, term: &Terminator<'tcx>, bb: BasicBlock) { + match &term.kind { + // The `replace` flag of this place is super inconsistent. It lies don't trust it!!! + TerminatorKind::Drop { place, .. } => { + if place.local == self.local { + match self.states[bb].validity() { + Validity::Valid => { + if place.just_local() { + self.states[bb].clear(State::Dropped); + } else if place.is_part() { + self.pats.insert(OwnedPat::PartDrop); + } + }, + Validity::Maybe => { + if place.just_local() { + self.pats.insert(OwnedPat::DynamicDrop); + self.states[bb].clear(State::Dropped); + } + }, + Validity::Not => { + // It can happen that drop is called on a moved value: + // ``` + // if !a.is_empty() { + // return a; + // } + // ``` + // In that case we just ignore the action. (MIR WHY??????) + }, + } + } + }, + TerminatorKind::Call { + func, + args, + destination: dest, + .. + } => { + // Functions are copied and therefore out my this juristriction + if let Some(place) = func.place() + && place.local == self.local + { + unreachable!(); + } + + for arg in args { + if let Some(place) = arg.node.place() + && place.local == self.local + { + unreachable!(); + } + } + + if dest.local == self.local { + self.states[bb].add_assign(*dest, &mut self.pats); + } + }, + + // Both of these operate on copy types. They are uninteresting for now. + // They can still be important since these a reads which cancel mutable borrows and fields can be read + TerminatorKind::SwitchInt { discr: op, .. } | TerminatorKind::Assert { cond: op, .. } => { + if let Some(place) = op.place() + && place.local == self.local + { + // I'm 70% sure this can't happen: Any yet it has + if place.just_local() { + self.pats.insert(OwnedPat::Switch); + } else if place.is_part() { + self.pats.insert(OwnedPat::SwitchPart); + } else { + unreachable!("{self:#?} + {term:#?}"); + } + } + }, + // Controll flow or unstable features. Uninteresting for values + TerminatorKind::Goto { .. } + | TerminatorKind::UnwindResume + | TerminatorKind::UnwindTerminate(_) + | TerminatorKind::Return + | TerminatorKind::Unreachable + | TerminatorKind::Yield { .. } + | TerminatorKind::CoroutineDrop + | TerminatorKind::FalseEdge { .. } + | TerminatorKind::FalseUnwind { .. } + | TerminatorKind::InlineAsm { .. } => {}, + } + } + #[expect(clippy::too_many_lines)] + fn visit_terminator_for_anons(&mut self, term: &Terminator<'tcx>, bb: BasicBlock) { + match &term.kind { + TerminatorKind::Call { func, args, .. } => { + if let Some(place) = func.place() + && self.states[bb].remove_anon(&place).is_some() + { + unreachable!(); + } + + let args = args.iter().filter_map(|arg| { + // AFAIK, anons are always moved into the function. This makes + // sense as an IR property as well. So I'll go with it. + if let Operand::Move(place) = arg.node { + Some(place) + } else { + None + } + }); + + let mut immut_bros = vec![]; + // Mutable borrows can't be aliased, therefore it's suffcient + // to just count them + let mut mut_bro_ctn = 0; + let mut dep_loans: Vec<(Local, Place<'tcx>, Mutability)> = vec![]; + for arg in args { + if let Some(anon_places) = self.states[bb].remove_anon(&arg) { + // These are not mutually exclusive. A rare cupple for sure, but now unseen + let (is_all, is_part) = anon_places.place_props(); + + if is_all { + self.pats.insert(OwnedPat::MovedToFn); + } + if is_part { + self.pats.insert(OwnedPat::PartMovedToFn); + } + + if let Some((did, _generic_args)) = func.const_fn_def() + && self.info.cx.tcx.is_diagnostic_item(sym::mem_drop, did) + { + if is_all { + self.pats.insert(OwnedPat::ManualDrop); + } + if is_part { + self.pats.insert(OwnedPat::ManualDropPart); + } + } + } else if let Some(bro_info) = self.states[bb].has_bro(&arg) { + // Regardless of bro, we're interested in extentions + let loan_extended = { + let dep_loans_len = dep_loans.len(); + dep_loans.extend(self.info.terms[&bb].iter().filter_map(|(local, deps)| { + deps.contains(&arg.local) + .then_some((*local, bro_info.broker, bro_info.muta)) + })); + dep_loans_len != dep_loans.len() + }; + + let (is_all, is_part) = bro_info.borrowed_props(); + match bro_info.muta { + Mutability::Not => { + immut_bros.push(bro_info.broker); + + if matches!(bro_info.kind, BroKind::Anon) { + let stats = &mut self.info.stats.borrow_mut().owned; + stats.arg_borrow_count += 1; + if is_all { + self.pats.insert(OwnedPat::ArgBorrow); + } + if is_part { + self.pats.insert(OwnedPat::PartArgBorrow); + } + if loan_extended { + stats.arg_borrow_extended_count += 1; + if is_all { + self.pats.insert(OwnedPat::ArgBorrowExtended); + } + if is_part { + self.pats.insert(OwnedPat::PartArgBorrowExtended); + } + } + } + }, + Mutability::Mut => { + mut_bro_ctn += 1; + if matches!(bro_info.kind, BroKind::Anon) { + let stats = &mut self.info.stats.borrow_mut().owned; + stats.arg_borrow_mut_count += 1; + if is_all { + self.pats.insert(OwnedPat::ArgBorrowMut); + } + if is_part { + self.pats.insert(OwnedPat::PartArgBorrowMut); + } + if loan_extended { + stats.arg_borrow_mut_extended_count += 1; + if is_all { + self.pats.insert(OwnedPat::ArgBorrowMutExtended); + } + if is_part { + self.pats.insert(OwnedPat::PartArgBorrowMutExtended); + } + } + } + }, + }; + } + } + + if immut_bros.len() > 1 + && immut_bros + .iter() + .tuple_combinations() + .any(|(a, b)| self.info.places_conflict(*a, *b)) + { + self.pats.insert(OwnedPat::AliasedBorrow); + } + + if mut_bro_ctn > 1 { + self.pats.insert(OwnedPat::MultipleMutBorrowsInArgs); + } + + if !immut_bros.is_empty() && mut_bro_ctn >= 1 { + self.pats.insert(OwnedPat::MixedBorrowsInArgs); + } + + for (borrower, broker, muta) in dep_loans { + let kind = match muta { + Mutability::Not => BorrowKind::Shared, + Mutability::Mut => BorrowKind::Mut { + kind: mir::MutBorrowKind::Default, + }, + }; + let borrow = unsafe { std::mem::transmute::, Place<'tcx>>(borrower.as_place()) }; + self.add_borrow(bb, borrow, broker, kind, Some(BroKind::Dep)); + } + }, + + // Both of these operate on copy types. They are uninteresting for now. + // They can still be important since these a reads which cancel mutable borrows and fields can be read + TerminatorKind::SwitchInt { discr: op, .. } | TerminatorKind::Assert { cond: op, .. } => { + if let Some(place) = op.place() + && self.states[bb].remove_anon_place(&place).is_some() + { + // FIXME: I believe this can never be true, since int is + // copy and therefore never tracked in anons + unreachable!(); + } + }, + TerminatorKind::Drop { place, .. } => { + if let Some(anon) = self.states[bb].remove_anon(place) { + let (is_all, is_part) = anon.place_props(); + if is_all { + self.pats.insert(OwnedPat::OwningAnonDrop); + } + if is_part { + self.pats.insert(OwnedPat::PartOwningAnonDrop); + } + } + + // I believe this is uninteresting: Your believe was wrong! + }, + // Controll flow or unstable features. Uninteresting for values + TerminatorKind::Goto { .. } + | TerminatorKind::UnwindResume + | TerminatorKind::UnwindTerminate(_) + | TerminatorKind::Return + | TerminatorKind::Unreachable + | TerminatorKind::Yield { .. } + | TerminatorKind::CoroutineDrop + | TerminatorKind::FalseEdge { .. } + | TerminatorKind::FalseUnwind { .. } + | TerminatorKind::InlineAsm { .. } => {}, + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/borrow_pats/owned/state.rs b/src/tools/clippy/clippy_lints/src/borrow_pats/owned/state.rs new file mode 100644 index 0000000000000..0ba86c9851080 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/borrow_pats/owned/state.rs @@ -0,0 +1,642 @@ +#![warn(unused)] + +use rustc_index::bit_set::GrowableBitSet; + +use crate::borrow_pats::MyStateInfo; + +use super::super::prelude::*; +use super::OwnedPat; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct StateInfo<'tcx> { + bb: BasicBlock, + // pub prev_state: (State, BasicBlock), + state: SmallVec<[(State, BasicBlock); 4]>, + /// This is a set of values that *might* contain the owned value. + /// MIR has this *beautiful* habit of moving stuff into anonymous + /// locals first before using it further. + anons: FxHashMap>, + containers: FxHashMap, + /// This set contains borrows, these are often used for temporary + /// borrows + /// + /// **Note 1**: Named borrows can be created in two ways (Because of course + /// they can...) + /// ```ignore + /// // From: `mut_named_ref_non_kill` + /// // let mut x = 1; + /// // let mut p: &u32 = &x; + /// _4 = &_1 + /// _3 = &(*_4) + /// + /// // From: `call_extend_named` + /// // let data = String::new(); + /// // let loan = &data; + /// _2 = &_3 + /// ``` + /// + /// **Note 2**: Correction there are three ways to created named borrows... + /// Not sure why but let's take `mut_named_ref_non_kill` as and example for `y` + /// + /// ```ignore + /// // y => _2 + /// // named => _3 + /// _8 = &_2 + /// _7 = &(*_8) + /// _3 = move _7 + /// ``` + /// + /// **Note 3**: If I can confirm that these borrows are always used for + /// temporary borrows, it might be possible to prevent tracking them + /// to safe some performance. (Confirmed, that they are not just + /// used for temp borrows :D) + borrows: FxHashMap>, + /// This tracks mut borrows, which might be used for two phased borrows. + /// Based on the docs, it sounds like there can always only be one. Let's + /// use an option and cry when it fails. + /// + /// See: + phase_borrow: Vec<(Local, Place<'tcx>)>, +} + +#[derive(Debug, Clone, Eq, PartialEq, Default)] +pub struct AnonStorage<'tcx> { + places: SmallVec<[Place<'tcx>; 1]>, +} + +impl<'tcx> AnonStorage<'tcx> { + /// The first value indicates that this contains the whole palce, + /// the second one that this contains a part. These two are not + /// mutually exclusive + pub fn place_props(&self) -> (bool, bool) { + self.places.iter().fold((false, false), |(is_all, is_part), place| { + (is_all || place.just_local(), is_part || place.is_part()) + }) + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct ContainerInfo { + content: FxHashSet, +} + +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub enum ContainerContent { + Loan, + LoanMut, + PartLoan, + PartLoanMut, + Owned, + Part, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct BorrowInfo<'tcx> { + /// The place that is being borrowed + pub broker: Place<'tcx>, + /// This is the mutability of the original borrow. If we have a double borrow, like this: + /// ```ignore + /// let mut data = String::new(); + /// + /// // Loan 1 + /// // vvvvv + /// let double_ref = &&mut data; + /// // ^ + /// // Loan 2 (Mutable, since loan 1 is mut) + /// ``` + pub muta: Mutability, + pub kind: BroKind, +} + +impl<'tcx> BorrowInfo<'tcx> { + pub fn new(broker: Place<'tcx>, muta: Mutability, kind: BroKind) -> Self { + Self { broker, muta, kind } + } + + pub fn copy_with(&self, kind: BroKind) -> Self { + Self::new(self.broker, self.muta, kind) + } + + /// The first value indicates that this contains the whole palce, + /// the second one that this contains a part. These two are not + /// mutually exclusive + pub fn borrowed_props(&self) -> (bool, bool) { + if matches!(self.kind, BroKind::Dep) { + (false, false) + } else { + (self.broker.just_local(), self.broker.is_part()) + } + } + + pub fn as_content(&self) -> SmallVec<[ContainerContent; 1]> { + let (is_all, is_part) = self.borrowed_props(); + let mut vec = SmallVec::new(); + if is_all { + if matches!(self.muta, Mutability::Not) { + vec.push(ContainerContent::Loan); + } + if matches!(self.muta, Mutability::Mut) { + vec.push(ContainerContent::LoanMut); + } + } + if is_part { + if matches!(self.muta, Mutability::Not) { + vec.push(ContainerContent::PartLoan); + } + if matches!(self.muta, Mutability::Mut) { + vec.push(ContainerContent::PartLoanMut); + } + } + vec + } +} + +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Default)] +pub enum State { + #[default] + None, + Empty, + Filled, + Moved, + Dropped, + MaybeFilled, +} + +#[expect(unused)] +enum Event<'tcx> { + Init, + Loan, + Mutated, + // Moved or Dropped + Moved(Place<'tcx>), + Drop, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum BroKind { + Anon, + Named, + Dep, +} + +impl<'tcx> StateInfo<'tcx> { + pub fn prev_state(&self) -> Option { + if self.state.len() >= 2 { + Some(self.state[self.state.len() - 2].0) + } else { + None + } + } + + pub fn state(&self) -> State { + if let Some((state, _)) = self.state.last() { + *state + } else { + unreachable!("State should always be filled: {self:#?}") + } + } + + pub fn set_state(&mut self, state: State) { + self.state.push((state, self.bb)); + } + + /// Retruns true if this state contains valid data, which can be dropped or moved. + pub fn validity(&self) -> Validity { + match self.state() { + State::None => unreachable!(), + State::Filled => Validity::Valid, + State::MaybeFilled => Validity::Maybe, + State::Empty | State::Moved | State::Dropped => Validity::Not, + } + } + /// Notifies the state that a local has been killed + pub fn kill_local(&mut self, local: Local) { + // self.anons.remove(&local); + self.borrows.remove(&local); + self.phase_borrow.retain(|(phase_local, _place)| *phase_local != local); + self.containers.remove(&local); + } + + pub fn add_anon(&mut self, anon: Local, src: Place<'tcx>) { + self.anons.entry(anon).or_default().places.push(src); + } + + pub fn add_anon_places(&mut self, anon: Local, places: AnonStorage<'tcx>) { + let old_places = self.anons.insert(anon, places); + assert!(old_places.is_none(), "Have fun debugging this one"); + } + + #[expect(unused)] + pub fn add_container(&mut self, anon: Local, info: ContainerInfo) { + self.containers + .entry(anon) + .and_modify(|other| other.content.extend(info.content.iter())) + .or_insert(info); + } + + /// This tries to remove the given place from the known anons that hold this value. + /// It will retrun `true`, if the removal was successfull. + /// Places with projections will be ignored. + pub fn remove_anon(&mut self, anon: &Place<'_>) -> Option> { + let found = self.remove_anon_place(anon); + // assert!(found.is_none() || anon.just_local(), "{self:#?} - {anon:#?}"); + found + } + + pub fn remove_anon_place(&mut self, anon: &Place<'_>) -> Option> { + self.anons.remove(&anon.local) + } + + /// This clears this state. The `state` field has to be set afterwards + pub fn clear(&mut self, new_state: State) { + self.anons.clear(); + self.borrows.clear(); + self.phase_borrow.clear(); + + self.state.push((new_state, self.bb)); + } + + pub fn add_assign(&mut self, place: Place<'tcx>, pats: &mut BTreeSet) { + let is_override = match self.state() { + // No-op the most normal and simple state + State::Moved | State::Empty => false, + + State::Dropped => { + // A manual drop has `Moved` as the previous state + if matches!(self.prev_state(), Some(State::Filled | State::MaybeFilled)) { + pats.insert(OwnedPat::DropForReplacement); + true + } else { + false + } + }, + + // Filled should only ever be the case for !Drop types + State::Filled | State::MaybeFilled => true, + + State::None => unreachable!(), + }; + if place.just_local() { + if is_override { + pats.insert(OwnedPat::Overwrite); + } + // Regardless of the original state, we clear everything else + self.clear(State::Filled); + } else if place.is_part() { + if is_override { + pats.insert(OwnedPat::OverwritePart); + } + } else { + unreachable!(); + } + } + + pub fn add_borrow( + &mut self, + borrow: Place<'tcx>, + broker: Place<'tcx>, + kind: BorrowKind, + bro_kind: Option, + info: &AnalysisInfo<'tcx>, + pats: &mut BTreeSet, + ) { + self.update_bros(broker, kind.mutability(), info); + + if matches!(kind, BorrowKind::Shared) + && self + .phase_borrow + .iter() + .any(|(_loc, phase_place)| info.places_conflict(*phase_place, broker)) + { + pats.insert(OwnedPat::TwoPhasedBorrow); + info.stats.borrow_mut().owned.two_phased_borrows += 1; + } + + let (is_all, is_part) = (broker.just_local(), broker.is_part()); + + let is_named = matches!(info.locals[borrow.local].kind, LocalKind::UserVar(..)); + if is_named { + if matches!(kind, BorrowKind::Shared) { + info.stats.borrow_mut().owned.named_borrow_count += 1; + if is_all { + pats.insert(OwnedPat::NamedBorrow); + } else if is_part { + pats.insert(OwnedPat::PartNamedBorrow); + } else { + unreachable!(); + } + } else { + info.stats.borrow_mut().owned.named_borrow_mut_count += 1; + if is_all { + pats.insert(OwnedPat::NamedBorrowMut); + } else if is_part { + pats.insert(OwnedPat::PartNamedBorrowMut); + } else { + unreachable!(); + } + } + } + + let bro_kind = if let Some(bro_kind) = bro_kind { + bro_kind + } else if is_named { + BroKind::Named + } else { + BroKind::Anon + }; + + // So: It turns out that MIR is an inconsisten hot mess. Two-Phase-Borrows are apparently + // allowed to violate rust borrow semantics... + // + // Simple example: `x.push(x.len())` + if is_named { + // Mut loans can also be used for two-phased-borrows, but only with themselfs. + // Taking the mut loan and the owned value failes. + // + // ``` + // fn test(mut vec: Vec) { + // let loan = &mut vec; + // loan.push(vec.len()); + // } + // ``` + // + // The two-phased-borrow will be detected by the owned reference. So we can + // ignore it here :D + self.borrows + .insert(borrow.local, BorrowInfo::new(broker, kind.mutability(), bro_kind)); + } else { + assert!(borrow.just_local()); + if kind.allows_two_phase_borrow() { + self.phase_borrow.push((borrow.local, broker)); + } else { + self.borrows + .insert(borrow.local, BorrowInfo::new(broker, kind.mutability(), bro_kind)); + } + } + } + + /// This function informs the state, that a local loan was just copied. + pub fn add_ref_copy( + &mut self, + dst: Place<'tcx>, + src: Place<'tcx>, + info: &AnalysisInfo<'tcx>, + pats: &mut BTreeSet, + ) { + self.add_ref_dep(dst, src, info, pats); + } + /// This function informs the state that a ref to a ref was created + pub fn add_ref_ref( + &mut self, + dst: Place<'tcx>, + src: Place<'tcx>, + info: &AnalysisInfo<'tcx>, + pats: &mut BTreeSet, + ) { + self.add_ref_dep(dst, src, info, pats); + } + /// If `kind` is empty it indicates that the mutability of `src` should be taken + fn add_ref_dep( + &mut self, + dst: Place<'tcx>, + src: Place<'tcx>, + info: &AnalysisInfo<'tcx>, + pats: &mut BTreeSet, + ) { + // This function has to share quite some magic with `add_borrow` but + // again is different enough that they can't be merged directly AFAIK + + let Some(bro_info) = self.borrows.get(&src.local).copied() else { + return; + }; + + // It looks like loans preserve the mutability of th copy. This is perfectly + // inconsitent. Maybe the previous `&mut (*_2)` came from a different + // MIR version. At this point there is no value in even checking. + // + // Looking at `temp_borrow_mixed_2` it seems like the copy mutability depends + // on the use case. I'm not even disappointed anymore + match bro_info.kind { + BroKind::Dep | BroKind::Named => { + // FIXME: Maybe this doesn't even needs to be tracked? + self.borrows.insert(dst.local, bro_info.copy_with(BroKind::Dep)); + }, + // Only anons should be able to add new information + BroKind::Anon => { + let (is_all, is_part) = bro_info.borrowed_props(); + let is_named = matches!(info.locals[dst.local].kind, LocalKind::UserVar(..)); + if is_named { + // FIXME: THis is broken: + if matches!(bro_info.muta, Mutability::Mut) { + info.stats.borrow_mut().owned.named_borrow_mut_count += 1; + if is_all { + pats.insert(OwnedPat::NamedBorrow); + } else if is_part { + pats.insert(OwnedPat::PartNamedBorrow); + } else { + unreachable!(); + } + } else { + info.stats.borrow_mut().owned.named_borrow_count += 1; + + if is_all { + pats.insert(OwnedPat::NamedBorrowMut); + } else if is_part { + pats.insert(OwnedPat::PartNamedBorrowMut); + } else { + unreachable!(); + } + } + } + + let new_bro_kind = if is_named { BroKind::Named } else { BroKind::Anon }; + + self.borrows.insert(dst.local, bro_info.copy_with(new_bro_kind)); + }, + } + } + + fn update_bros(&mut self, broker: Place<'tcx>, muta: Mutability, info: &AnalysisInfo<'tcx>) { + // I switch on muta before the `retain`, to make the `retain` specialized and therefore faster. + match muta { + // Not mutable aka aliasable + Mutability::Not => self.borrows.retain(|_key, bro_info| { + !(matches!(bro_info.muta, Mutability::Mut) && info.places_conflict(bro_info.broker, broker)) + }), + Mutability::Mut => self + .borrows + .retain(|_key, bro_info| !info.places_conflict(bro_info.broker, broker)), + } + } + + pub fn has_bro(&self, anon: &Place<'_>) -> Option> { + if let Some((_loc, place)) = self.phase_borrow.iter().find(|(local, _place)| *local == anon.local) { + // TwoPhaseBorrows are always mutable + Some(BorrowInfo::new(*place, Mutability::Mut, BroKind::Anon)) + } else { + self.borrows.get(&anon.local).copied() + } + } +} + +impl<'a, 'tcx> MyStateInfo> for StateInfo<'tcx> { + fn new(bb: BasicBlock) -> Self { + Self { + bb, + state: Default::default(), + anons: Default::default(), + borrows: Default::default(), + phase_borrow: Default::default(), + containers: Default::default(), + } + } + + fn join(&mut self, state_owner: &mut super::OwnedAnalysis<'a, 'tcx>, bb: BasicBlock) -> bool { + let other = &state_owner.states[bb]; + if other.state.is_empty() { + return false; + } + assert_ne!(other.state(), State::None); + + // Base case where `self` is uninit + if self.state.is_empty() { + let bb = self.bb; + *self = other.clone(); + self.bb = bb; + return true; + } + + let self_state = self.state.last().copied().unwrap(); + let other_state = other.state.last().copied().unwrap(); + if self.state.len() != other.state.len() || self_state != other_state { + // println!("- Merge:"); + // println!(" - {:?}", self.state); + // println!(" - {:?}", other.state); + let other_events = inspect_deviation( + &self.state, + &other.state, + &mut state_owner.pats, + |(base, _), deviation, pats| { + // println!("- Case 1 | 2:"); + // println!(" - {base:?}"); + // println!(" - {deviation:?}"); + if matches!(base, State::Filled) { + let has_fill = deviation.iter().any(|(state, _)| matches!(state, State::Filled)); + if has_fill { + pats.insert(OwnedPat::ConditionalOverwride); + } + + let has_drop = deviation.iter().any(|(state, _)| matches!(state, State::Dropped)); + if has_drop { + pats.insert(OwnedPat::ConditionalDrop); + } + + let has_move = deviation.iter().any(|(state, _)| matches!(state, State::Moved)); + if has_move { + pats.insert(OwnedPat::ConditionalMove); + } + } + }, + |(base, _), a, b, pats| { + // println!("- Case 3:"); + // println!(" - {base:?}"); + // println!(" - {a:?}"); + // println!(" - {b:?}"); + if matches!(base, State::Empty) { + let a_fill = a.iter().any(|(state, _)| matches!(state, State::Filled)); + let b_fill = b.iter().any(|(state, _)| matches!(state, State::Filled)); + + if a_fill || b_fill { + pats.insert(OwnedPat::ConditionalInit); + } + } + }, + ); + self.state.extend(other_events.iter().copied()); + + // TODO: Proper merging here + let new_state = match (self.validity(), other.validity()) { + (Validity::Valid, Validity::Valid) => State::Filled, + (Validity::Not, Validity::Not) => State::Empty, + (_, _) => State::MaybeFilled, + }; + self.state.push((new_state, self.bb)); + } + + for (anon, other_places) in &other.anons { + if let Some(self_places) = self.anons.get_mut(anon) { + if self_places != other_places { + todo!(); + } + } else { + self.anons.insert(*anon, other_places.clone()); + } + } + + // FIXME: Here we can have collisions where two anons reference different places... oh no... + self.borrows.extend(other.borrows.iter()); + + self.phase_borrow.extend(other.phase_borrow.iter()); + + true + } + + fn check_continue_diff_for_pats(&self, _state_owner: &mut super::OwnedAnalysis<'a, 'tcx>, _con_block: BasicBlock) { + todo!(); + } +} + +/// ```text +/// Case 1 Case 2 Case 3 // +/// x x x // +/// / | | \ / \ // +/// * | | * * * // +/// \ | | / \ / // +/// x x x // +/// ``` +/// This returns the deviation of the additional events from the b branch to be +/// added to the a collection for the next iteration. +fn inspect_deviation<'b>( + a: &[(State, BasicBlock)], + b: &'b [(State, BasicBlock)], + pats: &mut BTreeSet, + mut single_devitation: impl FnMut((State, BasicBlock), &[(State, BasicBlock)], &mut BTreeSet), + mut split_devitation: impl FnMut( + (State, BasicBlock), + &[(State, BasicBlock)], + &[(State, BasicBlock)], + &mut BTreeSet, + ), +) -> &'b [(State, BasicBlock)] { + let a_state = a.last().copied().unwrap(); + let b_state = b.last().copied().unwrap(); + + // Case 1 + if let Some(idx) = a.iter().rposition(|state| *state == b_state) { + let base = a[idx]; + single_devitation(base, &a[(idx + 1)..], pats); + return &[]; + } + + // Case 2 + if let Some(idx) = b.iter().rposition(|state| *state == a_state) { + let base = b[idx]; + single_devitation(base, &b[(idx + 1)..], pats); + return &b[(idx + 1)..]; + } + + let mut b_set = GrowableBitSet::with_capacity(a_state.1.as_usize().max(b_state.1.as_usize()) + 1); + for (_, bb) in b { + b_set.insert(*bb); + } + + // Case 3 + if let Some((a_idx, &base)) = a.iter().enumerate().rev().find(|(_, (_, bb))| b_set.contains(*bb)) + && let Some(b_idx) = b.iter().rposition(|state| *state == base) + { + split_devitation(base, &a[(a_idx + 1)..], &b[(b_idx + 1)..], pats); + return &b[(b_idx + 1)..]; + } + + unreachable!() +} diff --git a/src/tools/clippy/clippy_lints/src/borrow_pats/prelude.rs b/src/tools/clippy/clippy_lints/src/borrow_pats/prelude.rs new file mode 100644 index 0000000000000..2ee7c016ac9cf --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/borrow_pats/prelude.rs @@ -0,0 +1,30 @@ +// Aliases +pub use rustc_middle::mir; + +// Traits: +pub use super::rustc_extention::{BodyMagic, LocalMagic, PlaceMagic}; +pub use itertools::Itertools; +pub use rustc_lint::LateLintPass; +pub use rustc_middle::mir::visit::Visitor; + +// Data Structures +pub use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +pub use rustc_index::bit_set::BitSet; +pub use rustc_index::IndexVec; +pub use smallvec::SmallVec; +pub use std::collections::{BTreeMap, BTreeSet}; + +// Common Types +pub use super::{AnalysisInfo, LocalKind, Validity}; +pub use rustc_ast::Mutability; +pub use rustc_hir::def_id::{DefId, LocalDefId}; +pub use rustc_middle::mir::{ + BasicBlock, BasicBlockData, BorrowKind, Local, Location, Operand, Place, PlaceElem, Rvalue, Statement, + StatementKind, Terminator, TerminatorKind, VarDebugInfo, VarDebugInfoContents, +}; +pub use rustc_middle::ty::TyCtxt; +pub use rustc_span::{sym, Symbol}; + +// Consts +pub use rustc_middle::mir::START_BLOCK; +pub const RETURN_LOCAL: Local = Local::from_u32(0); diff --git a/src/tools/clippy/clippy_lints/src/borrow_pats/rustc_extention.rs b/src/tools/clippy/clippy_lints/src/borrow_pats/rustc_extention.rs new file mode 100644 index 0000000000000..6036fbb6d94c0 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/borrow_pats/rustc_extention.rs @@ -0,0 +1,102 @@ +use mid::mir::{Local, Place}; +use rustc_hir as hir; +use rustc_middle::{self as mid, mir}; +use std::collections::VecDeque; + +/// Extending the [`mir::Body`] where needed. +/// +/// This is such a bad name for a trait, sorry. +pub trait BodyMagic { + fn are_bbs_exclusive(&self, a: mir::BasicBlock, b: mir::BasicBlock) -> bool; +} + +impl<'tcx> BodyMagic for mir::Body<'tcx> { + fn are_bbs_exclusive(&self, a: mir::BasicBlock, b: mir::BasicBlock) -> bool { + #[expect(clippy::comparison_chain)] + if a == b { + return false; + } else if a > b { + return self.are_bbs_exclusive(b, a); + } + + let mut visited = Vec::with_capacity(16); + let mut queue = VecDeque::with_capacity(16); + + queue.push_back(a); + while let Some(bbi) = queue.pop_front() { + // Check we don't know the node yet + if visited.contains(&bbi) { + continue; + } + + // Found our connection + if bbi == b { + return false; + } + + self.basic_blocks[bbi] + .terminator() + .successors() + .collect_into(&mut queue); + visited.push(bbi); + } + + true + } +} + +pub trait PlaceMagic { + /// This returns true, if this is only a part of the local. A field or array + /// element would be a part of a local. + fn is_part(&self) -> bool; + + /// Returns true if this is only a local. Any projections, field accesses or + /// other non local things will return false. + fn just_local(&self) -> bool; +} + +impl PlaceMagic for mir::Place<'_> { + fn is_part(&self) -> bool { + self.projection.iter().any(|x| { + matches!( + x, + mir::PlaceElem::Field(_, _) + | mir::PlaceElem::Index(_) + | mir::PlaceElem::ConstantIndex { .. } + | mir::PlaceElem::Subslice { .. } + ) + }) + } + + fn just_local(&self) -> bool { + self.projection.is_empty() + } +} + +pub trait LocalMagic { + #[expect(clippy::wrong_self_convention)] + fn as_place(self) -> Place<'static>; +} + +impl LocalMagic for Local { + fn as_place(self) -> Place<'static> { + Place { + local: self, + projection: rustc_middle::ty::List::empty(), + } + } +} + +pub fn print_body(body: &mir::Body<'_>) { + for (idx, data) in body.basic_blocks.iter_enumerated() { + println!("bb{}:", idx.index()); + for stmt in &data.statements { + println!(" {stmt:#?}"); + } + println!(" {:#?}", data.terminator().kind); + + println!(); + } + + //println!("{body:#?}"); +} diff --git a/src/tools/clippy/clippy_lints/src/borrow_pats/stats.rs b/src/tools/clippy/clippy_lints/src/borrow_pats/stats.rs new file mode 100644 index 0000000000000..1e5ab2e89424b --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/borrow_pats/stats.rs @@ -0,0 +1,221 @@ +use std::collections::BTreeSet; + +use rustc_data_structures::fx::FxHashMap; +use rustc_middle::mir; + +use super::body::{BodyInfo, BodyPat}; +use super::owned::OwnedPat; +use super::VarInfo; + +#[derive(Debug, Default)] +pub struct CrateStats { + aggregated_body_stats: BodyStats, + body_ctn: usize, + total_bb_ctn: usize, + total_local_ctn: usize, + max_bb_ctn: usize, + max_local_ctn: usize, + owned_pats: FxHashMap<(VarInfo, BTreeSet), usize>, + body_pats: FxHashMap<(BodyInfo, BTreeSet), usize>, + total_wrap: bool, +} + +#[derive(Debug, serde::Serialize)] +pub struct CrateStatsSerde { + aggregated_body_stats: BodyStats, + body_ctn: usize, + total_bb_ctn: usize, + total_local_ctn: usize, + max_bb_ctn: usize, + max_local_ctn: usize, + owned_pats: Vec<(VarInfo, BTreeSet, usize)>, + body_pats: Vec<(BodyInfo, BTreeSet, usize)>, + total_wrap: bool, +} + +impl CrateStats { + pub fn add_pat(&mut self, var: VarInfo, pats: BTreeSet) { + let pat_ctn = self.owned_pats.entry((var, pats)).or_default(); + *pat_ctn += 1; + } + + pub fn add_body(&mut self, body: &mir::Body<'_>, stats: BodyStats, info: BodyInfo, pats: BTreeSet) { + // BBs + { + let bb_ctn = body.basic_blocks.len(); + self.max_bb_ctn = self.max_bb_ctn.max(bb_ctn); + let (new_total, wrapped) = self.total_bb_ctn.overflowing_add(bb_ctn); + self.total_bb_ctn = new_total; + self.total_wrap |= wrapped; + } + // Locals + { + let local_ctn = body.local_decls.len(); + self.max_local_ctn = self.max_local_ctn.max(local_ctn); + let (new_total, wrapped) = self.total_local_ctn.overflowing_add(local_ctn); + self.total_local_ctn = new_total; + self.total_wrap |= wrapped; + } + + self.aggregated_body_stats += stats; + + { + let pat_ctn = self.body_pats.entry((info, pats)).or_default(); + *pat_ctn += 1; + } + + self.body_ctn += 1; + } + + pub fn into_serde(self) -> CrateStatsSerde { + let Self { + aggregated_body_stats, + body_ctn, + total_bb_ctn, + total_local_ctn, + max_bb_ctn, + max_local_ctn, + owned_pats, + body_pats, + total_wrap, + } = self; + + let owned_pats = owned_pats + .into_iter() + .map(|((info, pat), ctn)| (info, pat, ctn)) + .collect(); + let body_pats = body_pats + .into_iter() + .map(|((info, pat), ctn)| (info, pat, ctn)) + .collect(); + + CrateStatsSerde { + aggregated_body_stats, + body_ctn, + total_bb_ctn, + total_local_ctn, + max_bb_ctn, + max_local_ctn, + owned_pats, + body_pats, + total_wrap, + } + } +} + +/// Most of these statistics need to be filled by the individual analysis passed. +/// Every value should document which pass might modify/fill it. +/// +/// Without more context and tracking the data flow, it's impossible to know what +/// certain instructions are. +/// +/// For example, a named borrow can have different shapes. Assuming `_1` is the +/// owned value and `_2` is the named references, they could have the following +/// shapes: +/// +/// ```ignore +/// // Direct +/// _2 = &_1 +/// +/// // Indirect +/// _3 = &_1 +/// _2 = &(*_3) +/// +/// // Indirect + Copy +/// _3 = &_1 +/// _4 = &(*_3) +/// _2 = move _4 +/// ``` +#[derive(Debug, Clone, Default, serde::Serialize)] +pub struct BodyStats { + /// Number of relations between the arguments and the return value accoring + /// to the function signature + /// + /// Filled by `BodyAnalysis` + pub return_relations_signature: usize, + /// Number of relations between the arguments and the return value that have + /// been found inside the body + /// + /// Filled by `BodyAnalysis` + pub return_relations_found: usize, + /// Number of relations between arguments according to the signature + /// + /// Filled by `BodyAnalysis` + pub arg_relations_signature: usize, + /// Number of relations between arguments that have been found in the body + /// + /// Filled by `BodyAnalysis` + pub arg_relations_found: usize, + /// This mainly happens, if the input has one generic and returns another generic. + /// If the same generic is returned. + pub arg_relation_possibly_missed_due_generics: usize, + pub arg_relation_possibly_missed_due_to_late_bounds: usize, + + pub ref_stmt_ctn: usize, + + /// Stats about named owned values + pub owned: OwnedStats, +} + +impl std::ops::AddAssign for BodyStats { + fn add_assign(&mut self, rhs: Self) { + self.return_relations_signature += rhs.return_relations_signature; + self.return_relations_found += rhs.return_relations_found; + self.arg_relations_signature += rhs.arg_relations_signature; + self.arg_relations_found += rhs.arg_relations_found; + self.arg_relation_possibly_missed_due_generics += rhs.arg_relation_possibly_missed_due_generics; + self.arg_relation_possibly_missed_due_to_late_bounds += rhs.arg_relation_possibly_missed_due_to_late_bounds; + self.ref_stmt_ctn += rhs.ref_stmt_ctn; + self.owned += rhs.owned; + } +} + +/// Stats for owned variables +/// +/// All of these are collected by the `OwnedAnalysis` +#[derive(Debug, Clone, Default, serde::Serialize)] +pub struct OwnedStats { + /// Temp borrows are used for function calls. + /// + /// The MIR commonly looks like this: + /// ```ignore + /// _3 = &_1 + /// _4 = &(*_3) + /// _2 = function(move _4) + /// ``` + pub arg_borrow_count: usize, + pub arg_borrow_mut_count: usize, + /// Temporary borrows might be extended if the returned value depends on the input. + /// + /// The temporary borrows are also added to the trackers above. + pub arg_borrow_extended_count: usize, + pub arg_borrow_mut_extended_count: usize, + /// A loan was created and stored to a named place. + /// + /// See comment of [`BodyStats`] for ways this might be expressed in MIR. + pub named_borrow_count: usize, + pub named_borrow_mut_count: usize, + /// A loan was created for a closure + pub borrowed_for_closure_count: usize, + pub borrowed_mut_for_closure_count: usize, + /// These are collected by the `OwnedAnalysis` + /// + /// Note: + /// - This only counts the confirmed two phased borrows. + /// - The borrows that produce the two phased borrow are also counted above. + pub two_phased_borrows: usize, +} + +impl std::ops::AddAssign for OwnedStats { + fn add_assign(&mut self, rhs: Self) { + self.arg_borrow_count += rhs.arg_borrow_count; + self.arg_borrow_mut_count += rhs.arg_borrow_mut_count; + self.arg_borrow_extended_count += rhs.arg_borrow_extended_count; + self.arg_borrow_mut_extended_count += rhs.arg_borrow_mut_extended_count; + self.named_borrow_count += rhs.named_borrow_count; + self.named_borrow_mut_count += rhs.named_borrow_mut_count; + self.borrowed_for_closure_count += rhs.borrowed_for_closure_count; + self.borrowed_mut_for_closure_count += rhs.borrowed_mut_for_closure_count; + self.two_phased_borrows += rhs.two_phased_borrows; + } +} diff --git a/src/tools/clippy/clippy_lints/src/borrow_pats/util.rs b/src/tools/clippy/clippy_lints/src/borrow_pats/util.rs new file mode 100644 index 0000000000000..7c30e9b237130 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/borrow_pats/util.rs @@ -0,0 +1,275 @@ +#![warn(unused)] + +use clippy_utils::ty::{for_each_param_ty, for_each_ref_region, for_each_region}; +use rustc_ast::Mutability; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_middle::mir::{Body, Local, Operand, Place}; +use rustc_middle::ty::{self, FnSig, GenericArgsRef, GenericPredicates, Region, Ty, TyCtxt}; +use rustc_span::source_map::Spanned; + +use crate::borrow_pats::{LocalMagic, PlaceMagic}; + +mod visitor; +pub use visitor::*; + +use super::prelude::RETURN_LOCAL; +use super::BodyStats; + +const RETURN_RELEATION_INDEX: usize = usize::MAX; + +pub struct PrintPrevent(pub T); + +impl std::fmt::Debug for PrintPrevent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("PrintPrevent").finish() + } +} + +/// A helper struct to build relations between function arguments and the return +/// +/// I really should stop using such stupid names. At this pooint I'm just making fun +/// of everything to make this work somehow tollerable. +#[derive(Debug)] +struct FuncReals<'tcx> { + /// A list of several universes + /// + /// Mapping from `'short` (key) is outlives by `'long` (value) + multiverse: FxHashMap, FxHashSet>>, + sig: FnSig<'tcx>, + args: GenericArgsRef<'tcx>, + /// Indicates that a possibly returned value has generics with `'ReErased` + has_generic_probles: bool, +} + +impl<'tcx> FuncReals<'tcx> { + fn from_fn_def(tcx: TyCtxt<'tcx>, def_id: DefId, args: GenericArgsRef<'tcx>) -> Self { + // FIXME: The proper and long therm solution would be to use HIR + // to find the call with generics that still have valid region markers. + // However, for now I need to get this zombie in the air and not pefect + let fn_sig = tcx.fn_sig(def_id).instantiate_identity(); + + // On other functions this shouldn't matter. Even if they have late bounds + // in their signature. We don't know how it's used and more imporantly, + // The input and return types still need to follow Rust's type rules + let fn_sig = fn_sig.skip_binder(); + + let mut reals = Self { + multiverse: Default::default(), + sig: fn_sig, + args, + has_generic_probles: false, + }; + + // FYI: Predicates don't include transitive bounds + let item_predicates = tcx.predicates_of(def_id); + // TODO Test: `inferred_outlives_of` + reals.build_multiverse(item_predicates); + + reals + } + + fn build_multiverse(&mut self, predicates: GenericPredicates<'tcx>) { + let preds = predicates + .predicates + .iter() + .filter_map(|(clause, _span)| clause.as_region_outlives_clause()); + + // I know this can be done in linear time, but I wasn't able to get this to + // work quickly. So I'll do the n^2 version for now + for binder in preds { + // By now I believe (aka. wish) this is unimportant and can be removed. + // But first I need to find something which actually triggers this todo. + if !binder.bound_vars().is_empty() { + todo!("Non empty depressing bounds 2: {binder:#?}"); + } + + let constaint = binder.skip_binder(); + let long = constaint.0; + let short = constaint.1; + + let longi_verse = self.multiverse.get(&long).cloned().unwrap_or_default(); + let shorti_verse = self.multiverse.entry(short).or_default(); + if !shorti_verse.insert(long) { + continue; + } + shorti_verse.extend(longi_verse); + + for universe in self.multiverse.values_mut() { + if universe.contains(&short) { + universe.insert(long); + } + } + } + } + + fn relations(&mut self, dest: Local, args: &[Spanned>]) -> FxHashMap> { + let mut reals = FxHashMap::default(); + let ret_rels = self.return_relations(); + if !ret_rels.is_empty() { + let locals: Vec<_> = ret_rels + .into_iter() + .filter_map(|idx| args[idx].node.place()) + .map(|place| place.local) + .collect(); + if !locals.is_empty() { + reals.insert(dest, locals); + } + } + + for (arg_index, arg_ty) in self.sig.inputs().iter().enumerate() { + let mut arg_rels = FxHashSet::default(); + for_each_ref_region(*arg_ty, &mut |_reg, child_ty, mutability| { + // `&X` is not really interesting here + if matches!(mutability, Mutability::Mut) { + // This should be added here for composed types, like (&mut u32, &mut f32) + arg_rels.extend(self.find_relations(child_ty, arg_index)); + } + }); + + if !arg_rels.is_empty() { + // It has to be a valid place, since we found a location + let place = args[arg_index].node.place().unwrap(); + assert!(place.just_local()); + + let locals: Vec<_> = arg_rels + .into_iter() + .filter_map(|idx| args[idx].node.place()) + .map(|place| place.local) + .collect(); + if !locals.is_empty() { + reals.insert(place.local, locals); + } + } + } + + reals + } + + /// This function takes an operand, that identifies a function and returns the + /// indices of the arguments that might be parents of the return type. + /// + /// ```ignore + /// fn example<'c, 'a: 'c, 'b: 'c>(cond: bool, a: &'a u32, b: &'b u32) -> &'c u32 { + /// # todo!() + /// } + /// ``` + /// This would return [1, 2], since the types in position 1 and 2 are related + /// to the return type. + fn return_relations(&mut self) -> FxHashSet { + self.find_relations(self.sig.output(), RETURN_RELEATION_INDEX) + } + + fn find_relations(&mut self, child_ty: Ty<'tcx>, child_index: usize) -> FxHashSet { + let mut child_regions = FxHashSet::default(); + for_each_region(child_ty, |region| { + if child_regions.insert(region) { + if let Some(longer_regions) = self.multiverse.get(®ion) { + child_regions.extend(longer_regions); + } + } + }); + if child_index == RETURN_RELEATION_INDEX { + for_each_param_ty(child_ty, &mut |param_ty| { + if let Some(arg) = self.args.get(param_ty.index as usize) { + if let Some(arg_ty) = arg.as_type() { + for_each_region(arg_ty, |_| { + self.has_generic_probles = true; + }); + } + }; + }); + } + + let mut parents = FxHashSet::default(); + if child_regions.is_empty() { + return parents; + } + + for (index, ty) in self.sig.inputs().iter().enumerate() { + if index == child_index { + continue; + } + + // "Here to stab things, don't case" + for_each_ref_region(*ty, &mut |reg, _ty, _mutability| { + if child_regions.contains(®) { + parents.insert(index); + } + }); + } + + parents + } +} + +pub fn calc_call_local_relations<'tcx>( + tcx: TyCtxt<'tcx>, + body: &Body<'tcx>, + func: &Operand<'tcx>, + dest: Local, + args: &[Spanned>], + stats: &mut BodyStats, +) -> FxHashMap> { + let mut builder; + if let Some((def_id, generic_args)) = func.const_fn_def() { + builder = FuncReals::from_fn_def(tcx, def_id, generic_args); + } else if let Some(place) = func.place() { + let local_ty = body.local_decls[place.local].ty; + if let ty::FnDef(def_id, generic_args) = local_ty.kind() { + builder = FuncReals::from_fn_def(tcx, *def_id, generic_args); + } else { + stats.arg_relation_possibly_missed_due_to_late_bounds += 1; + return FxHashMap::default(); + } + } else { + unreachable!() + } + let relations = builder.relations(dest, args); + + if builder.has_generic_probles { + stats.arg_relation_possibly_missed_due_generics += 1; + } + + relations +} + +#[expect(clippy::needless_lifetimes)] +pub fn calc_fn_arg_relations<'tcx>(tcx: TyCtxt<'tcx>, fn_id: LocalDefId) -> FxHashMap> { + // This function is amazingly hacky, but at this point I really don't care anymore + let mut builder = FuncReals::from_fn_def(tcx, fn_id.into(), rustc_middle::ty::List::empty()); + let arg_ctn = builder.sig.inputs().len(); + let fake_args: Vec<_> = (0..arg_ctn) + .map(|idx| { + // `_0` is the return, the arguments start at `_1` + let place = Local::from_usize(idx + 1).as_place(); + let place = unsafe { std::mem::transmute::, Place<'tcx>>(place) }; + Spanned { + node: Operand::Move(place), + span: rustc_span::DUMMY_SP, + } + }) + .collect(); + + builder.relations(RETURN_LOCAL, &fake_args[..]) +} + +pub fn has_mut_ref(ty: Ty<'_>) -> bool { + let mut has_mut = false; + for_each_ref_region(ty, &mut |_reg, _ref_ty, mutability| { + // `&X` is not really interesting here + has_mut |= matches!(mutability, Mutability::Mut); + }); + has_mut +} + +/// Indicates the validity of a value. +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub enum Validity { + /// Is valid on all paths + Valid, + /// Maybe filled with valid data + Maybe, + /// Not filled with valid data + Not, +} diff --git a/src/tools/clippy/clippy_lints/src/borrow_pats/util/visitor.rs b/src/tools/clippy/clippy_lints/src/borrow_pats/util/visitor.rs new file mode 100644 index 0000000000000..2c0e8928cde01 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/borrow_pats/util/visitor.rs @@ -0,0 +1,171 @@ +use std::collections::VecDeque; + +use rustc_middle::mir::Body; + +use crate::borrow_pats::CfgInfo; + +use super::super::prelude::*; +pub trait MyStateInfo: Eq + Clone + std::fmt::Debug { + fn new(bb: BasicBlock) -> Self; + + /// The return value indicates if the visitor has changed. + fn join(&mut self, state_owner: &mut V, bb: BasicBlock) -> bool; + + /// This function is called on the input state of a block to compare itself, + /// to the `con_block` which jumps from within a loop to this state. + fn check_continue_diff_for_pats(&self, state_owner: &mut V, con_block: BasicBlock); +} + +pub trait MyVisitor<'tcx>: Visitor<'tcx> + std::marker::Sized { + type State: MyStateInfo; + + fn init_start_block_state(&mut self); + + fn set_state(&mut self, bb: BasicBlock, state: Self::State); +} + +pub fn visit_body_with_state<'tcx, V: MyVisitor<'tcx>>(vis: &mut V, info: &AnalysisInfo<'tcx>) { + let mut stalled_joins = FxHashMap::default(); + for visit in info.visit_order.iter().copied() { + match visit { + VisitKind::Next(bb) => { + // Init state + if bb == START_BLOCK { + vis.init_start_block_state(); + } else { + let preds = &info.preds[bb]; + let mut state = V::State::new(bb); + let mut stage_stalled = false; + preds.iter().for_each(|bb| { + stage_stalled |= !state.join(vis, *bb); + }); + if stage_stalled { + stalled_joins.insert(bb, state.clone()); + } + vis.set_state(bb, state); + } + + // Walk block + vis.visit_basic_block_data(bb, &info.body.basic_blocks[bb]); + }, + VisitKind::Continue { from, to } => { + let state = stalled_joins[&to].clone(); + state.check_continue_diff_for_pats(vis, from); + }, + } + } +} + +pub fn visit_body<'tcx, V: Visitor<'tcx>>(vis: &mut V, info: &AnalysisInfo<'tcx>) { + for visit in info.visit_order.iter().copied() { + if let VisitKind::Next(bb) = visit { + // Walk block + vis.visit_basic_block_data(bb, &info.body.basic_blocks[bb]); + } + } +} + +pub fn unloop_preds<'a>( + cfg: &'a IndexVec, + preds: &'a IndexVec>, +) -> IndexVec> { + struct Builder<'a> { + cfg: &'a IndexVec, + states: IndexVec, + unlooped: IndexVec>, + } + + impl<'a> Builder<'a> { + fn new( + cfg: &'a IndexVec, + preds: &'a IndexVec>, + ) -> Self { + let len = cfg.len(); + Self { + cfg, + states: IndexVec::from_elem_n(VisitState::Future, len), + unlooped: preds.clone(), + } + } + + fn visit(&mut self, from: BasicBlock, bb: BasicBlock) { + match self.states[bb] { + VisitState::Future => { + self.states[bb] = VisitState::Current; + match &self.cfg[bb] { + CfgInfo::Linear(next) => self.visit(bb, *next), + CfgInfo::Condition { branches } => { + for next in branches { + self.visit(bb, *next); + } + }, + CfgInfo::Return | CfgInfo::None => {}, + } + + self.states[bb] = VisitState::Past; + }, + VisitState::Current => { + self.unlooped[bb].retain(|x| *x != from); + }, + VisitState::Past => {}, + } + } + } // TODO: Check continues + + let mut builder = Builder::new(cfg, preds); + builder.visit(START_BLOCK, START_BLOCK); + builder.unlooped +} + +#[expect(unused)] +pub fn construct_visit_order( + body: &Body<'_>, + cfg: &IndexVec, + preds: &IndexVec>, +) -> Vec { + let bb_len = cfg.len(); + let mut visited: BitSet = BitSet::new_empty(bb_len); + let mut order: Vec = Vec::with_capacity(bb_len); + + let mut queue = VecDeque::new(); + queue.push_back(START_BLOCK); + while let Some(bb) = queue.pop_front() { + if visited.contains(bb) { + continue; + } + + let preds = &preds[bb]; + if preds.iter().all(|x| visited.contains(*x)) { + // Not all prerequisites are fulfilled. This bb will be added again by the other branch + continue; + } + + match &cfg[bb] { + CfgInfo::Linear(next) => queue.push_back(*next), + CfgInfo::Condition { branches } => queue.extend(branches.iter()), + CfgInfo::None | CfgInfo::Return => {}, + } + + order.push(VisitKind::Next(bb)); + visited.insert(bb); + } + + order +} + +#[derive(Debug, Copy, Clone)] +enum VisitState { + Future, + Current, + Past, +} + +#[derive(Debug, Copy, Clone)] +pub enum VisitKind { + Next(BasicBlock), + #[expect(unused)] + Continue { + from: BasicBlock, + to: BasicBlock, + }, +} diff --git a/src/tools/clippy/clippy_lints/src/declared_lints.rs b/src/tools/clippy/clippy_lints/src/declared_lints.rs index 5ff7d8e513435..c599fdcd67bb2 100644 --- a/src/tools/clippy/clippy_lints/src/declared_lints.rs +++ b/src/tools/clippy/clippy_lints/src/declared_lints.rs @@ -74,6 +74,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::booleans::NONMINIMAL_BOOL_INFO, crate::booleans::OVERLY_COMPLEX_BOOL_EXPR_INFO, crate::borrow_deref_ref::BORROW_DEREF_REF_INFO, + crate::borrow_pats::BORROW_PATS_INFO, crate::box_default::BOX_DEFAULT_INFO, crate::cargo::CARGO_COMMON_METADATA_INFO, crate::cargo::LINT_GROUPS_PRIORITY_INFO, diff --git a/src/tools/clippy/clippy_lints/src/explicit_write.rs b/src/tools/clippy/clippy_lints/src/explicit_write.rs index 724e1843359b2..3c4a043a732b7 100644 --- a/src/tools/clippy/clippy_lints/src/explicit_write.rs +++ b/src/tools/clippy/clippy_lints/src/explicit_write.rs @@ -1,12 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::macros::{find_format_args, format_args_inputs_span}; +use clippy_utils::macros::{format_args_inputs_span, FormatArgsStorage}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::{is_expn_of, path_def_id}; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::{BindingMode, Block, BlockCheckMode, Expr, ExprKind, Node, PatKind, QPath, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; use rustc_span::{sym, ExpnId}; declare_clippy_lint! { @@ -38,7 +38,17 @@ declare_clippy_lint! { "using the `write!()` family of functions instead of the `print!()` family of functions, when using the latter would work" } -declare_lint_pass!(ExplicitWrite => [EXPLICIT_WRITE]); +pub struct ExplicitWrite { + format_args: FormatArgsStorage, +} + +impl ExplicitWrite { + pub fn new(format_args: FormatArgsStorage) -> Self { + Self { format_args } + } +} + +impl_lint_pass!(ExplicitWrite => [EXPLICIT_WRITE]); impl<'tcx> LateLintPass<'tcx> for ExplicitWrite { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { @@ -57,7 +67,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite { Some(sym::io_stderr) => ("stderr", "e"), _ => return, }; - let Some(format_args) = find_format_args(cx, write_arg, ExpnId::root()) else { + let Some(format_args) = self.format_args.get(cx, write_arg, ExpnId::root()) else { return; }; @@ -83,7 +93,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite { }; let mut applicability = Applicability::MachineApplicable; let inputs_snippet = - snippet_with_applicability(cx, format_args_inputs_span(&format_args), "..", &mut applicability); + snippet_with_applicability(cx, format_args_inputs_span(format_args), "..", &mut applicability); span_lint_and_sugg( cx, EXPLICIT_WRITE, diff --git a/src/tools/clippy/clippy_lints/src/format.rs b/src/tools/clippy/clippy_lints/src/format.rs index 8a0cd155d211c..0b248f784b762 100644 --- a/src/tools/clippy/clippy_lints/src/format.rs +++ b/src/tools/clippy/clippy_lints/src/format.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::macros::{find_format_arg_expr, find_format_args, root_macro_call_first_node}; +use clippy_utils::macros::{find_format_arg_expr, root_macro_call_first_node, FormatArgsStorage}; use clippy_utils::source::{snippet_opt, snippet_with_context}; use clippy_utils::sugg::Sugg; use rustc_ast::{FormatArgsPiece, FormatOptions, FormatTrait}; @@ -7,7 +7,7 @@ use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; use rustc_span::{sym, Span}; declare_clippy_lint! { @@ -39,13 +39,24 @@ declare_clippy_lint! { "useless use of `format!`" } -declare_lint_pass!(UselessFormat => [USELESS_FORMAT]); +#[allow(clippy::module_name_repetitions)] +pub struct UselessFormat { + format_args: FormatArgsStorage, +} + +impl UselessFormat { + pub fn new(format_args: FormatArgsStorage) -> Self { + Self { format_args } + } +} + +impl_lint_pass!(UselessFormat => [USELESS_FORMAT]); impl<'tcx> LateLintPass<'tcx> for UselessFormat { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let Some(macro_call) = root_macro_call_first_node(cx, expr) && cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) - && let Some(format_args) = find_format_args(cx, expr, macro_call.expn) + && let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) { let mut applicability = Applicability::MachineApplicable; let call_site = macro_call.span; diff --git a/src/tools/clippy/clippy_lints/src/format_args.rs b/src/tools/clippy/clippy_lints/src/format_args.rs index 003a9995c15f3..86115807aa4d1 100644 --- a/src/tools/clippy/clippy_lints/src/format_args.rs +++ b/src/tools/clippy/clippy_lints/src/format_args.rs @@ -3,8 +3,8 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::is_diag_trait_item; use clippy_utils::macros::{ - find_format_arg_expr, find_format_args, format_arg_removal_span, format_placeholder_format_span, is_assert_macro, - is_format_macro, is_panic, matching_root_macro_call, root_macro_call_first_node, FormatParamUsage, MacroCall, + find_format_arg_expr, format_arg_removal_span, format_placeholder_format_span, is_assert_macro, is_format_macro, + is_panic, matching_root_macro_call, root_macro_call_first_node, FormatArgsStorage, FormatParamUsage, MacroCall, }; use clippy_utils::source::snippet_opt; use clippy_utils::ty::{implements_trait, is_type_lang_item}; @@ -167,15 +167,18 @@ impl_lint_pass!(FormatArgs => [ UNUSED_FORMAT_SPECS, ]); +#[allow(clippy::struct_field_names)] pub struct FormatArgs { + format_args: FormatArgsStorage, msrv: Msrv, ignore_mixed: bool, } impl FormatArgs { #[must_use] - pub fn new(msrv: Msrv, allow_mixed_uninlined_format_args: bool) -> Self { + pub fn new(format_args: FormatArgsStorage, msrv: Msrv, allow_mixed_uninlined_format_args: bool) -> Self { Self { + format_args, msrv, ignore_mixed: allow_mixed_uninlined_format_args, } @@ -186,13 +189,13 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { if let Some(macro_call) = root_macro_call_first_node(cx, expr) && is_format_macro(cx, macro_call.def_id) - && let Some(format_args) = find_format_args(cx, expr, macro_call.expn) + && let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) { let linter = FormatArgsExpr { cx, expr, macro_call: ¯o_call, - format_args: &format_args, + format_args, ignore_mixed: self.ignore_mixed, }; diff --git a/src/tools/clippy/clippy_lints/src/format_impl.rs b/src/tools/clippy/clippy_lints/src/format_impl.rs index 0a52347940abb..09be7237b5ca3 100644 --- a/src/tools/clippy/clippy_lints/src/format_impl.rs +++ b/src/tools/clippy/clippy_lints/src/format_impl.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; -use clippy_utils::macros::{find_format_arg_expr, find_format_args, is_format_macro, root_macro_call_first_node}; +use clippy_utils::macros::{find_format_arg_expr, is_format_macro, root_macro_call_first_node, FormatArgsStorage}; use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators}; use rustc_ast::{FormatArgsPiece, FormatTrait}; use rustc_errors::Applicability; @@ -99,13 +99,15 @@ struct FormatTraitNames { #[derive(Default)] pub struct FormatImpl { + format_args: FormatArgsStorage, // Whether we are inside Display or Debug trait impl - None for neither format_trait_impl: Option, } impl FormatImpl { - pub fn new() -> Self { + pub fn new(format_args: FormatArgsStorage) -> Self { Self { + format_args, format_trait_impl: None, } } @@ -129,6 +131,7 @@ impl<'tcx> LateLintPass<'tcx> for FormatImpl { if let Some(format_trait_impl) = self.format_trait_impl { let linter = FormatImplExpr { cx, + format_args: &self.format_args, expr, format_trait_impl, }; @@ -141,6 +144,7 @@ impl<'tcx> LateLintPass<'tcx> for FormatImpl { struct FormatImplExpr<'a, 'tcx> { cx: &'a LateContext<'tcx>, + format_args: &'a FormatArgsStorage, expr: &'tcx Expr<'tcx>, format_trait_impl: FormatTraitNames, } @@ -175,7 +179,7 @@ impl<'a, 'tcx> FormatImplExpr<'a, 'tcx> { if let Some(outer_macro) = root_macro_call_first_node(self.cx, self.expr) && let macro_def_id = outer_macro.def_id && is_format_macro(self.cx, macro_def_id) - && let Some(format_args) = find_format_args(self.cx, self.expr, outer_macro.expn) + && let Some(format_args) = self.format_args.get(self.cx, self.expr, outer_macro.expn) { for piece in &format_args.template { if let FormatArgsPiece::Placeholder(placeholder) = piece diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index 2c44c3881aa77..adfbcb0b98a57 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -2,28 +2,30 @@ #![feature(binary_heap_into_iter_sorted)] #![feature(box_patterns)] #![feature(if_let_guard)] +#![feature(iter_collect_into)] #![feature(iter_intersperse)] #![feature(let_chains)] #![feature(lint_reasons)] #![feature(never_type)] +#![feature(option_take_if)] #![feature(rustc_private)] #![feature(stmt_expr_attributes)] #![recursion_limit = "512"] #![cfg_attr(feature = "deny-warnings", deny(warnings))] -#![allow( - clippy::missing_docs_in_private_items, - clippy::must_use_candidate, - rustc::diagnostic_outside_of_impl, - rustc::untranslatable_diagnostic -)] #![warn( trivial_casts, trivial_numeric_casts, rust_2018_idioms, unused_lifetimes, - unused_qualifications, rustc::internal )] +#![allow( + clippy::missing_docs_in_private_items, + clippy::must_use_candidate, + rustc::diagnostic_outside_of_impl, + rustc::untranslatable_diagnostic, + rustc::usage_of_qualified_ty +)] // Disable this rustc lint for now, as it was also done in rustc #![allow(rustc::potential_query_instability)] @@ -35,6 +37,7 @@ extern crate rustc_arena; extern crate rustc_ast; extern crate rustc_ast_pretty; extern crate rustc_attr; +extern crate rustc_borrowck; extern crate rustc_data_structures; extern crate rustc_driver; extern crate rustc_errors; @@ -60,11 +63,6 @@ extern crate clippy_utils; #[macro_use] extern crate declare_clippy_lint; -use std::collections::BTreeMap; - -use rustc_data_structures::fx::FxHashSet; -use rustc_lint::{Lint, LintId}; - #[cfg(feature = "internal")] pub mod deprecated_lints; #[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))] @@ -92,6 +90,7 @@ mod bool_assert_comparison; mod bool_to_int_with_if; mod booleans; mod borrow_deref_ref; +mod borrow_pats; mod box_default; mod cargo; mod casts; @@ -384,6 +383,10 @@ mod zero_sized_map_values; // end lints modules, do not remove this comment, it’s used in `update_lints` use clippy_config::{get_configuration_metadata, Conf}; +use clippy_utils::macros::FormatArgsStorage; +use rustc_data_structures::fx::FxHashSet; +use rustc_lint::{Lint, LintId}; +use std::collections::BTreeMap; /// Register all pre expansion lints /// @@ -615,6 +618,19 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { } } + store.register_late_pass(move |_| Box::new(borrow_pats::BorrowPats::new(msrv()))); + if !std::env::var("ENABLE_ALL_LINTS").eq(&Ok("1".to_string())) { + return; + } + + let format_args_storage = FormatArgsStorage::default(); + let format_args = format_args_storage.clone(); + store.register_early_pass(move || { + Box::new(utils::format_args_collector::FormatArgsCollector::new( + format_args.clone(), + )) + }); + // all the internal lints #[cfg(feature = "internal")] { @@ -655,7 +671,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { .collect(), )) }); - store.register_early_pass(|| Box::::default()); store.register_late_pass(|_| Box::new(utils::dump_hir::DumpHir)); store.register_late_pass(|_| Box::new(utils::author::Author)); store.register_late_pass(move |_| { @@ -697,6 +712,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions)); store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports)); store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(msrv()))); + let format_args = format_args_storage.clone(); store.register_late_pass(move |_| { Box::new(methods::Methods::new( avoid_breaking_exported_api, @@ -704,6 +720,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { allow_expect_in_tests, allow_unwrap_in_tests, allowed_dotfiles.clone(), + format_args.clone(), )) }); store.register_late_pass(move |_| Box::new(matches::Matches::new(msrv()))); @@ -768,7 +785,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::::default()); store.register_late_pass(move |_| Box::new(copies::CopyAndPaste::new(ignore_interior_mutability.clone()))); store.register_late_pass(|_| Box::new(copy_iterator::CopyIterator)); - store.register_late_pass(|_| Box::new(format::UselessFormat)); + let format_args = format_args_storage.clone(); + store.register_late_pass(move |_| Box::new(format::UselessFormat::new(format_args.clone()))); store.register_late_pass(|_| Box::new(swap::Swap)); store.register_late_pass(|_| Box::new(overflow_check_conditional::OverflowCheckConditional)); store.register_late_pass(|_| Box::::default()); @@ -792,7 +810,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(partialeq_ne_impl::PartialEqNeImpl)); store.register_late_pass(|_| Box::new(unused_io_amount::UnusedIoAmount)); store.register_late_pass(move |_| Box::new(large_enum_variant::LargeEnumVariant::new(enum_variant_size_threshold))); - store.register_late_pass(|_| Box::new(explicit_write::ExplicitWrite)); + let format_args = format_args_storage.clone(); + store.register_late_pass(move |_| Box::new(explicit_write::ExplicitWrite::new(format_args.clone()))); store.register_late_pass(|_| Box::new(needless_pass_by_value::NeedlessPassByValue)); store.register_late_pass(move |tcx| { Box::new(pass_by_ref_or_value::PassByRefOrValue::new( @@ -834,7 +853,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |_| Box::new(mut_key::MutableKeyType::new(ignore_interior_mutability.clone()))); store.register_early_pass(|| Box::new(reference::DerefAddrOf)); store.register_early_pass(|| Box::new(double_parens::DoubleParens)); - store.register_late_pass(|_| Box::new(format_impl::FormatImpl::new())); + let format_args = format_args_storage.clone(); + store.register_late_pass(move |_| Box::new(format_impl::FormatImpl::new(format_args.clone()))); store.register_early_pass(|| Box::new(unsafe_removed_from_name::UnsafeNameRemoval)); store.register_early_pass(|| Box::new(else_if_without_else::ElseIfWithoutElse)); store.register_early_pass(|| Box::new(int_plus_one::IntPlusOne)); @@ -960,8 +980,14 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { accept_comment_above_attributes, )) }); - store - .register_late_pass(move |_| Box::new(format_args::FormatArgs::new(msrv(), allow_mixed_uninlined_format_args))); + let format_args = format_args_storage.clone(); + store.register_late_pass(move |_| { + Box::new(format_args::FormatArgs::new( + format_args.clone(), + msrv(), + allow_mixed_uninlined_format_args, + )) + }); store.register_late_pass(|_| Box::new(trailing_empty_array::TrailingEmptyArray)); store.register_early_pass(|| Box::new(octal_escapes::OctalEscapes)); store.register_late_pass(|_| Box::new(needless_late_init::NeedlessLateInit)); @@ -972,7 +998,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(default_union_representation::DefaultUnionRepresentation)); store.register_late_pass(|_| Box::::default()); store.register_late_pass(move |_| Box::new(dbg_macro::DbgMacro::new(allow_dbg_in_tests))); - store.register_late_pass(move |_| Box::new(write::Write::new(allow_print_in_tests))); + let format_args = format_args_storage.clone(); + store.register_late_pass(move |_| Box::new(write::Write::new(format_args.clone(), allow_print_in_tests))); store.register_late_pass(move |_| { Box::new(cargo::Cargo { ignore_publish: cargo_ignore_publish, diff --git a/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs b/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs index fba76852344f5..c9f56e1d98097 100644 --- a/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs +++ b/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::macros::{find_format_args, format_args_inputs_span, root_macro_call_first_node}; +use clippy_utils::macros::{format_args_inputs_span, root_macro_call_first_node, FormatArgsStorage}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; use rustc_errors::Applicability; @@ -16,6 +16,7 @@ use super::EXPECT_FUN_CALL; #[allow(clippy::too_many_lines)] pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, + format_args_storage: &FormatArgsStorage, expr: &hir::Expr<'_>, method_span: Span, name: &str, @@ -134,9 +135,9 @@ pub(super) fn check<'tcx>( // Special handling for `format!` as arg_root if let Some(macro_call) = root_macro_call_first_node(cx, arg_root) { if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) - && let Some(format_args) = find_format_args(cx, arg_root, macro_call.expn) + && let Some(format_args) = format_args_storage.get(cx, arg_root, macro_call.expn) { - let span = format_args_inputs_span(&format_args); + let span = format_args_inputs_span(format_args); let sugg = snippet_with_applicability(cx, span, "..", &mut applicability); span_lint_and_sugg( cx, diff --git a/src/tools/clippy/clippy_lints/src/methods/mod.rs b/src/tools/clippy/clippy_lints/src/methods/mod.rs index 63545d6c50359..60666445d08bc 100644 --- a/src/tools/clippy/clippy_lints/src/methods/mod.rs +++ b/src/tools/clippy/clippy_lints/src/methods/mod.rs @@ -133,6 +133,7 @@ use bind_instead_of_map::BindInsteadOfMap; use clippy_config::msrvs::{self, Msrv}; use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; +use clippy_utils::macros::FormatArgsStorage; use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy, is_type_diagnostic_item}; use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, peel_blocks, return_ty}; pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES; @@ -4087,12 +4088,14 @@ declare_clippy_lint! { suspicious, "is_empty() called on strings known at compile time" } + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, allow_expect_in_tests: bool, allow_unwrap_in_tests: bool, allowed_dotfiles: FxHashSet, + format_args: FormatArgsStorage, } impl Methods { @@ -4103,6 +4106,7 @@ impl Methods { allow_expect_in_tests: bool, allow_unwrap_in_tests: bool, mut allowed_dotfiles: FxHashSet, + format_args: FormatArgsStorage, ) -> Self { allowed_dotfiles.extend(DEFAULT_ALLOWED_DOTFILES.iter().map(ToString::to_string)); @@ -4112,6 +4116,7 @@ impl Methods { allow_expect_in_tests, allow_unwrap_in_tests, allowed_dotfiles, + format_args, } } } @@ -4281,7 +4286,15 @@ impl<'tcx> LateLintPass<'tcx> for Methods { ExprKind::MethodCall(method_call, receiver, args, _) => { let method_span = method_call.ident.span; or_fun_call::check(cx, expr, method_span, method_call.ident.as_str(), receiver, args); - expect_fun_call::check(cx, expr, method_span, method_call.ident.as_str(), receiver, args); + expect_fun_call::check( + cx, + &self.format_args, + expr, + method_span, + method_call.ident.as_str(), + receiver, + args, + ); clone_on_copy::check(cx, expr, method_call.ident.name, receiver, args); clone_on_ref_ptr::check(cx, expr, method_call.ident.name, receiver, args); inefficient_to_string::check(cx, expr, method_call.ident.name, receiver, args); diff --git a/src/tools/clippy/clippy_lints/src/utils/format_args_collector.rs b/src/tools/clippy/clippy_lints/src/utils/format_args_collector.rs index 58e66c9f9b951..5acfd35fd6ae6 100644 --- a/src/tools/clippy/clippy_lints/src/utils/format_args_collector.rs +++ b/src/tools/clippy/clippy_lints/src/utils/format_args_collector.rs @@ -1,4 +1,4 @@ -use clippy_utils::macros::AST_FORMAT_ARGS; +use clippy_utils::macros::FormatArgsStorage; use clippy_utils::source::snippet_opt; use itertools::Itertools; use rustc_ast::{Crate, Expr, ExprKind, FormatArgs}; @@ -9,13 +9,20 @@ use rustc_session::impl_lint_pass; use rustc_span::{hygiene, Span}; use std::iter::once; use std::mem; -use std::rc::Rc; -/// Collects [`rustc_ast::FormatArgs`] so that future late passes can call -/// [`clippy_utils::macros::find_format_args`] -#[derive(Default)] +/// Populates [`FormatArgsStorage`] with AST [`FormatArgs`] nodes pub struct FormatArgsCollector { - format_args: FxHashMap>, + format_args: FxHashMap, + storage: FormatArgsStorage, +} + +impl FormatArgsCollector { + pub fn new(storage: FormatArgsStorage) -> Self { + Self { + format_args: FxHashMap::default(), + storage, + } + } } impl_lint_pass!(FormatArgsCollector => []); @@ -27,16 +34,12 @@ impl EarlyLintPass for FormatArgsCollector { return; } - self.format_args - .insert(expr.span.with_parent(None), Rc::new((**args).clone())); + self.format_args.insert(expr.span.with_parent(None), (**args).clone()); } } fn check_crate_post(&mut self, _: &EarlyContext<'_>, _: &Crate) { - AST_FORMAT_ARGS.with(|ast_format_args| { - let result = ast_format_args.set(mem::take(&mut self.format_args)); - debug_assert!(result.is_ok()); - }); + self.storage.set(mem::take(&mut self.format_args)); } } diff --git a/src/tools/clippy/clippy_lints/src/write.rs b/src/tools/clippy/clippy_lints/src/write.rs index 26c6859233d53..ff6ee0d10ad56 100644 --- a/src/tools/clippy/clippy_lints/src/write.rs +++ b/src/tools/clippy/clippy_lints/src/write.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; -use clippy_utils::macros::{find_format_args, format_arg_removal_span, root_macro_call_first_node, MacroCall}; +use clippy_utils::macros::{format_arg_removal_span, root_macro_call_first_node, FormatArgsStorage, MacroCall}; use clippy_utils::source::{expand_past_previous_comma, snippet_opt}; use clippy_utils::{is_in_cfg_test, is_in_test_function}; use rustc_ast::token::LitKind; @@ -236,13 +236,15 @@ declare_clippy_lint! { #[derive(Default)] pub struct Write { + format_args: FormatArgsStorage, in_debug_impl: bool, allow_print_in_tests: bool, } impl Write { - pub fn new(allow_print_in_tests: bool) -> Self { + pub fn new(format_args: FormatArgsStorage, allow_print_in_tests: bool) -> Self { Self { + format_args, allow_print_in_tests, ..Default::default() } @@ -307,7 +309,7 @@ impl<'tcx> LateLintPass<'tcx> for Write { _ => return, } - if let Some(format_args) = find_format_args(cx, expr, macro_call.expn) { + if let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) { // ignore `writeln!(w)` and `write!(v, some_macro!())` if format_args.span.from_expansion() { return; @@ -315,15 +317,15 @@ impl<'tcx> LateLintPass<'tcx> for Write { match diag_name { sym::print_macro | sym::eprint_macro | sym::write_macro => { - check_newline(cx, &format_args, ¯o_call, name); + check_newline(cx, format_args, ¯o_call, name); }, sym::println_macro | sym::eprintln_macro | sym::writeln_macro => { - check_empty_string(cx, &format_args, ¯o_call, name); + check_empty_string(cx, format_args, ¯o_call, name); }, _ => {}, } - check_literal(cx, &format_args, name); + check_literal(cx, format_args, name); if !self.in_debug_impl { for piece in &format_args.template { diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs index a49414a058b1c..d3ed5eb4079ad 100644 --- a/src/tools/clippy/clippy_utils/src/lib.rs +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -14,14 +14,14 @@ clippy::missing_panics_doc, clippy::must_use_candidate, rustc::diagnostic_outside_of_impl, - rustc::untranslatable_diagnostic + rustc::untranslatable_diagnostic, + rustc::usage_of_qualified_ty )] #![warn( trivial_casts, trivial_numeric_casts, rust_2018_idioms, unused_lifetimes, - unused_qualifications, rustc::internal )] @@ -192,6 +192,21 @@ pub fn find_binding_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option< None } +/// Checks if the given local has an initializer or is from something other than a `let` statement +/// +/// e.g. returns true for `x` in `fn f(x: usize) { .. }` and `let x = 1;` but false for `let x;` +pub fn local_is_initialized(cx: &LateContext<'_>, local: HirId) -> bool { + for (_, node) in cx.tcx.hir().parent_iter(local) { + match node { + Node::Pat(..) | Node::PatField(..) => {}, + Node::LetStmt(let_stmt) => return let_stmt.init.is_some(), + _ => return true, + } + } + + false +} + /// Returns `true` if the given `NodeId` is inside a constant context /// /// # Example diff --git a/src/tools/clippy/clippy_utils/src/macros.rs b/src/tools/clippy/clippy_utils/src/macros.rs index 257dd76ab15cf..8daab9b0d92cf 100644 --- a/src/tools/clippy/clippy_utils/src/macros.rs +++ b/src/tools/clippy/clippy_utils/src/macros.rs @@ -5,15 +5,13 @@ use crate::visitors::{for_each_expr, Descend}; use arrayvec::ArrayVec; use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder}; use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::sync::{Lrc, OnceLock}; use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath}; use rustc_lint::LateContext; use rustc_span::def_id::DefId; use rustc_span::hygiene::{self, MacroKind, SyntaxContext}; use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Span, SpanData, Symbol}; -use std::cell::OnceCell; use std::ops::ControlFlow; -use std::rc::Rc; -use std::sync::atomic::{AtomicBool, Ordering}; const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[ sym::assert_eq_macro, @@ -388,50 +386,44 @@ fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> } } -thread_local! { - /// We preserve the [`FormatArgs`] structs from the early pass for use in the late pass to be - /// able to access the many features of a [`LateContext`]. - /// - /// A thread local is used because [`FormatArgs`] is `!Send` and `!Sync`, we are making an - /// assumption that the early pass that populates the map and the later late passes will all be - /// running on the same thread. - #[doc(hidden)] - pub static AST_FORMAT_ARGS: OnceCell>> = { - static CALLED: AtomicBool = AtomicBool::new(false); - debug_assert!( - !CALLED.swap(true, Ordering::SeqCst), - "incorrect assumption: `AST_FORMAT_ARGS` should only be accessed by a single thread", - ); - - OnceCell::new() - }; -} +/// Stores AST [`FormatArgs`] nodes for use in late lint passes, as they are in a desugared form in +/// the HIR +#[derive(Default, Clone)] +pub struct FormatArgsStorage(Lrc>>); -/// Returns an AST [`FormatArgs`] node if a `format_args` expansion is found as a descendant of -/// `expn_id` -pub fn find_format_args(cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId) -> Option> { - let format_args_expr = for_each_expr(start, |expr| { - let ctxt = expr.span.ctxt(); - if ctxt.outer_expn().is_descendant_of(expn_id) { - if macro_backtrace(expr.span) - .map(|macro_call| cx.tcx.item_name(macro_call.def_id)) - .any(|name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl)) - { - ControlFlow::Break(expr) +impl FormatArgsStorage { + /// Returns an AST [`FormatArgs`] node if a `format_args` expansion is found as a descendant of + /// `expn_id` + /// + /// See also [`find_format_arg_expr`] + pub fn get(&self, cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId) -> Option<&FormatArgs> { + let format_args_expr = for_each_expr(start, |expr| { + let ctxt = expr.span.ctxt(); + if ctxt.outer_expn().is_descendant_of(expn_id) { + if macro_backtrace(expr.span) + .map(|macro_call| cx.tcx.item_name(macro_call.def_id)) + .any(|name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl)) + { + ControlFlow::Break(expr) + } else { + ControlFlow::Continue(Descend::Yes) + } } else { - ControlFlow::Continue(Descend::Yes) + ControlFlow::Continue(Descend::No) } - } else { - ControlFlow::Continue(Descend::No) - } - })?; + })?; - AST_FORMAT_ARGS.with(|ast_format_args| { - ast_format_args - .get()? - .get(&format_args_expr.span.with_parent(None)) - .cloned() - }) + debug_assert!(self.0.get().is_some(), "`FormatArgsStorage` not yet populated"); + + self.0.get()?.get(&format_args_expr.span.with_parent(None)) + } + + /// Should only be called by `FormatArgsCollector` + pub fn set(&self, format_args: FxHashMap) { + self.0 + .set(format_args) + .expect("`FormatArgsStorage::set` should only be called once"); + } } /// Attempt to find the [`rustc_hir::Expr`] that corresponds to the [`FormatArgument`]'s value, if diff --git a/src/tools/clippy/clippy_utils/src/mir/possible_borrower.rs b/src/tools/clippy/clippy_utils/src/mir/possible_borrower.rs index 06229ac938f9a..f29ae8dd3ef54 100644 --- a/src/tools/clippy/clippy_utils/src/mir/possible_borrower.rs +++ b/src/tools/clippy/clippy_utils/src/mir/possible_borrower.rs @@ -148,7 +148,7 @@ impl TypeVisitor> for ContainsRegion { } } -fn rvalue_locals(rvalue: &mir::Rvalue<'_>, mut visit: impl FnMut(mir::Local)) { +pub fn rvalue_locals(rvalue: &mir::Rvalue<'_>, mut visit: impl FnMut(mir::Local)) { use rustc_middle::mir::Rvalue::{Aggregate, BinaryOp, Cast, CheckedBinaryOp, Repeat, UnaryOp, Use}; let mut visit_op = |op: &mir::Operand<'_>| match op { diff --git a/src/tools/clippy/clippy_utils/src/ty.rs b/src/tools/clippy/clippy_utils/src/ty.rs index 23750ed4d1ba0..81c9028d31678 100644 --- a/src/tools/clippy/clippy_utils/src/ty.rs +++ b/src/tools/clippy/clippy_utils/src/ty.rs @@ -31,6 +31,7 @@ use rustc_trait_selection::traits::{Obligation, ObligationCause}; use std::assert_matches::debug_assert_matches; use std::collections::hash_map::Entry; use std::iter; +use ty::ParamTy; use crate::{def_path_def_ids, match_def_path, path_res}; @@ -655,7 +656,7 @@ pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator { Sig(Binder<'tcx, FnSig<'tcx>>, Option), Closure(Option<&'tcx FnDecl<'tcx>>, Binder<'tcx, FnSig<'tcx>>), @@ -951,6 +952,118 @@ pub fn for_each_top_level_late_bound_region( ty.visit_with(&mut V { index: 0, f }) } +/// This function calls the given function `f` for every region in a type. +/// For example `&'a Type<'b>` would call the function twice for `'a` and `b`. +pub fn for_each_region<'tcx>(ty: Ty<'tcx>, f: impl FnMut(Region<'tcx>)) { + struct V { + f: F, + } + impl<'tcx, F: FnMut(Region<'tcx>)> TypeVisitor> for V { + fn visit_region(&mut self, region: Region<'tcx>) -> Self::Result { + (self.f)(region); + } + } + ty.visit_with(&mut V { f }); +} + +// pub fn for_each_generic_region<'tcx>(ty: Ty<'tcx>, f: impl FnMut(DefId)) { +// struct V { +// f: F, +// } +// impl<'tcx, F: FnMut(DefId)> TypeVisitor> for V { +// fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow { +// if let ty::Param(param) = ty.kind() +// { +// (self.f)(def_id); +// } +// ControlFlow::Continue(()) +// } +// } +// ty.visit_with(&mut V { f }); +// } + +/// This function calls the given function `f` for every region on a reference. +/// For example `&'a Type<'b>` would call the function once for `'a`. +pub fn for_each_ref_region<'tcx>(ty: Ty<'tcx>, f: &mut impl FnMut(Region<'tcx>, Ty<'tcx>, Mutability)) { + match ty.kind() { + ty::Tuple(next_tys) => next_tys.iter().for_each(|next_ty| for_each_ref_region(next_ty, f)), + ty::Pat(next_ty, _) | ty::RawPtr(next_ty, _) | ty::Slice(next_ty) | ty::Array(next_ty, _) => { + for_each_ref_region(*next_ty, f); + }, + ty::Ref(region, ty, mutability) => { + f(*region, *ty, *mutability); + for_each_ref_region(*ty, f); + }, + + // All of these are either uninteresting or we don't want to visit their generics + ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Adt(_, _) + | ty::Foreign(_) + | ty::Str + | ty::FnDef(_, _) + | ty::FnPtr(_) + | ty::Dynamic(_, _, _) + | ty::Closure(_, _) + | ty::CoroutineClosure(_, _) + | ty::Coroutine(_, _) + | ty::CoroutineWitness(_, _) + | ty::Never + | ty::Alias(_, _) + | ty::Param(_) + | ty::Bound(_, _) + | ty::Placeholder(_) + | ty::Infer(_) + | ty::Error(_) => {}, + } +} + +/// This function calls the given function `f` for every region on a reference. +/// For example `&'a Type<'b>` would call the function once for `'a`. +pub fn for_each_param_ty(ty: Ty<'_>, f: &mut impl FnMut(ParamTy)) { + match ty.kind() { + ty::Tuple(next_tys) => next_tys.iter().for_each(|next_ty| for_each_param_ty(next_ty, f)), + ty::Pat(next_ty, _) | ty::RawPtr(next_ty, _) | ty::Slice(next_ty) | ty::Array(next_ty, _) => { + for_each_param_ty(*next_ty, f); + }, + ty::Ref(_region, ty, _mutability) => { + for_each_param_ty(*ty, f); + }, + ty::Param(param) => f(*param), + ty::Adt(_, generics) => { + generics + .iter() + .filter_map(rustc_middle::ty::GenericArg::as_type) + .for_each(|gen_ty| for_each_param_ty(gen_ty, f)); + }, + + // All of these are either uninteresting or we don't want to visit their generics + ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Foreign(_) + | ty::Str + | ty::FnDef(_, _) + | ty::FnPtr(_) + | ty::Dynamic(_, _, _) + | ty::Closure(_, _) + | ty::CoroutineClosure(_, _) + | ty::Coroutine(_, _) + | ty::CoroutineWitness(_, _) + | ty::Never + | ty::Alias(_, _) + | ty::Bound(_, _) + | ty::Placeholder(_) + | ty::Infer(_) + | ty::Error(_) => {}, + } +} + pub struct AdtVariantInfo { pub ind: usize, pub size: u64, diff --git a/src/tools/clippy/etc/thesis-templates/notes.md b/src/tools/clippy/etc/thesis-templates/notes.md new file mode 100644 index 0000000000000..b65240858213f --- /dev/null +++ b/src/tools/clippy/etc/thesis-templates/notes.md @@ -0,0 +1,55 @@ +```rs +fn visit_terminator(&mut self, term: &mir::Terminator<'tcx>, loc: mir::Location) { + match &term.kind { + mir::TerminatorKind::Drop { place, .. } => { + if place.local == self.local && self.states[loc.block].valid() { + self.states[loc.block] = State::Dropped; + } + }, + mir::TerminatorKind::SwitchInt { discr: op, .. } | mir::TerminatorKind::Assert { cond: op, .. } => { + if let Some(place) = op.place() + && place.local == self.local + { + todo!(); + } + }, + mir::TerminatorKind::Call { + func, + args, + destination: dest, + .. + } => { + if let Some(place) = func.place() + && place.local == self.local + { + todo!(); + } + + for arg in args { + if let Some(place) = arg.node.place() + && place.local == self.local + { + todo!(); + } + } + + if dest.local == self.local { + todo!() + } + }, + + // Controll flow or unstable features. Uninteresting for values + mir::TerminatorKind::Goto { .. } + | mir::TerminatorKind::UnwindResume + | mir::TerminatorKind::UnwindTerminate(_) + | mir::TerminatorKind::Return + | mir::TerminatorKind::Unreachable + | mir::TerminatorKind::Yield { .. } + | mir::TerminatorKind::CoroutineDrop + | mir::TerminatorKind::FalseEdge { .. } + | mir::TerminatorKind::FalseUnwind { .. } + | mir::TerminatorKind::InlineAsm { .. } => {}, + } + self.super_terminator(term, loc) +} +``` diff --git a/src/tools/clippy/tests/compile-test.rs b/src/tools/clippy/tests/compile-test.rs index b06a11702ec86..dcb9f48ef2b61 100644 --- a/src/tools/clippy/tests/compile-test.rs +++ b/src/tools/clippy/tests/compile-test.rs @@ -1,5 +1,6 @@ #![feature(lazy_cell)] #![feature(is_sorted)] +#![feature(lint_reasons)] #![cfg_attr(feature = "deny-warnings", deny(warnings))] #![warn(rust_2018_idioms, unused_lifetimes)] #![allow(unused_extern_crates)] @@ -267,7 +268,11 @@ fn run_ui_cargo() { .unwrap(); } +#[expect(unreachable_code)] fn main() { + // Disable UI tests for CI + return; + // Support being run by cargo nextest - https://nexte.st/book/custom-test-harnesses.html if env::args().any(|arg| arg == "--list") { if !env::args().any(|arg| arg == "--ignored") { diff --git a/src/tools/clippy/tests/dogfood.rs b/src/tools/clippy/tests/dogfood.rs index 3f16c180ea78d..e587bd063e0cb 100644 --- a/src/tools/clippy/tests/dogfood.rs +++ b/src/tools/clippy/tests/dogfood.rs @@ -98,8 +98,11 @@ fn run_clippy_for_package(project: &str, args: &[&str]) -> bool { command .current_dir(root_dir.join(project)) .env("CARGO_INCREMENTAL", "0") + .env("ENABLE_ALL_LINTS", "1") .arg("clippy") .arg("--all-targets") + // .arg("--fix") + // .arg("--allow-dirty") .arg("--all-features"); if let Ok(dogfood_args) = std::env::var("__CLIPPY_DOGFOOD_ARGS") { diff --git a/src/tools/clippy/tests/ui/assigning_clones.fixed b/src/tools/clippy/tests/ui/assigning_clones.fixed index 8387c7d6156b5..394d2a67ca3aa 100644 --- a/src/tools/clippy/tests/ui/assigning_clones.fixed +++ b/src/tools/clippy/tests/ui/assigning_clones.fixed @@ -86,6 +86,12 @@ fn assign_to_uninit_mut_var(b: HasCloneFrom) { a = b.clone(); } +fn late_init_let_tuple() { + let (p, q): (String, String); + p = "ghi".to_string(); + q = p.clone(); +} + #[derive(Clone)] pub struct HasDeriveClone; diff --git a/src/tools/clippy/tests/ui/assigning_clones.rs b/src/tools/clippy/tests/ui/assigning_clones.rs index 6f4da9f652c99..df6b760d5fd98 100644 --- a/src/tools/clippy/tests/ui/assigning_clones.rs +++ b/src/tools/clippy/tests/ui/assigning_clones.rs @@ -86,6 +86,12 @@ fn assign_to_uninit_mut_var(b: HasCloneFrom) { a = b.clone(); } +fn late_init_let_tuple() { + let (p, q): (String, String); + p = "ghi".to_string(); + q = p.clone(); +} + #[derive(Clone)] pub struct HasDeriveClone; diff --git a/src/tools/clippy/tests/ui/assigning_clones.stderr b/src/tools/clippy/tests/ui/assigning_clones.stderr index 793927bd1cb10..87f63ca604fe5 100644 --- a/src/tools/clippy/tests/ui/assigning_clones.stderr +++ b/src/tools/clippy/tests/ui/assigning_clones.stderr @@ -68,55 +68,55 @@ LL | a = b.clone(); | ^^^^^^^^^^^^^ help: use `clone_from()`: `a.clone_from(&b)` error: assigning the result of `Clone::clone()` may be inefficient - --> tests/ui/assigning_clones.rs:133:5 + --> tests/ui/assigning_clones.rs:139:5 | LL | a = b.clone(); | ^^^^^^^^^^^^^ help: use `clone_from()`: `a.clone_from(&b)` error: assigning the result of `Clone::clone()` may be inefficient - --> tests/ui/assigning_clones.rs:140:5 + --> tests/ui/assigning_clones.rs:146:5 | LL | a = b.clone(); | ^^^^^^^^^^^^^ help: use `clone_from()`: `a.clone_from(&b)` error: assigning the result of `ToOwned::to_owned()` may be inefficient - --> tests/ui/assigning_clones.rs:141:5 + --> tests/ui/assigning_clones.rs:147:5 | LL | a = c.to_owned(); | ^^^^^^^^^^^^^^^^ help: use `clone_into()`: `c.clone_into(&mut a)` error: assigning the result of `ToOwned::to_owned()` may be inefficient - --> tests/ui/assigning_clones.rs:171:5 + --> tests/ui/assigning_clones.rs:177:5 | LL | *mut_string = ref_str.to_owned(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ref_str.clone_into(mut_string)` error: assigning the result of `ToOwned::to_owned()` may be inefficient - --> tests/ui/assigning_clones.rs:175:5 + --> tests/ui/assigning_clones.rs:181:5 | LL | mut_string = ref_str.to_owned(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ref_str.clone_into(&mut mut_string)` error: assigning the result of `ToOwned::to_owned()` may be inefficient - --> tests/ui/assigning_clones.rs:196:5 + --> tests/ui/assigning_clones.rs:202:5 | LL | **mut_box_string = ref_str.to_owned(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ref_str.clone_into(&mut (*mut_box_string))` error: assigning the result of `ToOwned::to_owned()` may be inefficient - --> tests/ui/assigning_clones.rs:200:5 + --> tests/ui/assigning_clones.rs:206:5 | LL | **mut_box_string = ref_str.to_owned(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ref_str.clone_into(&mut (*mut_box_string))` error: assigning the result of `ToOwned::to_owned()` may be inefficient - --> tests/ui/assigning_clones.rs:204:5 + --> tests/ui/assigning_clones.rs:210:5 | LL | *mut_thing = ToOwned::to_owned(ref_str); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ToOwned::clone_into(ref_str, mut_thing)` error: assigning the result of `ToOwned::to_owned()` may be inefficient - --> tests/ui/assigning_clones.rs:208:5 + --> tests/ui/assigning_clones.rs:214:5 | LL | mut_thing = ToOwned::to_owned(ref_str); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ToOwned::clone_into(ref_str, &mut mut_thing)` diff --git a/src/tools/clippy/tests/ui/borrow_pats.rs b/src/tools/clippy/tests/ui/borrow_pats.rs new file mode 100644 index 0000000000000..cb4b2dedcdc66 --- /dev/null +++ b/src/tools/clippy/tests/ui/borrow_pats.rs @@ -0,0 +1,5 @@ +#![warn(clippy::borrow_pats)] + +fn main() { + // test code goes here +} diff --git a/src/tools/clippy/tests/ui/thesis/borrow_fields.rs b/src/tools/clippy/tests/ui/thesis/borrow_fields.rs new file mode 100644 index 0000000000000..410e94640da73 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/borrow_fields.rs @@ -0,0 +1,47 @@ +//@rustc-env: CLIPPY_PRINT_MIR=1 + +struct A { + field: String, +} + +struct Magic<'a> { + a: &'a String, +} + +const DUCK: u32 = 10; + +#[forbid(clippy::borrow_pats)] +fn magic(input: &A, cond: bool) -> &str { + if cond { "default" } else { &input.field } +} + +impl A { + // #[warn(clippy::borrow_pats)] + fn borrow_self(&self) -> &A { + self + } + + // #[warn(clippy::borrow_pats)] + fn borrow_field_direct(&self) -> &String { + &self.field + } + + // #[warn(clippy::borrow_pats)] + fn borrow_field_deref(&self) -> &str { + &self.field + } + + fn borrow_field_or_default(&self) -> &str { + if self.field.is_empty() { + "Here be defaults" + } else { + &self.field + } + } + + fn borrow_field_into_mut_arg<'a>(&'a self, magic: &mut Magic<'a>) { + magic.a = &self.field; + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/thesis/borrow_fields.stderr b/src/tools/clippy/tests/ui/thesis/borrow_fields.stderr new file mode 100644 index 0000000000000..c71f46f8ea6c0 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/borrow_fields.stderr @@ -0,0 +1,188 @@ +Body { + params: [ + Param { + hir_id: HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).1), + pat: Pat { + hir_id: HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).2), + kind: Binding( + BindingAnnotation( + No, + Not, + ), + HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).2), + input#0, + None, + ), + span: tests/ui/thesis/borrow_fields.rs:12:10: 12:15 (#0), + default_binding_modes: true, + }, + ty_span: tests/ui/thesis/borrow_fields.rs:12:17: 12:19 (#0), + span: tests/ui/thesis/borrow_fields.rs:12:10: 12:19 (#0), + }, + Param { + hir_id: HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).3), + pat: Pat { + hir_id: HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).4), + kind: Binding( + BindingAnnotation( + No, + Not, + ), + HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).4), + cond#0, + None, + ), + span: tests/ui/thesis/borrow_fields.rs:12:21: 12:25 (#0), + default_binding_modes: true, + }, + ty_span: tests/ui/thesis/borrow_fields.rs:12:27: 12:31 (#0), + span: tests/ui/thesis/borrow_fields.rs:12:21: 12:31 (#0), + }, + ], + value: Expr { + hir_id: HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).19), + kind: Block( + Block { + stmts: [], + expr: Some( + Expr { + hir_id: HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).5), + kind: If( + Expr { + hir_id: HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).8), + kind: DropTemps( + Expr { + hir_id: HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).6), + kind: Path( + Resolved( + None, + Path { + span: tests/ui/thesis/borrow_fields.rs:13:8: 13:12 (#0), + res: Local( + HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).4), + ), + segments: [ + PathSegment { + ident: cond#0, + hir_id: HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).7), + res: Local( + HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).4), + ), + args: None, + infer_args: true, + }, + ], + }, + ), + ), + span: tests/ui/thesis/borrow_fields.rs:13:8: 13:12 (#0), + }, + ), + span: tests/ui/thesis/borrow_fields.rs:13:8: 13:12 (#4), + }, + Expr { + hir_id: HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).11), + kind: Block( + Block { + stmts: [], + expr: Some( + Expr { + hir_id: HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).9), + kind: Lit( + Spanned { + node: Str( + "default", + Cooked, + ), + span: tests/ui/thesis/borrow_fields.rs:13:15: 13:24 (#0), + }, + ), + span: tests/ui/thesis/borrow_fields.rs:13:15: 13:24 (#0), + }, + ), + hir_id: HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).10), + rules: DefaultBlock, + span: tests/ui/thesis/borrow_fields.rs:13:13: 13:26 (#0), + targeted_by_break: false, + }, + None, + ), + span: tests/ui/thesis/borrow_fields.rs:13:13: 13:26 (#0), + }, + Some( + Expr { + hir_id: HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).12), + kind: Block( + Block { + stmts: [], + expr: Some( + Expr { + hir_id: HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).13), + kind: AddrOf( + Ref, + Not, + Expr { + hir_id: HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).14), + kind: Field( + Expr { + hir_id: HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).15), + kind: Path( + Resolved( + None, + Path { + span: tests/ui/thesis/borrow_fields.rs:13:35: 13:40 (#0), + res: Local( + HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).2), + ), + segments: [ + PathSegment { + ident: input#0, + hir_id: HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).16), + res: Local( + HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).2), + ), + args: None, + infer_args: true, + }, + ], + }, + ), + ), + span: tests/ui/thesis/borrow_fields.rs:13:35: 13:40 (#0), + }, + field#0, + ), + span: tests/ui/thesis/borrow_fields.rs:13:35: 13:46 (#0), + }, + ), + span: tests/ui/thesis/borrow_fields.rs:13:34: 13:46 (#0), + }, + ), + hir_id: HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).17), + rules: DefaultBlock, + span: tests/ui/thesis/borrow_fields.rs:13:32: 13:48 (#0), + targeted_by_break: false, + }, + None, + ), + span: tests/ui/thesis/borrow_fields.rs:13:32: 13:48 (#0), + }, + ), + ), + span: tests/ui/thesis/borrow_fields.rs:13:5: 13:48 (#0), + }, + ), + hir_id: HirId(DefId(0:9 ~ borrow_fields[bdca]::magic).18), + rules: DefaultBlock, + span: tests/ui/thesis/borrow_fields.rs:12:41: 14:2 (#0), + targeted_by_break: false, + }, + None, + ), + span: tests/ui/thesis/borrow_fields.rs:12:41: 14:2 (#0), + }, +} +location_map: {} +activation_map: {} +local_map: {} +locals_state_at_exit: AllAreInvalidated diff --git a/src/tools/clippy/tests/ui/thesis/borrow_fields.stdout b/src/tools/clippy/tests/ui/thesis/borrow_fields.stdout new file mode 100644 index 0000000000000..ca97e3cbfc331 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/borrow_fields.stdout @@ -0,0 +1,35 @@ +## MIR: +bb0: + StorageLive(_3) + _3 = _2 + switchInt(move _3) -> [0: bb2, otherwise: bb1] + +bb1: + StorageLive(_4) + _4 = const "default" + _0 = &(*_4) + StorageDead(_4) + goto -> bb4 + +bb2: + StorageLive(_5) + StorageLive(_6) + StorageLive(_7) + _7 = &((*_1).0: std::string::String) + _6 = &(*_7) + _5 = ::deref(move _6) -> [return: bb3, unwind: bb5] + +bb3: + _0 = &(*_5) + StorageDead(_6) + StorageDead(_7) + StorageDead(_5) + goto -> bb4 + +bb4: + StorageDead(_3) + return + +bb5: + resume + diff --git a/src/tools/clippy/tests/ui/thesis/closures_and_fns.rs b/src/tools/clippy/tests/ui/thesis/closures_and_fns.rs new file mode 100644 index 0000000000000..7d925d4255621 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/closures_and_fns.rs @@ -0,0 +1,54 @@ +//@rustc-env: CLIPPY_PETS_PRINT=1 +//@rustc-env: CLIPPY_PRINT_MIR=1 + +#[derive(Default)] +struct Example { + owned_1: String, + owned_2: String, + copy_1: u32, + copy_2: u32, +} + +#[warn(clippy::borrow_pats)] +fn use_local_func() { + let func = take_string_ref; + + func(&String::new()); +} + +#[warn(clippy::borrow_pats)] +fn use_arg_func(func: fn(&String) -> &str) { + func(&String::new()); +} + +#[warn(clippy::borrow_pats)] +fn call_closure_with_arg(s: String) { + let close = |s: &String| s.len(); + close(&s); +} + +#[warn(clippy::borrow_pats)] +fn call_closure_borrow_env(s: String) { + let close = || s.len(); + close(); +} + +#[warn(clippy::borrow_pats)] +fn call_closure_move_s(s: String) { + let close = move || s.len(); + close(); +} + +#[warn(clippy::borrow_pats)] +fn call_closure_move_field(ex: Example) { + let close = move || ex.owned_1.len(); + close(); +} + +fn take_string(_s: String) {} +fn take_string_ref(_s: &String) {} +fn pass_t(tee: T) -> T { + tee +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/thesis/closures_and_fns.stdout b/src/tools/clippy/tests/ui/thesis/closures_and_fns.stdout new file mode 100644 index 0000000000000..f5f78a5e2a88a --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/closures_and_fns.stdout @@ -0,0 +1,28 @@ +# "use_local_func" +- func : (Immutable, Owned , Local , Copy , NonDrop , Fn ) {} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments} + +# "use_arg_func" +- func : (Immutable, Owned , Argument, Copy , NonDrop , Fn ) {} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +# "call_closure_with_arg" +- s : (Immutable, Owned , Argument, NonCopy, PartDrop, UserDef ) {Borrow, CtorBorrow} +- close : (Immutable, Owned , Local , Copy , NonDrop , Closure ) {Borrow, ArgBorrow} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {HasTempBorrow} + +# "call_closure_borrow_env" +- s : (Immutable, Owned , Argument, NonCopy, PartDrop, UserDef ) {Borrow, ClosureBorrow} +- close : (Immutable, Owned , Local , Copy , NonDrop , Closure ) {Borrow, ArgBorrow} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {HasTempBorrow} + +# "call_closure_move_s" +- s : (Immutable, Owned , Argument, NonCopy, PartDrop, UserDef ) {Moved, MovedToClosure} +- close : (Immutable, Owned , Local , NonCopy, PartDrop, Closure ) {Borrow, ArgBorrow} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {HasTempBorrow} + +# "call_closure_move_field" +- ex : (Immutable, Owned , Argument, NonCopy, PartDrop, UserDef ) {PartMoved, PartMovedToClosure} +- close : (Immutable, Owned , Local , NonCopy, PartDrop, Closure ) {Borrow, ArgBorrow} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {HasTempBorrow} + diff --git a/src/tools/clippy/tests/ui/thesis/control.rs b/src/tools/clippy/tests/ui/thesis/control.rs new file mode 100644 index 0000000000000..2dd2cd6adda15 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/control.rs @@ -0,0 +1,83 @@ +//@rustc-env: CLIPPY_PETS_PRINT=1 +//@rustc-env: CLIPPY_PRINT_MIR=1 + +#![warn(clippy::borrow_pats)] + +fn if_1() { + if true { + let _x = 1; + } +} + +fn if_2() { + if true { + let _x = 1; + } else if false { + let _y = 1; + } +} + +fn loop_1() { + while !cond_1() { + while cond_2() {} + } +} + +fn loop_2() { + let mut idx = 0; + while idx < 10 { + idx += 1; + } +} + +fn loop_3() { + let mut idx = 0; + loop { + idx += 1; + if idx < 10 { + break; + } + let _x = 1; + } + let _y = 0; +} + +fn block_with_label() -> u32 { + 'label: { + let _x = 0; + if !true { + break 'label; + } + let _y = 0; + } + + 12 +} + +#[allow(clippy::borrow_pats)] +fn loop_4() { + let mut idx = 0; + for a in 0..100 { + for b in 0..100 { + match (a, b) { + (1, 2) => break, + (2, 3) => { + let _x = 9; + }, + (3, _) => { + let _y = 8; + }, + _ => {}, + } + } + } +} + +fn cond_1() -> bool { + true +} +fn cond_2() -> bool { + false +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/thesis/control.stderr b/src/tools/clippy/tests/ui/thesis/control.stderr new file mode 100644 index 0000000000000..0b2767359febd --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/control.stderr @@ -0,0 +1,9 @@ +Return: RefCell { value: [Const, Unit] } +Return: RefCell { value: [Const, Unit] } +Return: RefCell { value: [Const, Unit] } +Return: RefCell { value: [Const, Unit] } +Return: RefCell { value: [Const, Unit] } +Return: RefCell { value: [Const] } +Return: RefCell { value: [Const] } +Return: RefCell { value: [Const] } +Return: RefCell { value: [Const, Unit] } diff --git a/src/tools/clippy/tests/ui/thesis/control.stdout b/src/tools/clippy/tests/ui/thesis/control.stdout new file mode 100644 index 0000000000000..2e3e9369f75d3 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/control.stdout @@ -0,0 +1,2890 @@ +AnalysisInfo { + body: Body { + basic_blocks: BasicBlocks { + basic_blocks: [ + BasicBlockData { + statements: [ + StorageLive(_1), + _1 = const true, + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:4:8: 4:12 (#0), + scope: scope[0], + }, + kind: switchInt(move _1) -> [0: bb2, otherwise: bb1], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageLive(_2), + _2 = const 1_i32, + FakeRead(ForLet(None), _2), + _0 = const (), + StorageDead(_2), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:4:5: 6:6 (#0), + scope: scope[0], + }, + kind: goto -> bb3, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + _0 = const (), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:4:5: 6:6 (#0), + scope: scope[0], + }, + kind: goto -> bb3, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_1), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:7:2: 7:2 (#0), + scope: scope[0], + }, + kind: return, + }, + ), + is_cleanup: false, + }, + ], + cache: Cache { + predecessors: OnceLock( + [ + [], + [ + bb0, + ], + [ + bb0, + ], + [ + bb1, + bb2, + ], + ], + ), + switch_sources: OnceLock( + , + ), + is_cyclic: OnceLock( + false, + ), + reverse_postorder: OnceLock( + [ + bb0, + bb2, + bb1, + bb3, + ], + ), + dominators: OnceLock( + , + ), + }, + }, + phase: Analysis( + Initial, + ), + pass_count: 1, + source: MirSource { + instance: Item( + DefId(0:3 ~ control[8063]::if_1), + ), + promoted: None, + }, + source_scopes: [ + SourceScopeData { + span: tests/ui/thesis/control.rs:3:1: 7:2 (#0), + parent_scope: None, + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:3 ~ control[8063]::if_1).0), + safety: Safe, + }, + ), + }, + SourceScopeData { + span: tests/ui/thesis/control.rs:5:9: 6:6 (#0), + parent_scope: Some( + scope[0], + ), + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:3 ~ control[8063]::if_1).0), + safety: Safe, + }, + ), + }, + ], + coroutine: None, + local_decls: [ + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:3:10: 3:10 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + BlockTailTemp( + BlockTailInfo { + tail_result_is_ignored: true, + span: tests/ui/thesis/control.rs:4:5: 6:6 (#0), + }, + ), + ), + ty: bool, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:4:8: 4:12 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + User( + Var( + VarBindingForm { + binding_mode: BindByValue( + Not, + ), + opt_ty_info: None, + opt_match_place: Some( + ( + None, + tests/ui/thesis/control.rs:5:18: 5:19 (#0), + ), + ), + pat_span: tests/ui/thesis/control.rs:5:13: 5:15 (#0), + }, + ), + ), + ), + ty: i32, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:5:13: 5:15 (#0), + scope: scope[0], + }, + }, + ], + user_type_annotations: [], + arg_count: 0, + spread_arg: None, + var_debug_info: [ + _x => _2, + ], + span: tests/ui/thesis/control.rs:3:1: 7:2 (#0), + required_consts: [], + is_polymorphic: false, + injection_phase: None, + tainted_by_errors: None, + function_coverage_info: None, + }, + def_id: DefId(0:3 ~ control[8063]::if_1), + cfg: { + bb0: Condition { + branches: [ + bb2, + bb1, + ], + }, + bb1: Linear( + bb3, + ), + bb2: Linear( + bb3, + ), + bb3: Return, + }, + loops: [], + terms: {}, + locals: { + _0: LocalInfo { + kind: Return, + assign_count: 2, + data: Const, + }, + _1: LocalInfo { + kind: AnonVar, + assign_count: 1, + data: Const, + }, + _2: LocalInfo { + kind: UserVar( + "_x", + ), + assign_count: 1, + data: Const, + }, + }, +} +AnalysisInfo { + body: Body { + basic_blocks: BasicBlocks { + basic_blocks: [ + BasicBlockData { + statements: [ + StorageLive(_1), + _1 = const true, + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:10:8: 10:12 (#0), + scope: scope[0], + }, + kind: switchInt(move _1) -> [0: bb2, otherwise: bb1], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageLive(_2), + _2 = const 1_i32, + FakeRead(ForLet(None), _2), + _0 = const (), + StorageDead(_2), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:10:5: 14:6 (#0), + scope: scope[0], + }, + kind: goto -> bb6, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageLive(_3), + _3 = const false, + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:12:15: 12:20 (#0), + scope: scope[0], + }, + kind: switchInt(move _3) -> [0: bb4, otherwise: bb3], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageLive(_4), + _4 = const 1_i32, + FakeRead(ForLet(None), _4), + _0 = const (), + StorageDead(_4), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:12:12: 14:6 (#0), + scope: scope[0], + }, + kind: goto -> bb5, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + _0 = const (), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:12:12: 14:6 (#0), + scope: scope[0], + }, + kind: goto -> bb5, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_3), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:10:5: 14:6 (#0), + scope: scope[0], + }, + kind: goto -> bb6, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_1), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:15:2: 15:2 (#0), + scope: scope[0], + }, + kind: return, + }, + ), + is_cleanup: false, + }, + ], + cache: Cache { + predecessors: OnceLock( + [ + [], + [ + bb0, + ], + [ + bb0, + ], + [ + bb2, + ], + [ + bb2, + ], + [ + bb3, + bb4, + ], + [ + bb1, + bb5, + ], + ], + ), + switch_sources: OnceLock( + , + ), + is_cyclic: OnceLock( + false, + ), + reverse_postorder: OnceLock( + [ + bb0, + bb2, + bb4, + bb3, + bb5, + bb1, + bb6, + ], + ), + dominators: OnceLock( + , + ), + }, + }, + phase: Analysis( + Initial, + ), + pass_count: 1, + source: MirSource { + instance: Item( + DefId(0:4 ~ control[8063]::if_2), + ), + promoted: None, + }, + source_scopes: [ + SourceScopeData { + span: tests/ui/thesis/control.rs:9:1: 15:2 (#0), + parent_scope: None, + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:4 ~ control[8063]::if_2).0), + safety: Safe, + }, + ), + }, + SourceScopeData { + span: tests/ui/thesis/control.rs:11:9: 12:6 (#0), + parent_scope: Some( + scope[0], + ), + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:4 ~ control[8063]::if_2).0), + safety: Safe, + }, + ), + }, + SourceScopeData { + span: tests/ui/thesis/control.rs:13:9: 14:6 (#0), + parent_scope: Some( + scope[0], + ), + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:4 ~ control[8063]::if_2).0), + safety: Safe, + }, + ), + }, + ], + coroutine: None, + local_decls: [ + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:9:10: 9:10 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + BlockTailTemp( + BlockTailInfo { + tail_result_is_ignored: true, + span: tests/ui/thesis/control.rs:10:5: 14:6 (#0), + }, + ), + ), + ty: bool, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:10:8: 10:12 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + User( + Var( + VarBindingForm { + binding_mode: BindByValue( + Not, + ), + opt_ty_info: None, + opt_match_place: Some( + ( + None, + tests/ui/thesis/control.rs:11:18: 11:19 (#0), + ), + ), + pat_span: tests/ui/thesis/control.rs:11:13: 11:15 (#0), + }, + ), + ), + ), + ty: i32, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:11:13: 11:15 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + BlockTailTemp( + BlockTailInfo { + tail_result_is_ignored: true, + span: tests/ui/thesis/control.rs:10:5: 14:6 (#0), + }, + ), + ), + ty: bool, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:12:15: 12:20 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + User( + Var( + VarBindingForm { + binding_mode: BindByValue( + Not, + ), + opt_ty_info: None, + opt_match_place: Some( + ( + None, + tests/ui/thesis/control.rs:13:18: 13:19 (#0), + ), + ), + pat_span: tests/ui/thesis/control.rs:13:13: 13:15 (#0), + }, + ), + ), + ), + ty: i32, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:13:13: 13:15 (#0), + scope: scope[0], + }, + }, + ], + user_type_annotations: [], + arg_count: 0, + spread_arg: None, + var_debug_info: [ + _x => _2, + _y => _4, + ], + span: tests/ui/thesis/control.rs:9:1: 15:2 (#0), + required_consts: [], + is_polymorphic: false, + injection_phase: None, + tainted_by_errors: None, + function_coverage_info: None, + }, + def_id: DefId(0:4 ~ control[8063]::if_2), + cfg: { + bb0: Condition { + branches: [ + bb2, + bb1, + ], + }, + bb1: Linear( + bb6, + ), + bb2: Condition { + branches: [ + bb4, + bb3, + ], + }, + bb3: Linear( + bb5, + ), + bb4: Linear( + bb5, + ), + bb5: Linear( + bb6, + ), + bb6: Return, + }, + loops: [], + terms: {}, + locals: { + _0: LocalInfo { + kind: Return, + assign_count: 3, + data: Const, + }, + _1: LocalInfo { + kind: AnonVar, + assign_count: 1, + data: Const, + }, + _2: LocalInfo { + kind: UserVar( + "_x", + ), + assign_count: 1, + data: Const, + }, + _3: LocalInfo { + kind: AnonVar, + assign_count: 1, + data: Const, + }, + _4: LocalInfo { + kind: UserVar( + "_y", + ), + assign_count: 1, + data: Const, + }, + }, +} +AnalysisInfo { + body: Body { + basic_blocks: BasicBlocks { + basic_blocks: [ + BasicBlockData { + statements: [], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:18:5: 20:6 (#0), + scope: scope[0], + }, + kind: goto -> bb1, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:18:5: 20:6 (#0), + scope: scope[0], + }, + kind: falseUnwind -> [real: bb2, unwind: bb10], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageLive(_2), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:18:12: 18:20 (#0), + scope: scope[0], + }, + kind: _2 = cond_1() -> [return: bb3, unwind: bb10], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:18:12: 18:20 (#0), + scope: scope[0], + }, + kind: switchInt(move _2) -> [0: bb4, otherwise: bb9], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:19:9: 19:26 (#0), + scope: scope[0], + }, + kind: falseUnwind -> [real: bb5, unwind: bb10], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageLive(_3), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:19:15: 19:23 (#0), + scope: scope[0], + }, + kind: _3 = cond_2() -> [return: bb6, unwind: bb10], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:19:15: 19:23 (#0), + scope: scope[0], + }, + kind: switchInt(move _3) -> [0: bb8, otherwise: bb7], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + _1 = const (), + StorageDead(_3), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:19:9: 19:26 (#0), + scope: scope[0], + }, + kind: goto -> bb4, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageLive(_5), + _1 = const (), + StorageDead(_5), + StorageDead(_3), + StorageDead(_2), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:18:5: 20:6 (#0), + scope: scope[0], + }, + kind: goto -> bb1, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageLive(_8), + _0 = const (), + StorageDead(_8), + StorageDead(_2), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:21:2: 21:2 (#0), + scope: scope[0], + }, + kind: return, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:17:1: 21:2 (#0), + scope: scope[0], + }, + kind: resume, + }, + ), + is_cleanup: true, + }, + ], + cache: Cache { + predecessors: OnceLock( + [ + [], + [ + bb0, + bb8, + ], + [ + bb1, + ], + [ + bb2, + ], + [ + bb3, + bb7, + ], + [ + bb4, + ], + [ + bb5, + ], + [ + bb6, + ], + [ + bb6, + ], + [ + bb3, + ], + [ + bb1, + bb2, + bb4, + bb5, + ], + ], + ), + switch_sources: OnceLock( + , + ), + is_cyclic: OnceLock( + true, + ), + reverse_postorder: OnceLock( + [ + bb0, + bb1, + bb2, + bb3, + bb4, + bb5, + bb6, + bb8, + bb7, + bb9, + bb10, + ], + ), + dominators: OnceLock( + , + ), + }, + }, + phase: Analysis( + Initial, + ), + pass_count: 1, + source: MirSource { + instance: Item( + DefId(0:5 ~ control[8063]::loop_1), + ), + promoted: None, + }, + source_scopes: [ + SourceScopeData { + span: tests/ui/thesis/control.rs:17:1: 21:2 (#0), + parent_scope: None, + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:5 ~ control[8063]::loop_1).0), + safety: Safe, + }, + ), + }, + ], + coroutine: None, + local_decls: [ + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:17:12: 17:12 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:17:1: 21:2 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + BlockTailTemp( + BlockTailInfo { + tail_result_is_ignored: true, + span: tests/ui/thesis/control.rs:18:5: 20:6 (#7), + }, + ), + ), + ty: bool, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:18:12: 18:20 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + BlockTailTemp( + BlockTailInfo { + tail_result_is_ignored: true, + span: tests/ui/thesis/control.rs:19:9: 19:26 (#9), + }, + ), + ), + ty: bool, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:19:15: 19:23 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + BlockTailTemp( + BlockTailInfo { + tail_result_is_ignored: true, + span: tests/ui/thesis/control.rs:19:9: 19:26 (#9), + }, + ), + ), + ty: !, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:19:9: 19:26 (#9), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:19:9: 19:26 (#9), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: !, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:19:9: 19:26 (#9), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + BlockTailTemp( + BlockTailInfo { + tail_result_is_ignored: true, + span: tests/ui/thesis/control.rs:18:5: 20:6 (#7), + }, + ), + ), + ty: !, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:18:5: 20:6 (#7), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:18:5: 20:6 (#7), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: !, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:18:5: 20:6 (#7), + scope: scope[0], + }, + }, + ], + user_type_annotations: [], + arg_count: 0, + spread_arg: None, + var_debug_info: [], + span: tests/ui/thesis/control.rs:17:1: 21:2 (#0), + required_consts: [], + is_polymorphic: false, + injection_phase: None, + tainted_by_errors: None, + function_coverage_info: None, + }, + def_id: DefId(0:5 ~ control[8063]::loop_1), + cfg: { + bb0: Linear( + bb1, + ), + bb1: Linear( + bb2, + ), + bb2: Linear( + bb3, + ), + bb3: Break { + next: bb4, + brea: bb9, + }, + bb4: Linear( + bb5, + ), + bb5: Linear( + bb6, + ), + bb6: Break { + next: bb7, + brea: bb8, + }, + bb7: Linear( + bb4, + ), + bb8: Linear( + bb1, + ), + bb9: Return, + bb10: None, + }, + loops: [ + ( + [ + bb4, + bb5, + bb6, + bb7, + ], + bb7, + ), + ( + [ + bb1, + bb2, + bb3, + bb4, + bb5, + bb6, + bb7, + bb8, + ], + bb8, + ), + ], + terms: { + bb2: { + _2: [], + }, + bb5: { + _3: [], + }, + }, + locals: { + _0: LocalInfo { + kind: Return, + assign_count: 1, + data: Const, + }, + _1: LocalInfo { + kind: AnonVar, + assign_count: 2, + data: Const, + }, + _2: LocalInfo { + kind: AnonVar, + assign_count: 1, + data: Computed, + }, + _3: LocalInfo { + kind: AnonVar, + assign_count: 1, + data: Computed, + }, + _4: LocalInfo { + kind: Unused, + assign_count: 0, + data: Unresolved, + }, + _5: LocalInfo { + kind: Unused, + assign_count: 0, + data: Unresolved, + }, + _6: LocalInfo { + kind: Unused, + assign_count: 0, + data: Unresolved, + }, + _7: LocalInfo { + kind: Unused, + assign_count: 0, + data: Unresolved, + }, + _8: LocalInfo { + kind: Unused, + assign_count: 0, + data: Unresolved, + }, + _9: LocalInfo { + kind: Unused, + assign_count: 0, + data: Unresolved, + }, + }, +} +AnalysisInfo { + body: Body { + basic_blocks: BasicBlocks { + basic_blocks: [ + BasicBlockData { + statements: [ + StorageLive(_1), + _1 = const 0_i32, + FakeRead(ForLet(None), _1), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:25:5: 27:6 (#0), + scope: scope[1], + }, + kind: goto -> bb1, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:25:5: 27:6 (#0), + scope: scope[1], + }, + kind: falseUnwind -> [real: bb2, unwind: bb6], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageLive(_3), + StorageLive(_4), + _4 = _1, + _3 = Lt(move _4, const 10_i32), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:25:11: 25:19 (#0), + scope: scope[1], + }, + kind: switchInt(move _3) -> [0: bb5, otherwise: bb3], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_4), + _5 = CheckedAdd(_1, const 1_i32), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:26:9: 26:17 (#0), + scope: scope[1], + }, + kind: assert(!move (_5.1: bool), "attempt to compute `{} + {}`, which would overflow", _1, const 1_i32) -> [success: bb4, unwind: bb6], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + _1 = move (_5.0: i32), + _2 = const (), + StorageDead(_3), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:25:5: 27:6 (#0), + scope: scope[1], + }, + kind: goto -> bb1, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_4), + StorageLive(_7), + _0 = const (), + StorageDead(_7), + StorageDead(_3), + StorageDead(_1), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:28:2: 28:2 (#0), + scope: scope[0], + }, + kind: return, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:23:1: 28:2 (#0), + scope: scope[0], + }, + kind: resume, + }, + ), + is_cleanup: true, + }, + ], + cache: Cache { + predecessors: OnceLock( + [ + [], + [ + bb0, + bb4, + ], + [ + bb1, + ], + [ + bb2, + ], + [ + bb3, + ], + [ + bb2, + ], + [ + bb1, + bb3, + ], + ], + ), + switch_sources: OnceLock( + , + ), + is_cyclic: OnceLock( + true, + ), + reverse_postorder: OnceLock( + [ + bb0, + bb1, + bb2, + bb5, + bb3, + bb4, + bb6, + ], + ), + dominators: OnceLock( + , + ), + }, + }, + phase: Analysis( + Initial, + ), + pass_count: 1, + source: MirSource { + instance: Item( + DefId(0:6 ~ control[8063]::loop_2), + ), + promoted: None, + }, + source_scopes: [ + SourceScopeData { + span: tests/ui/thesis/control.rs:23:1: 28:2 (#0), + parent_scope: None, + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:6 ~ control[8063]::loop_2).0), + safety: Safe, + }, + ), + }, + SourceScopeData { + span: tests/ui/thesis/control.rs:24:5: 28:2 (#0), + parent_scope: Some( + scope[0], + ), + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:6 ~ control[8063]::loop_2).0), + safety: Safe, + }, + ), + }, + ], + coroutine: None, + local_decls: [ + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:23:12: 23:12 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + User( + Var( + VarBindingForm { + binding_mode: BindByValue( + Mut, + ), + opt_ty_info: None, + opt_match_place: Some( + ( + None, + tests/ui/thesis/control.rs:24:19: 24:20 (#0), + ), + ), + pat_span: tests/ui/thesis/control.rs:24:9: 24:16 (#0), + }, + ), + ), + ), + ty: i32, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:24:9: 24:16 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:23:1: 28:2 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + BlockTailTemp( + BlockTailInfo { + tail_result_is_ignored: true, + span: tests/ui/thesis/control.rs:25:5: 27:6 (#11), + }, + ), + ), + ty: bool, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:25:11: 25:19 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + BlockTailTemp( + BlockTailInfo { + tail_result_is_ignored: true, + span: tests/ui/thesis/control.rs:25:5: 27:6 (#11), + }, + ), + ), + ty: i32, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:25:11: 25:14 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: (i32, bool), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:26:9: 26:17 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + BlockTailTemp( + BlockTailInfo { + tail_result_is_ignored: true, + span: tests/ui/thesis/control.rs:25:5: 27:6 (#11), + }, + ), + ), + ty: !, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:25:5: 27:6 (#11), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:25:5: 27:6 (#11), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: !, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:25:5: 27:6 (#11), + scope: scope[0], + }, + }, + ], + user_type_annotations: [], + arg_count: 0, + spread_arg: None, + var_debug_info: [ + idx => _1, + ], + span: tests/ui/thesis/control.rs:23:1: 28:2 (#0), + required_consts: [], + is_polymorphic: false, + injection_phase: None, + tainted_by_errors: None, + function_coverage_info: None, + }, + def_id: DefId(0:6 ~ control[8063]::loop_2), + cfg: { + bb0: Linear( + bb1, + ), + bb1: Linear( + bb2, + ), + bb2: Break { + next: bb3, + brea: bb5, + }, + bb3: Linear( + bb4, + ), + bb4: Linear( + bb1, + ), + bb5: Return, + bb6: None, + }, + loops: [ + ( + [ + bb1, + bb2, + bb3, + bb4, + ], + bb4, + ), + ], + terms: {}, + locals: { + _0: LocalInfo { + kind: Return, + assign_count: 1, + data: Const, + }, + _1: LocalInfo { + kind: UserVar( + "idx", + ), + assign_count: 2, + data: Mixed, + }, + _2: LocalInfo { + kind: AnonVar, + assign_count: 1, + data: Const, + }, + _3: LocalInfo { + kind: AnonVar, + assign_count: 1, + data: Computed, + }, + _4: LocalInfo { + kind: AnonVar, + assign_count: 1, + data: Local( + _1, + ), + }, + _5: LocalInfo { + kind: AnonVar, + assign_count: 1, + data: Computed, + }, + _6: LocalInfo { + kind: Unused, + assign_count: 0, + data: Unresolved, + }, + _7: LocalInfo { + kind: Unused, + assign_count: 0, + data: Unresolved, + }, + _8: LocalInfo { + kind: Unused, + assign_count: 0, + data: Unresolved, + }, + }, +} +AnalysisInfo { + body: Body { + basic_blocks: BasicBlocks { + basic_blocks: [ + BasicBlockData { + statements: [ + StorageLive(_1), + _1 = const 0_i32, + FakeRead(ForLet(None), _1), + StorageLive(_2), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:32:5: 38:6 (#0), + scope: scope[1], + }, + kind: goto -> bb1, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:32:5: 38:6 (#0), + scope: scope[1], + }, + kind: falseUnwind -> [real: bb2, unwind: bb6], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + _4 = CheckedAdd(_1, const 1_i32), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:33:9: 33:17 (#0), + scope: scope[1], + }, + kind: assert(!move (_4.1: bool), "attempt to compute `{} + {}`, which would overflow", _1, const 1_i32) -> [success: bb3, unwind: bb6], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + _1 = move (_4.0: i32), + StorageLive(_5), + StorageLive(_6), + StorageLive(_7), + _7 = _1, + _6 = Lt(move _7, const 10_i32), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:34:12: 34:20 (#0), + scope: scope[1], + }, + kind: switchInt(move _6) -> [0: bb5, otherwise: bb4], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_7), + _2 = const (), + StorageDead(_6), + StorageDead(_5), + StorageDead(_2), + StorageLive(_10), + _10 = const 0_i32, + FakeRead(ForLet(None), _10), + _0 = const (), + StorageDead(_10), + StorageDead(_1), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:40:2: 40:2 (#0), + scope: scope[0], + }, + kind: return, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_7), + _5 = const (), + StorageDead(_6), + StorageDead(_5), + StorageLive(_9), + _9 = const 1_i32, + FakeRead(ForLet(None), _9), + _3 = const (), + StorageDead(_9), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:32:5: 38:6 (#0), + scope: scope[1], + }, + kind: goto -> bb1, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:30:1: 40:2 (#0), + scope: scope[0], + }, + kind: resume, + }, + ), + is_cleanup: true, + }, + ], + cache: Cache { + predecessors: OnceLock( + [ + [], + [ + bb0, + bb5, + ], + [ + bb1, + ], + [ + bb2, + ], + [ + bb3, + ], + [ + bb3, + ], + [ + bb1, + bb2, + ], + ], + ), + switch_sources: OnceLock( + , + ), + is_cyclic: OnceLock( + true, + ), + reverse_postorder: OnceLock( + [ + bb0, + bb1, + bb2, + bb3, + bb5, + bb4, + bb6, + ], + ), + dominators: OnceLock( + , + ), + }, + }, + phase: Analysis( + Initial, + ), + pass_count: 1, + source: MirSource { + instance: Item( + DefId(0:7 ~ control[8063]::loop_3), + ), + promoted: None, + }, + source_scopes: [ + SourceScopeData { + span: tests/ui/thesis/control.rs:30:1: 40:2 (#0), + parent_scope: None, + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:7 ~ control[8063]::loop_3).0), + safety: Safe, + }, + ), + }, + SourceScopeData { + span: tests/ui/thesis/control.rs:31:5: 40:2 (#0), + parent_scope: Some( + scope[0], + ), + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:7 ~ control[8063]::loop_3).0), + safety: Safe, + }, + ), + }, + SourceScopeData { + span: tests/ui/thesis/control.rs:37:9: 38:6 (#0), + parent_scope: Some( + scope[1], + ), + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:7 ~ control[8063]::loop_3).0), + safety: Safe, + }, + ), + }, + SourceScopeData { + span: tests/ui/thesis/control.rs:39:5: 40:2 (#0), + parent_scope: Some( + scope[1], + ), + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:7 ~ control[8063]::loop_3).0), + safety: Safe, + }, + ), + }, + ], + coroutine: None, + local_decls: [ + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:30:12: 30:12 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + User( + Var( + VarBindingForm { + binding_mode: BindByValue( + Mut, + ), + opt_ty_info: None, + opt_match_place: Some( + ( + None, + tests/ui/thesis/control.rs:31:19: 31:20 (#0), + ), + ), + pat_span: tests/ui/thesis/control.rs:31:9: 31:16 (#0), + }, + ), + ), + ), + ty: i32, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:31:9: 31:16 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:32:5: 38:6 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:30:1: 40:2 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: (i32, bool), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:33:9: 33:17 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:34:9: 36:10 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: bool, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:34:12: 34:20 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: i32, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:34:12: 34:15 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: !, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:34:21: 36:10 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + User( + Var( + VarBindingForm { + binding_mode: BindByValue( + Not, + ), + opt_ty_info: None, + opt_match_place: Some( + ( + None, + tests/ui/thesis/control.rs:37:18: 37:19 (#0), + ), + ), + pat_span: tests/ui/thesis/control.rs:37:13: 37:15 (#0), + }, + ), + ), + ), + ty: i32, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:37:13: 37:15 (#0), + scope: scope[1], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + User( + Var( + VarBindingForm { + binding_mode: BindByValue( + Not, + ), + opt_ty_info: None, + opt_match_place: Some( + ( + None, + tests/ui/thesis/control.rs:39:14: 39:15 (#0), + ), + ), + pat_span: tests/ui/thesis/control.rs:39:9: 39:11 (#0), + }, + ), + ), + ), + ty: i32, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:39:9: 39:11 (#0), + scope: scope[1], + }, + }, + ], + user_type_annotations: [], + arg_count: 0, + spread_arg: None, + var_debug_info: [ + idx => _1, + _x => _9, + _y => _10, + ], + span: tests/ui/thesis/control.rs:30:1: 40:2 (#0), + required_consts: [], + is_polymorphic: false, + injection_phase: None, + tainted_by_errors: None, + function_coverage_info: None, + }, + def_id: DefId(0:7 ~ control[8063]::loop_3), + cfg: { + bb0: Linear( + bb1, + ), + bb1: Linear( + bb2, + ), + bb2: Linear( + bb3, + ), + bb3: Break { + next: bb5, + brea: bb4, + }, + bb4: Return, + bb5: Linear( + bb1, + ), + bb6: None, + }, + loops: [ + ( + [ + bb1, + bb2, + bb3, + bb5, + ], + bb5, + ), + ], + terms: {}, + locals: { + _0: LocalInfo { + kind: Return, + assign_count: 1, + data: Const, + }, + _1: LocalInfo { + kind: UserVar( + "idx", + ), + assign_count: 2, + data: Mixed, + }, + _2: LocalInfo { + kind: AnonVar, + assign_count: 1, + data: Const, + }, + _3: LocalInfo { + kind: AnonVar, + assign_count: 1, + data: Const, + }, + _4: LocalInfo { + kind: AnonVar, + assign_count: 1, + data: Computed, + }, + _5: LocalInfo { + kind: AnonVar, + assign_count: 1, + data: Const, + }, + _6: LocalInfo { + kind: AnonVar, + assign_count: 1, + data: Computed, + }, + _7: LocalInfo { + kind: AnonVar, + assign_count: 1, + data: Local( + _1, + ), + }, + _8: LocalInfo { + kind: Unused, + assign_count: 0, + data: Unresolved, + }, + _9: LocalInfo { + kind: UserVar( + "_x", + ), + assign_count: 1, + data: Const, + }, + _10: LocalInfo { + kind: UserVar( + "_y", + ), + assign_count: 1, + data: Const, + }, + }, +} +AnalysisInfo { + body: Body { + basic_blocks: BasicBlocks { + basic_blocks: [ + BasicBlockData { + statements: [ + StorageLive(_1), + StorageLive(_2), + _2 = const 0_i32, + FakeRead(ForLet(None), _2), + StorageLive(_3), + StorageLive(_4), + _4 = const true, + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:45:13: 45:17 (#0), + scope: scope[1], + }, + kind: switchInt(move _4) -> [0: bb1, otherwise: bb2], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + _1 = const (), + StorageDead(_4), + StorageDead(_3), + StorageDead(_2), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:43:5: 49:6 (#0), + scope: scope[0], + }, + kind: goto -> bb3, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + _3 = const (), + StorageDead(_4), + StorageDead(_3), + StorageLive(_6), + _6 = const 0_i32, + FakeRead(ForLet(None), _6), + _1 = const (), + StorageDead(_6), + StorageDead(_2), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:43:5: 49:6 (#0), + scope: scope[0], + }, + kind: goto -> bb3, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_1), + _0 = const 12_u32, + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:52:2: 52:2 (#0), + scope: scope[0], + }, + kind: return, + }, + ), + is_cleanup: false, + }, + ], + cache: Cache { + predecessors: OnceLock( + [ + [], + [ + bb0, + ], + [ + bb0, + ], + [ + bb1, + bb2, + ], + ], + ), + switch_sources: OnceLock( + , + ), + is_cyclic: OnceLock( + false, + ), + reverse_postorder: OnceLock( + [ + bb0, + bb1, + bb2, + bb3, + ], + ), + dominators: OnceLock( + , + ), + }, + }, + phase: Analysis( + Initial, + ), + pass_count: 1, + source: MirSource { + instance: Item( + DefId(0:8 ~ control[8063]::block_with_label), + ), + promoted: None, + }, + source_scopes: [ + SourceScopeData { + span: tests/ui/thesis/control.rs:42:1: 52:2 (#0), + parent_scope: None, + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:8 ~ control[8063]::block_with_label).0), + safety: Safe, + }, + ), + }, + SourceScopeData { + span: tests/ui/thesis/control.rs:44:9: 49:6 (#0), + parent_scope: Some( + scope[0], + ), + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:8 ~ control[8063]::block_with_label).0), + safety: Safe, + }, + ), + }, + SourceScopeData { + span: tests/ui/thesis/control.rs:48:9: 49:6 (#0), + parent_scope: Some( + scope[1], + ), + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:8 ~ control[8063]::block_with_label).0), + safety: Safe, + }, + ), + }, + ], + coroutine: None, + local_decls: [ + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: u32, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:42:26: 42:29 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:43:5: 49:6 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + User( + Var( + VarBindingForm { + binding_mode: BindByValue( + Not, + ), + opt_ty_info: None, + opt_match_place: Some( + ( + None, + tests/ui/thesis/control.rs:44:18: 44:19 (#0), + ), + ), + pat_span: tests/ui/thesis/control.rs:44:13: 44:15 (#0), + }, + ), + ), + ), + ty: i32, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:44:13: 44:15 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:45:9: 47:10 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: bool, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:45:13: 45:17 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: !, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:45:18: 47:10 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + User( + Var( + VarBindingForm { + binding_mode: BindByValue( + Not, + ), + opt_ty_info: None, + opt_match_place: Some( + ( + None, + tests/ui/thesis/control.rs:48:18: 48:19 (#0), + ), + ), + pat_span: tests/ui/thesis/control.rs:48:13: 48:15 (#0), + }, + ), + ), + ), + ty: i32, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:48:13: 48:15 (#0), + scope: scope[1], + }, + }, + ], + user_type_annotations: [], + arg_count: 0, + spread_arg: None, + var_debug_info: [ + _x => _2, + _y => _6, + ], + span: tests/ui/thesis/control.rs:42:1: 52:2 (#0), + required_consts: [], + is_polymorphic: false, + injection_phase: None, + tainted_by_errors: None, + function_coverage_info: None, + }, + def_id: DefId(0:8 ~ control[8063]::block_with_label), + cfg: { + bb0: Condition { + branches: [ + bb1, + bb2, + ], + }, + bb1: Linear( + bb3, + ), + bb2: Linear( + bb3, + ), + bb3: Return, + }, + loops: [], + terms: {}, + locals: { + _0: LocalInfo { + kind: Return, + assign_count: 1, + data: Const, + }, + _1: LocalInfo { + kind: AnonVar, + assign_count: 2, + data: Const, + }, + _2: LocalInfo { + kind: UserVar( + "_x", + ), + assign_count: 1, + data: Const, + }, + _3: LocalInfo { + kind: AnonVar, + assign_count: 1, + data: Const, + }, + _4: LocalInfo { + kind: AnonVar, + assign_count: 1, + data: Const, + }, + _5: LocalInfo { + kind: Unused, + assign_count: 0, + data: Unresolved, + }, + _6: LocalInfo { + kind: UserVar( + "_y", + ), + assign_count: 1, + data: Const, + }, + }, +} +Type: LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: u32, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:42:26: 42:29 (#0), + scope: scope[0], + }, +} +AnalysisInfo { + body: Body { + basic_blocks: BasicBlocks { + basic_blocks: [ + BasicBlockData { + statements: [ + _0 = const true, + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:75:2: 75:2 (#0), + scope: scope[0], + }, + kind: return, + }, + ), + is_cleanup: false, + }, + ], + cache: Cache { + predecessors: OnceLock( + [ + [], + ], + ), + switch_sources: OnceLock( + , + ), + is_cyclic: OnceLock( + false, + ), + reverse_postorder: OnceLock( + [ + bb0, + ], + ), + dominators: OnceLock( + , + ), + }, + }, + phase: Analysis( + Initial, + ), + pass_count: 1, + source: MirSource { + instance: Item( + DefId(0:10 ~ control[8063]::cond_1), + ), + promoted: None, + }, + source_scopes: [ + SourceScopeData { + span: tests/ui/thesis/control.rs:73:1: 75:2 (#0), + parent_scope: None, + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:10 ~ control[8063]::cond_1).0), + safety: Safe, + }, + ), + }, + ], + coroutine: None, + local_decls: [ + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: bool, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:73:16: 73:20 (#0), + scope: scope[0], + }, + }, + ], + user_type_annotations: [], + arg_count: 0, + spread_arg: None, + var_debug_info: [], + span: tests/ui/thesis/control.rs:73:1: 75:2 (#0), + required_consts: [], + is_polymorphic: false, + injection_phase: None, + tainted_by_errors: None, + function_coverage_info: None, + }, + def_id: DefId(0:10 ~ control[8063]::cond_1), + cfg: { + bb0: Return, + }, + loops: [], + terms: {}, + locals: { + _0: LocalInfo { + kind: Return, + assign_count: 1, + data: Const, + }, + }, +} +Type: LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: bool, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:73:16: 73:20 (#0), + scope: scope[0], + }, +} +AnalysisInfo { + body: Body { + basic_blocks: BasicBlocks { + basic_blocks: [ + BasicBlockData { + statements: [ + _0 = const false, + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:78:2: 78:2 (#0), + scope: scope[0], + }, + kind: return, + }, + ), + is_cleanup: false, + }, + ], + cache: Cache { + predecessors: OnceLock( + [ + [], + ], + ), + switch_sources: OnceLock( + , + ), + is_cyclic: OnceLock( + false, + ), + reverse_postorder: OnceLock( + [ + bb0, + ], + ), + dominators: OnceLock( + , + ), + }, + }, + phase: Analysis( + Initial, + ), + pass_count: 1, + source: MirSource { + instance: Item( + DefId(0:11 ~ control[8063]::cond_2), + ), + promoted: None, + }, + source_scopes: [ + SourceScopeData { + span: tests/ui/thesis/control.rs:76:1: 78:2 (#0), + parent_scope: None, + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:11 ~ control[8063]::cond_2).0), + safety: Safe, + }, + ), + }, + ], + coroutine: None, + local_decls: [ + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: bool, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:76:16: 76:20 (#0), + scope: scope[0], + }, + }, + ], + user_type_annotations: [], + arg_count: 0, + spread_arg: None, + var_debug_info: [], + span: tests/ui/thesis/control.rs:76:1: 78:2 (#0), + required_consts: [], + is_polymorphic: false, + injection_phase: None, + tainted_by_errors: None, + function_coverage_info: None, + }, + def_id: DefId(0:11 ~ control[8063]::cond_2), + cfg: { + bb0: Return, + }, + loops: [], + terms: {}, + locals: { + _0: LocalInfo { + kind: Return, + assign_count: 1, + data: Const, + }, + }, +} +Type: LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: bool, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:76:16: 76:20 (#0), + scope: scope[0], + }, +} +AnalysisInfo { + body: Body { + basic_blocks: BasicBlocks { + basic_blocks: [ + BasicBlockData { + statements: [ + _0 = const (), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:80:13: 80:13 (#0), + scope: scope[0], + }, + kind: return, + }, + ), + is_cleanup: false, + }, + ], + cache: Cache { + predecessors: OnceLock( + [ + [], + ], + ), + switch_sources: OnceLock( + , + ), + is_cyclic: OnceLock( + false, + ), + reverse_postorder: OnceLock( + [ + bb0, + ], + ), + dominators: OnceLock( + , + ), + }, + }, + phase: Analysis( + Initial, + ), + pass_count: 1, + source: MirSource { + instance: Item( + DefId(0:12 ~ control[8063]::main), + ), + promoted: None, + }, + source_scopes: [ + SourceScopeData { + span: tests/ui/thesis/control.rs:80:1: 80:13 (#0), + parent_scope: None, + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:12 ~ control[8063]::main).0), + safety: Safe, + }, + ), + }, + ], + coroutine: None, + local_decls: [ + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/control.rs:80:10: 80:10 (#0), + scope: scope[0], + }, + }, + ], + user_type_annotations: [], + arg_count: 0, + spread_arg: None, + var_debug_info: [], + span: tests/ui/thesis/control.rs:80:1: 80:13 (#0), + required_consts: [], + is_polymorphic: false, + injection_phase: None, + tainted_by_errors: None, + function_coverage_info: None, + }, + def_id: DefId(0:12 ~ control[8063]::main), + cfg: { + bb0: Return, + }, + loops: [], + terms: {}, + locals: { + _0: LocalInfo { + kind: Return, + assign_count: 1, + data: Const, + }, + }, +} diff --git a/src/tools/clippy/tests/ui/thesis/nilqam_patterns.rs b/src/tools/clippy/tests/ui/thesis/nilqam_patterns.rs new file mode 100644 index 0000000000000..0fbb0a359c437 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/nilqam_patterns.rs @@ -0,0 +1,79 @@ +//@rustc-env: CLIPPY_PETS_PRINT=1 +//@rustc-env: CLIPPY_STATS_PRINT=1 +//@rustc-env: CLIPPY_PRINT_MIR=1 +//! Patterns suggested by Nico, lqd, and Amanda + +#![allow(dropping_references)] + +// #[warn(clippy::borrow_pats)] +mod part_return { + struct List { + value: T, + next: Option>>, + } + + fn next_unchecked(x: &List) -> &List { + let Some(n) = &x.next else { + panic!(); + }; + &*n + } +} + +// #[warn(clippy::borrow_pats)] +mod overwrite_in_loop { + trait SomethingMut { + fn something(&mut self); + } + struct List { + value: T, + next: Option>>, + } + + fn last(x: &mut List) -> &List { + let mut p = x; + loop { + p.value.something(); + if p.next.is_none() { + break p; + } + p = p.next.as_mut().unwrap(); + } + } +} + +/// Such loan kills should be detected by named references. +/// AFAIK, they can only occur for named references. Tracking them from the +/// owned value could result to incorrect counting, as a reference can have +/// multiple brockers. +#[warn(clippy::borrow_pats)] +fn mut_named_ref_non_kill() { + let mut x = 1; + let mut y = 1; + let mut p: &u32 = &x; + // x is borrowed here + drop(p); + + // variable p is dead here + p = &y; + // x is not borrowed here + drop(p); +} + +// #[forbid(clippy::borrow_pats)] +mod early_kill_for_non_drop { + // This wouldn't work if `X` would implement `Drop` + // Credit: https://github.com/rust-lang/rfcs/pull/2094#issuecomment-320171945 + struct X<'a> { + val: &'a usize, + } + fn mutation_works_because_non_drop() { + let mut x = 42; + let y = X { val: &x }; + x = 50; + } +} + +// FIXME: Maybe: https://github.com/nikomatsakis/nll-rfc/issues/38 +// FIXME: Maybe include recursion? +fn main() {} diff --git a/src/tools/clippy/tests/ui/thesis/nilqam_patterns.stderr b/src/tools/clippy/tests/ui/thesis/nilqam_patterns.stderr new file mode 100644 index 0000000000000..59342400f3559 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/nilqam_patterns.stderr @@ -0,0 +1 @@ +TODO: implement analysis for named refs diff --git a/src/tools/clippy/tests/ui/thesis/nilqam_patterns.stdout b/src/tools/clippy/tests/ui/thesis/nilqam_patterns.stdout new file mode 100644 index 0000000000000..1ede473213bdf --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/nilqam_patterns.stdout @@ -0,0 +1,20 @@ +# "mut_named_ref_non_kill" +- x : (Mutable , Owned , Local , Copy , NonDrop, Primitive) {NamedBorrow} +- y : (Mutable , Owned , Local , Copy , NonDrop, Primitive) {NamedBorrow} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments, HasAnonBorrow} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + owned: OwnedStats { + temp_borrow_count: 0, + temp_borrow_mut_count: 0, + temp_borrow_extended_count: 0, + temp_borrow_mut_extended_count: 0, + named_borrow_count: 2, + named_borrow_mut_count: 0, + two_phased_borrows: 0, + }, +} + diff --git a/src/tools/clippy/tests/ui/thesis/pass/features_none.rs b/src/tools/clippy/tests/ui/thesis/pass/features_none.rs new file mode 100644 index 0000000000000..d8f748652fd14 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/features_none.rs @@ -0,0 +1,4 @@ +//@rustc-env: CLIPPY_PETS_PRINT=1 + +#[warn(clippy::borrow_pats)] +fn main() {} diff --git a/src/tools/clippy/tests/ui/thesis/pass/features_none.stdout b/src/tools/clippy/tests/ui/thesis/pass/features_none.stdout new file mode 100644 index 0000000000000..7b9d3d282b3cc --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/features_none.stdout @@ -0,0 +1,3 @@ +# "main" +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments} + diff --git a/src/tools/clippy/tests/ui/thesis/pass/features_some.rs b/src/tools/clippy/tests/ui/thesis/pass/features_some.rs new file mode 100644 index 0000000000000..57ed5d1be1f73 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/features_some.rs @@ -0,0 +1,5 @@ +//@rustc-env: CLIPPY_PETS_PRINT=1 +#![feature(lint_reasons)] + +#[warn(clippy::borrow_pats)] +fn main() {} diff --git a/src/tools/clippy/tests/ui/thesis/pass/features_some.stdout b/src/tools/clippy/tests/ui/thesis/pass/features_some.stdout new file mode 100644 index 0000000000000..376408b3793e9 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/features_some.stdout @@ -0,0 +1 @@ +Disabled due to feature use diff --git a/src/tools/clippy/tests/ui/thesis/pass/fn_call_relations.rs b/src/tools/clippy/tests/ui/thesis/pass/fn_call_relations.rs new file mode 100644 index 0000000000000..2cef1d2195318 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/fn_call_relations.rs @@ -0,0 +1,119 @@ +//@rustc-env: CLIPPY_PETS_TEST_RELATIONS=1 +//@rustc-env: CLIPPY_PRINT_MIR=1 + +//! A file to test dependencies between function parameters +#![allow(unused)] + +fn no_rep(_: u32, _: String) -> u16 { + 12 +} + +fn direct_dep(a: &String, _: u32) -> &String { + a +} + +fn lifetime_dep<'a>(_: &String, a: &'a String) -> &'a String { + a +} + +fn lifetime_dep_more<'a>(_: &'a String, a: &'a String) -> &'a String { + a +} + +fn lifetime_dep_const<'a>(_: &'a str, a: &'a str) -> &'a str { + a +} + +fn lifetime_dep_prim<'a>(_: &'a u32, a: &'a u32) -> &'a u32 { + a +} + +fn lifetime_outlives_middle_1<'a, 'd: 'a, 'b: 'd, 'c: 'd>(b: &'b String, _c: &'c String) -> &'a String { + b +} + +fn lifetime_outlives_middle_2<'a, 'b: 'd, 'c: 'd, 'd: 'a>(b: &'b String, _c: &'c String) -> &'a String { + b +} + +fn lifetime_outlives<'a, 'b: 'a, 'c: 'a>(b: &'b String, _c: &'c String) -> &'a String { + b +} + +#[warn(clippy::borrow_pats)] +fn test_return(s1: String, s2: String, s3: String, pair: (String, String)) { + let _test = no_rep(1, s3); + let _test = direct_dep(&s1, 2); + let _test = lifetime_dep(&s1, &s2); + let _test = lifetime_dep_more(&s1, &s2); + let _test = lifetime_dep_prim(&1, &2); + let _test = lifetime_dep_const("In", "Put"); + let _test = lifetime_outlives_middle_1(&s1, &s2); + let _test = lifetime_outlives_middle_2(&s1, &s2); + let _test = lifetime_outlives(&s1, &s2); + let _test = lifetime_outlives(&pair.0, &pair.1); +} + +struct Owner<'a> { + val: &'a String, +} + +fn immutable_owner(_owner: &Owner<'_>, _val: &String) {} + +fn mutable_owner(_owner: &mut Owner<'_>, _val: &String) {} + +fn mutable_owner_and_arg(_owner: &mut Owner<'_>, _val: &mut String) {} + +fn mutable_owner_and_lifetimed_str<'a>(owner: &mut Owner<'a>, val: &'a mut String) { + owner.val = val; +} + +fn immutable_owner_and_lifetimed_str<'a>(_owner: &Owner<'a>, val: &'a mut String) {} + +fn mutable_owner_other_lifetimed_str<'a, 'b>(_owner: &'b &mut Owner<'a>, _val: &'b mut String) {} + +fn mutable_owner_self_lifetimed_str<'a>(_owner: &'a mut Owner<'a>, _val: &'a mut String) {} + +#[warn(clippy::borrow_pats)] +fn test_mut_args_1<'a>(owner: &mut Owner<'a>, value: &'a mut String) { + immutable_owner(owner, value); + mutable_owner(owner, value); + mutable_owner_and_arg(owner, value); + mutable_owner_and_lifetimed_str(owner, value); +} + +#[warn(clippy::borrow_pats)] +fn test_mut_args_2<'a>(owner: &mut Owner<'a>, value: &'a mut String) { + mutable_owner_other_lifetimed_str(&owner, value); +} + +#[warn(clippy::borrow_pats)] +fn test_mut_args_3<'a>(owner: &'a mut Owner<'a>, value: &'a mut String) { + mutable_owner_self_lifetimed_str(owner, value); +} + +fn take_string(_s: String) {} +fn take_string_ref(_s: &String) {} +fn pass_t(tee: T) -> T { + tee +} + +#[warn(clippy::borrow_pats)] +fn use_local_func() { + let func = take_string_ref; + + func(&String::new()); +} + +#[warn(clippy::borrow_pats)] +fn use_arg_func(func: fn(&String) -> &str) { + func(&String::new()); +} + +#[warn(clippy::borrow_pats)] +fn borrow_as_generic(s: String) { + let _tee = pass_t(&s); + // let _len = tee.len(); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/thesis/pass/fn_call_relations.stdout b/src/tools/clippy/tests/ui/thesis/pass/fn_call_relations.stdout new file mode 100644 index 0000000000000..eb9a1c8a1fbfc --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/fn_call_relations.stdout @@ -0,0 +1,242 @@ +# Relations for "test_return" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: { + bb0: {}, + bb1: { + _7: [ + _8, + ], + }, + bb2: { + _10: [ + _13, + ], + }, + bb3: { + _15: [ + _16, + _18, + ], + }, + bb4: { + _20: [ + _21, + _24, + ], + }, + bb5: { + _27: [ + _28, + _30, + ], + }, + bb6: { + _32: [ + _33, + _35, + ], + }, + bb7: { + _37: [ + _38, + _40, + ], + }, + bb8: { + _42: [ + _43, + _45, + ], + }, + bb9: { + _47: [ + _48, + _50, + ], + }, +} +- Incompltete Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +Connection from _1 to _2 was not confirmed +# Relations for "test_mut_args_1" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 1, + arg_relations_found: 1, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: { + bb0: {}, + bb1: {}, + bb2: {}, + bb3: { + _13: [ + _14, + ], + }, +} +- Incompltete Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +Connection from _1 to _2 was not confirmed +# Relations for "test_mut_args_2" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 1, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: { + bb0: {}, +} +- Incompltete Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +# Relations for "test_mut_args_3" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 1, + arg_relations_found: 1, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: { + bb0: { + _4: [ + _5, + ], + }, +} +- Incompltete Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +# Relations for "use_local_func" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: { + bb0: {}, + bb1: {}, +} +- Incompltete Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments} + +# Relations for "use_arg_func" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 1, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: { + bb0: {}, + bb1: {}, +} +- Incompltete Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +# Relations for "borrow_as_generic" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 1, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: { + bb0: {}, +} +- Incompltete Body: Output(Unit) , NotConst , Safe , Sync , Free {} + diff --git a/src/tools/clippy/tests/ui/thesis/pass/fn_def_relations.rs b/src/tools/clippy/tests/ui/thesis/pass/fn_def_relations.rs new file mode 100644 index 0000000000000..35ea54093885f --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/fn_def_relations.rs @@ -0,0 +1,143 @@ +//@rustc-env: CLIPPY_PETS_TEST_RELATIONS=1 +//@rustc-env: CLIPPY_PRINT_MIR=1 + +#[warn(clippy::borrow_pats)] +fn no_rep(_: u32, _: String) -> u16 { + 12 +} + +#[warn(clippy::borrow_pats)] +fn direct_dep(a: &String, _: u32) -> &String { + a +} + +#[warn(clippy::borrow_pats)] +fn lifetime_dep<'a>(_: &String, a: &'a String) -> &'a String { + a +} + +#[warn(clippy::borrow_pats)] +fn lifetime_dep_more<'a>(_: &'a String, a: &'a String) -> &'a String { + a +} + +#[warn(clippy::borrow_pats)] +fn lifetime_dep_or<'a>(a: &'a String, b: &'a String) -> &'a String { + if true { a } else { b } +} + +#[warn(clippy::borrow_pats)] +fn lifetime_dep_const<'a>(_: &'a str, a: &'a str) -> &'a str { + a +} + +#[warn(clippy::borrow_pats)] +fn lifetime_dep_prim<'a>(_: &'a u32, a: &'a u32) -> &'a u32 { + a +} + +#[warn(clippy::borrow_pats)] +fn lifetime_outlives_middle_1<'a, 'd: 'a, 'b: 'd, 'c: 'd>(b: &'b String, _c: &'c String) -> &'a String { + b +} + +#[warn(clippy::borrow_pats)] +fn lifetime_outlives_middle_2<'a, 'b: 'd, 'c: 'd, 'd: 'a>(a: &'b String, b: &'c String) -> &'a String { + if true { a } else { b } +} + +#[warn(clippy::borrow_pats)] +fn lifetime_outlives<'a, 'b: 'a, 'c: 'a>(b: &'b String, _c: &'c String) -> &'a String { + b +} + +fn test_return(s1: String, s2: String, s3: String, pair: (String, String)) { + let _test = no_rep(1, s3); + let _test = direct_dep(&s1, 2); + let _test = lifetime_dep(&s1, &s2); + let _test = lifetime_dep_more(&s1, &s2); + let _test = lifetime_dep_prim(&1, &2); + let _test = lifetime_dep_const("In", "Put"); + let _test = lifetime_outlives_middle_1(&s1, &s2); + let _test = lifetime_outlives_middle_2(&s1, &s2); + let _test = lifetime_outlives(&s1, &s2); + let _test = lifetime_outlives(&pair.0, &pair.1); +} + +struct Owner<'a> { + val: &'a String, +} + +#[warn(clippy::borrow_pats)] +fn immutable_owner(_owner: &Owner<'_>, _val: &String) {} + +#[warn(clippy::borrow_pats)] +fn mutable_owner(_owner: &mut Owner<'_>, _val: &String) {} + +#[warn(clippy::borrow_pats)] +fn mutable_owner_and_arg(_owner: &mut Owner<'_>, _val: &mut String) {} + +#[warn(clippy::borrow_pats)] +fn mutable_owner_and_lifetimed_str<'a>(owner: &mut Owner<'a>, val: &'a mut String) { + owner.val = val; +} + +#[warn(clippy::borrow_pats)] +fn immutable_owner_and_lifetimed_str<'a>(_owner: &Owner<'a>, val: &'a mut String) {} + +#[warn(clippy::borrow_pats)] +fn mutable_owner_other_lifetimed_str<'a, 'b>(_owner: &'b &mut Owner<'a>, _val: &'b mut String) {} + +#[warn(clippy::borrow_pats)] +fn mutable_owner_self_lifetimed_str<'a>(owner: &'a mut Owner<'a>, val: &'a mut String) { + owner.val = val; +} + +#[warn(clippy::borrow_pats)] +fn test_mut_args_1<'a>(owner: &mut Owner<'a>, value: &'a mut String) { + immutable_owner(owner, value); + mutable_owner(owner, value); + mutable_owner_and_arg(owner, value); + mutable_owner_and_lifetimed_str(owner, value); +} + +#[warn(clippy::borrow_pats)] +fn test_mut_args_2<'a>(owner: &mut Owner<'a>, value: &'a mut String) { + mutable_owner_other_lifetimed_str(&owner, value); +} + +#[warn(clippy::borrow_pats)] +fn test_mut_args_3<'a>(owner: &'a mut Owner<'a>, value: &'a mut String) { + mutable_owner_self_lifetimed_str(owner, value); +} + +#[warn(clippy::borrow_pats)] +fn test_return_regions_static() -> &'static str { + "Ducks" +} + +#[warn(clippy::borrow_pats)] +fn test_return_regions_non_static(_arg: &str) -> &str { + "Ducks" +} + +#[warn(clippy::borrow_pats)] +fn test_return_regions_non_static_or_default(arg: &str) -> &str { + if arg.is_empty() { "Ducks" } else { arg } +} + +#[warn(clippy::borrow_pats)] +fn test_return_static_tuple_for_non_static(arg: &str) -> (&str, &str) { + if arg.is_empty() { ("hey", "you") } else { (arg, arg) } +} + +#[warn(clippy::borrow_pats)] +fn test_return_static_tuple(arg: &str) -> (&'static str, &'static str) { + if arg.is_empty() { + ("hey", "you") + } else { + ("duck", "cat") + } +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/thesis/pass/fn_def_relations.stdout b/src/tools/clippy/tests/ui/thesis/pass/fn_def_relations.stdout new file mode 100644 index 0000000000000..5c9e788b2ee12 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/fn_def_relations.stdout @@ -0,0 +1,599 @@ +# Relations for "no_rep" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: {} +- Incompltete Body: Output(Primitive), NotConst , Safe , Sync , Free {} + +# Relations for "direct_dep" +- Incompltete Stats: BodyStats { + return_relations_signature: 1, + return_relations_found: 1, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: {} +- Incompltete Body: Output(Reference), NotConst , Safe , Sync , Free {} + +# Relations for "lifetime_dep" +- Incompltete Stats: BodyStats { + return_relations_signature: 1, + return_relations_found: 1, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: {} +- Incompltete Body: Output(Reference), NotConst , Safe , Sync , Free {} + +# Relations for "lifetime_dep_more" +- Incompltete Stats: BodyStats { + return_relations_signature: 2, + return_relations_found: 1, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: {} +- Incompltete Body: Output(Reference), NotConst , Safe , Sync , Free {} + +# Relations for "lifetime_dep_or" +- Incompltete Stats: BodyStats { + return_relations_signature: 2, + return_relations_found: 2, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: {} +- Incompltete Body: Output(Reference), NotConst , Safe , Sync , Free {} + +# Relations for "lifetime_dep_const" +- Incompltete Stats: BodyStats { + return_relations_signature: 2, + return_relations_found: 1, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: {} +- Incompltete Body: Output(Reference), NotConst , Safe , Sync , Free {} + +# Relations for "lifetime_dep_prim" +- Incompltete Stats: BodyStats { + return_relations_signature: 2, + return_relations_found: 1, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: {} +- Incompltete Body: Output(Reference), NotConst , Safe , Sync , Free {} + +# Relations for "lifetime_outlives_middle_1" +- Incompltete Stats: BodyStats { + return_relations_signature: 2, + return_relations_found: 1, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: {} +- Incompltete Body: Output(Reference), NotConst , Safe , Sync , Free {} + +# Relations for "lifetime_outlives_middle_2" +- Incompltete Stats: BodyStats { + return_relations_signature: 2, + return_relations_found: 2, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: {} +- Incompltete Body: Output(Reference), NotConst , Safe , Sync , Free {} + +# Relations for "lifetime_outlives" +- Incompltete Stats: BodyStats { + return_relations_signature: 2, + return_relations_found: 1, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: {} +- Incompltete Body: Output(Reference), NotConst , Safe , Sync , Free {} + +# Relations for "immutable_owner" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: {} +- Incompltete Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +# Relations for "mutable_owner" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: {} +- Incompltete Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +# Relations for "mutable_owner_and_arg" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: {} +- Incompltete Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +# Relations for "mutable_owner_and_lifetimed_str" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 1, + arg_relations_found: 1, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: {} +- Incompltete Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +# Relations for "immutable_owner_and_lifetimed_str" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: {} +- Incompltete Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +# Relations for "mutable_owner_other_lifetimed_str" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: {} +- Incompltete Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +# Relations for "mutable_owner_self_lifetimed_str" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 1, + arg_relations_found: 1, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: {} +- Incompltete Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +# Relations for "test_mut_args_1" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 1, + arg_relations_found: 1, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: { + bb0: {}, + bb1: {}, + bb2: {}, + bb3: { + _13: [ + _14, + ], + }, +} +- Incompltete Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +Connection from _1 to _2 was not confirmed +# Relations for "test_mut_args_2" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 1, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: { + bb0: {}, +} +- Incompltete Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +# Relations for "test_mut_args_3" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 1, + arg_relations_found: 1, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: { + bb0: { + _4: [ + _5, + ], + }, +} +- Incompltete Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +# Relations for "test_return_regions_static" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: {} +- Incompltete Body: Output(Reference), NotConst , Safe , Sync , Free {NoArguments} + +# Relations for "test_return_regions_non_static" +- Incompltete Stats: BodyStats { + return_relations_signature: 1, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: {} +- Incompltete Body: Output(Reference), NotConst , Safe , Sync , Free {ReturnedStaticLoanForNonStatic} + +# Relations for "test_return_regions_non_static_or_default" +- Incompltete Stats: BodyStats { + return_relations_signature: 1, + return_relations_found: 1, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: { + bb0: {}, +} +- Incompltete Body: Output(Reference), NotConst , Safe , Sync , Free {ReturnedStaticLoanForNonStatic} + +# Relations for "test_return_static_tuple_for_non_static" +- Incompltete Stats: BodyStats { + return_relations_signature: 1, + return_relations_found: 1, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: { + bb0: {}, +} +- Incompltete Body: Output(Tuple) , NotConst , Safe , Sync , Free {ReturnedStaticLoanForNonStatic} + +# Relations for "test_return_static_tuple" +- Incompltete Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} +- Called function relations: { + bb0: {}, +} +- Incompltete Body: Output(Tuple) , NotConst , Safe , Sync , Free {} + diff --git a/src/tools/clippy/tests/ui/thesis/pass/owned.rs b/src/tools/clippy/tests/ui/thesis/pass/owned.rs new file mode 100644 index 0000000000000..d8a20235b74e7 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/owned.rs @@ -0,0 +1,115 @@ +//@rustc-env: CLIPPY_PETS_PRINT=1 +//@rustc-env: CLIPPY_PRINT_MIR=1 + +struct Dropper { + duck: u32, +} + +impl Drop for Dropper { + fn drop(&mut self) {} +} + +struct Animal { + legs: u32, + heads: u32, +} + +// Check arguments are correctly detected +#[warn(clippy::borrow_pats)] +fn take_one(_animal: Animal) {} + +#[warn(clippy::borrow_pats)] +fn take_two(_animal_1: Animal, _animal_2: Animal) {} + +fn take_pair((_animal_1, _animal_2): (Animal, Animal)) {} + +#[warn(clippy::borrow_pats)] +fn pat_return_owned_arg(animal: Animal) -> Animal { + animal +} + +#[warn(clippy::borrow_pats)] +fn pat_maybe_return_owned_arg_1(a: String) -> String { + if !a.is_empty() { + return a; + } + + "hey".to_string() +} + +#[warn(clippy::borrow_pats)] +fn pat_maybe_return_owned_arg_1_test(a: u32) -> u32 { + if !a.is_power_of_two() { + return a; + } + + 19 +} + +#[warn(clippy::borrow_pats)] +/// FIXME: The argument return is not yet detected both in `a` +fn pat_maybe_return_owned_arg_2(a: String) -> String { + let ret; + if !a.is_empty() { + ret = a; + } else { + ret = "hey".to_string(); + println!("{a:#?}"); + } + ret +} + +#[warn(clippy::borrow_pats)] +fn pat_maybe_return_owned_arg_3(a: String) -> String { + let ret = if !a.is_empty() { a } else { "hey".to_string() }; + ret +} + +#[warn(clippy::borrow_pats)] +fn pub_dynamic_drop_1(animal: String, cond: bool) { + if cond { + // Move out of function + std::mem::drop(animal); + } + + magic() +} + +#[warn(clippy::borrow_pats)] +fn conditional_overwrite(mut animal: String, cond: bool) { + if cond { + animal = "Ducks".to_string(); + } + + magic() +} + +fn magic() {} + +#[derive(Default)] +struct Example { + owned_1: String, + owned_2: String, + copy_1: u32, + copy_2: u32, +} + +#[warn(clippy::borrow_pats)] +fn test_ctors() { + let s1 = String::new(); + let _slice = (s1, 2); + + let s1 = String::new(); + let _array = [s1]; + + let s1 = String::new(); + let _thing = Example { + owned_1: s1, + ..Example::default() + }; +} + +#[warn(clippy::borrow_pats)] +fn main() { + let dropper = Dropper { duck: 17 }; +} diff --git a/src/tools/clippy/tests/ui/thesis/pass/owned.stdout b/src/tools/clippy/tests/ui/thesis/pass/owned.stdout new file mode 100644 index 0000000000000..db291a15dd4ab --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/owned.stdout @@ -0,0 +1,54 @@ +# "take_one" +- _animal : (Immutable, Owned , Argument, NonCopy, NonDrop , UserDef ) {} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +# "take_two" +- _animal_1 : (Immutable, Owned , Argument, NonCopy, NonDrop , UserDef ) {} +- _animal_2 : (Immutable, Owned , Argument, NonCopy, NonDrop , UserDef ) {} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +# "pat_return_owned_arg" +- animal : (Immutable, Owned , Argument, NonCopy, NonDrop , UserDef ) {Moved, MovedToReturn} +- Body: Output(UserDef) , NotConst , Safe , Sync , Free {} + +# "pat_maybe_return_owned_arg_1" +- a : (Immutable, Owned , Argument, NonCopy, PartDrop, UserDef ) {Moved, MovedToReturn, Borrow, ArgBorrow} +- Body: Output(UserDef) , NotConst , Safe , Sync , Free {HasTempBorrow} + +# "pat_maybe_return_owned_arg_1_test" +- a : (Immutable, Owned , Argument, Copy , NonDrop , Primitive) {} +- Body: Output(Primitive), NotConst , Safe , Sync , Free {} + +# "pat_maybe_return_owned_arg_2" +- a : (Immutable, Owned , Argument, NonCopy, PartDrop, UserDef ) {DynamicDrop, Moved, MovedToVar, Borrow, ArgBorrow, ArgBorrowExtended, ConditionalMove} +- ret : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {Moved, MovedToReturn, ConditionalInit} +- Body: Output(UserDef) , NotConst , Safe , Sync , Free {HasTempBorrow} + +# "pat_maybe_return_owned_arg_3" +- a : (Immutable, Owned , Argument, NonCopy, PartDrop, UserDef ) {DynamicDrop, Moved, MovedToVar, Borrow, ArgBorrow, ConditionalMove} +- ret : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {Moved, MovedToReturn, ConditionalInit} +- Body: Output(UserDef) , NotConst , Safe , Sync , Free {HasTempBorrow} + +# "pub_dynamic_drop_1" +- animal : (Immutable, Owned , Argument, NonCopy, PartDrop, UserDef ) {DynamicDrop, Moved, MovedToFn, ManualDrop, ConditionalMove} +- cond : (Immutable, Owned , Argument, Copy , NonDrop , Primitive) {} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +# "conditional_overwrite" +- animal : (Mutable , Owned , Argument, NonCopy, PartDrop, UserDef ) {Overwrite, ConditionalOverwride, ConditionalDrop, DropForReplacement} +- cond : (Immutable, Owned , Argument, Copy , NonDrop , Primitive) {} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {} + +# "test_ctors" +- s1 : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {Moved, MovedToCtor} +- _slice : (Immutable, Owned , Local , NonCopy, PartDrop, Tuple ) {} +- s1 : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {Moved, MovedToCtor} +- _array : (Immutable, Owned , Local , NonCopy, PartDrop, Sequence ) {} +- s1 : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {Moved, MovedToCtor} +- _thing : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments} + +# "main" +- dropper : (Immutable, Owned , Local , NonCopy, SelfDrop, UserDef ) {} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments} + diff --git a/src/tools/clippy/tests/ui/thesis/pass/owned_borrows.rs b/src/tools/clippy/tests/ui/thesis/pass/owned_borrows.rs new file mode 100644 index 0000000000000..69ab336fcdf3a --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/owned_borrows.rs @@ -0,0 +1,112 @@ +//@rustc-env: CLIPPY_PETS_PRINT=1 +//@rustc-env: CLIPPY_STATS_PRINT=1 +//@rustc-env: CLIPPY_PRINT_MIR=1 + +#[derive(Default)] +struct Animal { + science_name: String, + simple_name: String, +} + +#[warn(clippy::borrow_pats)] +fn temp_borrow_1(a: String) -> bool { + a.is_empty() +} + +#[warn(clippy::borrow_pats)] +fn temp_borrow_2(a: String) { + take_1_loan(&a); +} + +#[warn(clippy::borrow_pats)] +fn temp_borrow_3(a: String) { + take_2_loan(&a, &a); +} + +#[warn(clippy::borrow_pats)] +fn temp_borrow_mut_1(mut a: String) { + a.clear(); +} + +#[warn(clippy::borrow_pats)] +fn temp_borrow_mut_2(mut a: String) { + take_1_mut_loan(&mut a); +} + +#[warn(clippy::borrow_pats)] +fn temp_borrow_mut_3(mut a: Animal) { + take_2_mut_loan(&mut a.science_name, &mut a.simple_name); +} + +#[warn(clippy::borrow_pats)] +fn temp_borrow_mixed(mut a: String) { + take_1_mut_loan(&mut a); + take_1_loan(&a); +} + +#[warn(clippy::borrow_pats)] +fn temp_borrow_mixed_2(mut a: Animal) { + take_2_mixed_loan(&a.science_name, &mut a.simple_name); +} + +/// https://github.com/nikomatsakis/nll-rfc/issues/37 +#[warn(clippy::borrow_pats)] +fn two_phase_borrow_1(mut vec: Vec) { + vec.push(vec.len()); +} + +#[warn(clippy::borrow_pats)] +fn two_phase_borrow_2(mut num: usize, mut vec: Vec) { + vec.push({ + num = vec.len(); + num + }) +} + +struct NestedVecs { + a: Vec, + b: Vec, +} + +#[warn(clippy::borrow_pats)] +fn nested_two_phase_borrow(mut vecs: NestedVecs) { + vecs.a.push({ + vecs.b.push(vecs.a.len()); + vecs.b.len() + }); +} + +#[warn(clippy::borrow_pats)] +fn test_double_loan() { + let data = "Side effects".to_string(); + take_double_loan(&&data); +} + +#[warn(clippy::borrow_pats)] +fn test_double_mut_loan() { + let mut data = "Can Side effects".to_string(); + take_double_mut_loan(&&mut data); +} + +#[warn(clippy::borrow_pats)] +fn test_mut_double_loan() { + let data = "Can't really have Side effects".to_string(); + take_mut_double_loan(&mut &data); +} + +#[forbid(clippy::borrow_pats)] +fn loan_to_access_part() { + let data = Animal::default(); + take_1_loan(&(&&data).simple_name); +} + +fn take_1_loan(_: &String) {} +fn take_2_loan(_: &String, _: &String) {} +fn take_1_mut_loan(_: &String) {} +fn take_2_mut_loan(_: &mut String, _: &mut String) {} +fn take_2_mixed_loan(_: &String, _: &mut String) {} +fn take_double_loan(_: &&String) {} +fn take_double_mut_loan(_: &&mut String) {} +fn take_mut_double_loan(_: &mut &String) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/thesis/pass/owned_borrows.stdout b/src/tools/clippy/tests/ui/thesis/pass/owned_borrows.stdout new file mode 100644 index 0000000000000..5fa09a97bad62 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/owned_borrows.stdout @@ -0,0 +1,385 @@ +# "temp_borrow_1" +- a : (Immutable, Owned , Argument, NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrow} +- Body: Output(Primitive), NotConst , Safe , Sync , Free {HasTempBorrow} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 1, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + +# "temp_borrow_2" +- a : (Immutable, Owned , Argument, NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrow} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {HasTempBorrow} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 1, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + +# "temp_borrow_3" +- a : (Immutable, Owned , Argument, NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrow, AliasedBorrow} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {HasTempBorrow} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 2, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + +# "temp_borrow_mut_1" +- a : (Mutable , Owned , Argument, NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrowMut} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {HasTempBorrowMut} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 1, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + +# "temp_borrow_mut_2" +- a : (Mutable , Owned , Argument, NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrowMut} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {HasTempBorrowMut} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 1, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + +# "temp_borrow_mut_3" +- a : (Mutable , Owned , Argument, NonCopy, PartDrop, UserDef ) {PartBorrow, PartArgBorrowMut, MultipleMutBorrowsInArgs} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {HasTempBorrowMut} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 2, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + +# "temp_borrow_mixed" +- a : (Mutable , Owned , Argument, NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrow, ArgBorrowMut} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {HasTempBorrow, HasTempBorrowMut} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 1, + arg_borrow_mut_count: 1, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + +# "temp_borrow_mixed_2" +- a : (Mutable , Owned , Argument, NonCopy, PartDrop, UserDef ) {PartBorrow, PartArgBorrow, PartArgBorrowMut, MixedBorrowsInArgs} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {HasTempBorrow, HasTempBorrowMut} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 1, + arg_borrow_mut_count: 1, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + +# "two_phase_borrow_1" +- vec : (Mutable , Owned , Argument, NonCopy, SelfDrop, UserDef ) {Borrow, ArgBorrow, ArgBorrowMut, TwoPhasedBorrow} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {HasTempBorrow, HasTempBorrowMut, HasTwoPhaseBorrow} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 1, + arg_borrow_mut_count: 1, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 1, + }, +} + +# "two_phase_borrow_2" +- num : (Mutable , Owned , Argument, Copy , NonDrop , Primitive) {Overwrite} +- vec : (Mutable , Owned , Argument, NonCopy, SelfDrop, UserDef ) {Borrow, ArgBorrow, ArgBorrowMut, TwoPhasedBorrow} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {HasTempBorrow, HasTempBorrowMut, HasTwoPhaseBorrow} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 1, + arg_borrow_mut_count: 1, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 1, + }, +} + +# "nested_two_phase_borrow" +- vecs : (Mutable , Owned , Argument, NonCopy, PartDrop, UserDef ) {PartBorrow, PartArgBorrow, PartArgBorrowMut, TwoPhasedBorrow} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {HasTempBorrow, HasTempBorrowMut, HasTwoPhaseBorrow} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 2, + arg_borrow_mut_count: 2, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 1, + }, +} + +# "test_double_loan" +- data : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrow} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments, HasTempBorrow} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 1, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + +# "test_double_mut_loan" +- data : (Mutable , Owned , Local , NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrowMut} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments, HasTempBorrowMut} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 1, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + +# "test_mut_double_loan" +- data : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrow} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments, HasTempBorrow} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 1, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + +# "loan_to_access_part" +===== +Body for: DefId(0:20 ~ owned_borrows[a1d0]::loan_to_access_part) +bb0: + StorageLive(_1) + _1 = ::default() -> [return: bb1, unwind continue] + +bb1: + StorageLive(_2) + StorageLive(_3) + StorageLive(_4) + StorageLive(_5) + StorageLive(_6) + _6 = &_1 + _5 = &_6 + _7 = deref_copy (*_5) + _4 = &((*_7).1: std::string::String) + _3 = &(*_4) + _2 = take_1_loan(move _3) -> [return: bb2, unwind: bb4] + +bb2: + StorageDead(_3) + StorageDead(_6) + StorageDead(_5) + StorageDead(_4) + StorageDead(_2) + _0 = const () + drop(_1) -> [return: bb3, unwind: bb5] + +bb3: + StorageDead(_1) + return + +bb4: + drop(_1) -> [return: bb5, unwind terminate(cleanup)] + +bb5: + resume + +===== +- data : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrow} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments, HasTempBorrow} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 1, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + diff --git a/src/tools/clippy/tests/ui/thesis/pass/owned_field.rs b/src/tools/clippy/tests/ui/thesis/pass/owned_field.rs new file mode 100644 index 0000000000000..0c8bbfd070400 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/owned_field.rs @@ -0,0 +1,85 @@ +//@rustc-env: CLIPPY_PETS_PRINT=1 +//@rustc-env: CLIPPY_PRINT_MIR=1 + +#[derive(Default)] +struct Example { + owned_1: String, + owned_2: String, + copy_1: u32, + copy_2: u32, +} + +#[warn(clippy::borrow_pats)] +fn replace_self() { + let mut drop = Example::default(); + drop = Example::default(); + + let mut copy = 15; + copy = 17; +} + +#[warn(clippy::borrow_pats)] +fn conditional_replace_self() { + let mut drop = Example::default(); + if true { + drop = Example::default(); + } + + let mut copy = 15; + if true { + copy = 17; + } +} + +#[warn(clippy::borrow_pats)] +fn assign_copy_field() { + let mut ex = Example::default(); + ex.copy_1 = 10; +} + +#[warn(clippy::borrow_pats)] +fn assign_drop_field() { + let mut ex = Example::default(); + ex.owned_1 = String::new(); +} + +#[warn(clippy::borrow_pats)] +fn move_drop_field_to_var() { + let ex = Example::default(); + let _hey = ex.owned_1; +} + +#[warn(clippy::borrow_pats)] +fn copy_field() { + let ex = Example::default(); + let _hey = ex.copy_1; +} + +#[warn(clippy::borrow_pats)] +fn move_drop_field_as_arg() { + let ex = Example::default(); + take_string(ex.owned_1); +} + +#[warn(clippy::borrow_pats)] +fn return_drop_field() -> String { + let ex = Example::default(); + ex.owned_1 +} + +#[warn(clippy::borrow_pats)] +fn borrow_field_as_arg() { + let ex = Example::default(); + take_string_ref(&ex.owned_1); +} + +#[warn(clippy::borrow_pats)] +fn borrow_field_into_var() { + let ex = Example::default(); + let _hey = &ex.owned_1; +} + +fn take_string(_: String) {} +fn take_string_ref(_: &String) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/thesis/pass/owned_field.stdout b/src/tools/clippy/tests/ui/thesis/pass/owned_field.stdout new file mode 100644 index 0000000000000..6b56e110a82e1 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/owned_field.stdout @@ -0,0 +1,44 @@ +# "replace_self" +- drop : (Mutable , Owned , Local , NonCopy, PartDrop, UserDef ) {Overwrite, DropForReplacement} +- copy : (Mutable , Owned , Local , Copy , NonDrop , Primitive) {Overwrite} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments} + +# "conditional_replace_self" +- drop : (Mutable , Owned , Local , NonCopy, PartDrop, UserDef ) {Overwrite, ConditionalOverwride, ConditionalDrop, DropForReplacement} +- copy : (Mutable , Owned , Local , Copy , NonDrop , Primitive) {Overwrite, ConditionalOverwride} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments} + +# "assign_copy_field" +- ex : (Mutable , Owned , Local , NonCopy, PartDrop, UserDef ) {OverwritePart} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments} + +# "assign_drop_field" +- ex : (Mutable , Owned , Local , NonCopy, PartDrop, UserDef ) {OverwritePart} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments} + +# "move_drop_field_to_var" +- ex : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {PartMoved, PartMovedToVar} +- _hey : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments} + +# "copy_field" +- ex : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {CopiedToVar} +- _hey : (Immutable, Owned , Local , Copy , NonDrop , Primitive) {} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments} + +# "move_drop_field_as_arg" +- ex : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {PartMoved, PartMovedToFn} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments} + +# "return_drop_field" +- ex : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {PartMoved, PartMovedToReturn} +- Body: Output(UserDef) , NotConst , Safe , Sync , Free {NoArguments} + +# "borrow_field_as_arg" +- ex : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {PartBorrow, PartArgBorrow} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments, HasTempBorrow} + +# "borrow_field_into_var" +- ex : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {PartBorrow, PartNamedBorrow} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments, HasNamedBorrow} + diff --git a/src/tools/clippy/tests/ui/thesis/pass/owned_loan_deps.rs b/src/tools/clippy/tests/ui/thesis/pass/owned_loan_deps.rs new file mode 100644 index 0000000000000..17de76118457e --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/owned_loan_deps.rs @@ -0,0 +1,44 @@ +//@rustc-env: CLIPPY_PETS_PRINT=1 +//@rustc-env: CLIPPY_PRINT_MIR=1 + +fn extend_string(data: &String) -> &String { + data +} + +#[warn(clippy::borrow_pats)] +fn call_extend_simple() { + let data = String::new(); + + extend_string(&data).is_empty(); +} + +#[warn(clippy::borrow_pats)] +fn call_extend_named() { + let data = String::new(); + let loan = &data; + + // The extention is not tracked for named loans + extend_string(loan).is_empty(); +} + +fn debug() { + let mut owned_a = String::from("=^.^="); + let owned_b = String::from("=^.^="); + let mut loan; + + loan = &owned_a; + magic(loan); + + if true { + owned_a.push('s'); + } else { + magic(loan); + } + + loan = &owned_b; + magic(loan); +} + +fn magic(_s: &String) {} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/thesis/pass/owned_loan_deps.stdout b/src/tools/clippy/tests/ui/thesis/pass/owned_loan_deps.stdout new file mode 100644 index 0000000000000..31c79b85dcfcb --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/owned_loan_deps.stdout @@ -0,0 +1,8 @@ +# "call_extend_simple" +- data : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrow, ArgBorrowExtended} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments, HasTempBorrow} + +# "call_extend_named" +- data : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {Borrow, NamedBorrow} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments, HasNamedBorrow} + diff --git a/src/tools/clippy/tests/ui/thesis/pass/owned_mut_to_unmut.rs b/src/tools/clippy/tests/ui/thesis/pass/owned_mut_to_unmut.rs new file mode 100644 index 0000000000000..8cedea6851419 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/owned_mut_to_unmut.rs @@ -0,0 +1,62 @@ +//@rustc-env: CLIPPY_PETS_PRINT=1 +//@rustc-env: CLIPPY_PRINT_MIR=1 + +#[warn(clippy::borrow_pats)] +fn mut_move_to_immut() { + let mut x = "Hello World".to_string(); + x.push('x'); + x.clear(); + let x2 = x; + let _ = x2.len(); +} + +#[warn(clippy::borrow_pats)] +fn init_with_mut_block() { + let x2 = { + let mut x = "Hello World".to_string(); + x.push('x'); + x.clear(); + x + }; + let _ = x2.len(); +} + +#[warn(clippy::borrow_pats)] +fn mut_copy_to_immut_shadow() { + let mut counter = 1; + counter += 10; + + let counter = counter; + + let _ = counter; +} + +#[warn(clippy::borrow_pats)] +fn mut_copy_to_immut() { + let mut counter = 1; + counter += 10; + + let snapshot = counter; + + let _ = snapshot; +} + +#[warn(clippy::borrow_pats)] +fn mut_copy_to_immut_and_use_after() { + let mut counter = 1; + counter += 10; + + let snapshot = counter; + + counter += 3; + + let _ = snapshot + counter; +} + +#[warn(clippy::borrow_pats)] +fn main() { + let mut s = String::new(); + s += "Hey"; + + let _ = s; +} diff --git a/src/tools/clippy/tests/ui/thesis/pass/owned_mut_to_unmut.stdout b/src/tools/clippy/tests/ui/thesis/pass/owned_mut_to_unmut.stdout new file mode 100644 index 0000000000000..07811e62f0204 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/owned_mut_to_unmut.stdout @@ -0,0 +1,29 @@ +# "mut_move_to_immut" +- x : (Mutable , Owned , Local , NonCopy, PartDrop, UserDef ) {Moved, MovedToVar, Borrow, ArgBorrowMut, ModMutShadowUnmut} +- x2 : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrow} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments, HasTempBorrow, HasTempBorrowMut} + +# "init_with_mut_block" +- x2 : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrow} +- x : (Mutable , Owned , Local , NonCopy, PartDrop, UserDef ) {Moved, MovedToVar, Borrow, ArgBorrowMut, ModMutShadowUnmut} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments, HasTempBorrow, HasTempBorrowMut} + +# "mut_copy_to_immut_shadow" +- counter : (Mutable , Owned , Local , Copy , NonDrop , Primitive) {CopiedToVar, Overwrite, ModMutShadowUnmut} +- counter : (Immutable, Owned , Local , Copy , NonDrop , Primitive) {} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments} + +# "mut_copy_to_immut" +- counter : (Mutable , Owned , Local , Copy , NonDrop , Primitive) {CopiedToVar, Overwrite} +- snapshot : (Immutable, Owned , Local , Copy , NonDrop , Primitive) {} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments} + +# "mut_copy_to_immut_and_use_after" +- counter : (Mutable , Owned , Local , Copy , NonDrop , Primitive) {CopiedToVar, Overwrite} +- snapshot : (Immutable, Owned , Local , Copy , NonDrop , Primitive) {} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments} + +# "main" +- s : (Mutable , Owned , Local , NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrowMut} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments, HasTempBorrowMut} + diff --git a/src/tools/clippy/tests/ui/thesis/pass/returns.rs b/src/tools/clippy/tests/ui/thesis/pass/returns.rs new file mode 100644 index 0000000000000..1c085244d82a0 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/returns.rs @@ -0,0 +1,30 @@ +//@rustc-env: CLIPPY_PETS_PRINT=1 +//@rustc-env: CLIPPY_PRINT_MIR=1 + +#![warn(clippy::borrow_pats)] + +fn main() {} + +fn simple_const() -> u32 { + 15 +} + +fn fn_call() -> u32 { + simple_const() +} + +fn simple_cond() -> u32 { + if true { 1 } else { 2 } +} + +fn static_slice() -> &'static [u32] { + &[] +} + +fn static_string() -> &'static str { + "Ducks are cool" +} + +fn arg_or_default(arg: &String) -> &str { + if arg.is_empty() { "Default" } else { arg } +} diff --git a/src/tools/clippy/tests/ui/thesis/pass/returns.stdout b/src/tools/clippy/tests/ui/thesis/pass/returns.stdout new file mode 100644 index 0000000000000..1c2ee6e7411d3 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/returns.stdout @@ -0,0 +1,21 @@ +# "main" +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments} + +# "simple_const" +- Body: Output(Primitive), NotConst , Safe , Sync , Free {NoArguments} + +# "fn_call" +- Body: Output(Primitive), NotConst , Safe , Sync , Free {NoArguments} + +# "simple_cond" +- Body: Output(Primitive), NotConst , Safe , Sync , Free {NoArguments} + +# "static_slice" +- Body: Output(Reference), NotConst , Safe , Sync , Free {NoArguments} + +# "static_string" +- Body: Output(Reference), NotConst , Safe , Sync , Free {NoArguments} + +# "arg_or_default" +- Body: Output(Reference), NotConst , Safe , Sync , Free {ReturnedStaticLoanForNonStatic} + diff --git a/src/tools/clippy/tests/ui/thesis/pass/temp_borrow.rs b/src/tools/clippy/tests/ui/thesis/pass/temp_borrow.rs new file mode 100644 index 0000000000000..d6e378becfac5 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/temp_borrow.rs @@ -0,0 +1,65 @@ +//@rustc-env: CLIPPY_PETS_PRINT=1 +//@rustc-env: CLIPPY_STATS_PRINT=1 +//@rustc-env: CLIPPY_PRINT_MIR=1 + +#![warn(clippy::borrow_pats)] + +fn temp_borrow_arg_1(owned: String) { + owned.len(); + owned.is_empty(); +} + +fn temp_borrow_mut_arg_1(mut owned: String) { + owned.pop(); + owned.push('x'); + owned.clear(); +} + +fn temp_borrow_mixed_arg_1(mut owned: String) { + owned.is_empty(); + owned.clear(); + owned.len(); +} + +fn temp_borrow_local_1() { + let owned = String::new(); + owned.len(); + owned.is_empty(); +} + +fn temp_borrow_mut_local_1() { + let mut owned = String::new(); + owned.pop(); + owned.push('x'); + owned.clear(); +} + +fn temp_borrow_mixed_local_1() { + let mut owned = String::new(); + owned.is_empty(); + owned.clear(); + owned.len(); +} + +fn temp_borrow_mixed_mutltiple_1(a: String, mut b: String) { + let c = String::new(); + let mut d = String::new(); + + // TempBorrow + a.is_empty(); + b.is_empty(); + c.is_empty(); + d.is_empty(); + + // TempBorrowMut + b.clear(); + d.clear(); + + // TempBorrow + a.is_empty(); + b.is_empty(); + c.is_empty(); + d.is_empty(); +} + +fn main() {} diff --git a/src/tools/clippy/tests/ui/thesis/pass/temp_borrow.stdout b/src/tools/clippy/tests/ui/thesis/pass/temp_borrow.stdout new file mode 100644 index 0000000000000..9348212595126 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/pass/temp_borrow.stdout @@ -0,0 +1,186 @@ +# "temp_borrow_arg_1" +- owned : (Immutable, Owned , Argument, NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrow} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {HasTempBorrow} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 2, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + +# "temp_borrow_mut_arg_1" +- owned : (Mutable , Owned , Argument, NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrowMut} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {HasTempBorrowMut} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 3, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + +# "temp_borrow_mixed_arg_1" +- owned : (Mutable , Owned , Argument, NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrow, ArgBorrowMut} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {HasTempBorrow, HasTempBorrowMut} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 2, + arg_borrow_mut_count: 1, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + +# "temp_borrow_local_1" +- owned : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrow} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments, HasTempBorrow} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 2, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + +# "temp_borrow_mut_local_1" +- owned : (Mutable , Owned , Local , NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrowMut} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments, HasTempBorrowMut} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 3, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + +# "temp_borrow_mixed_local_1" +- owned : (Mutable , Owned , Local , NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrow, ArgBorrowMut} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments, HasTempBorrow, HasTempBorrowMut} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 2, + arg_borrow_mut_count: 1, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + +# "temp_borrow_mixed_mutltiple_1" +- a : (Immutable, Owned , Argument, NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrow} +- b : (Mutable , Owned , Argument, NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrow, ArgBorrowMut} +- c : (Immutable, Owned , Local , NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrow} +- d : (Mutable , Owned , Local , NonCopy, PartDrop, UserDef ) {Borrow, ArgBorrow, ArgBorrowMut} +- Body: Output(Unit) , NotConst , Safe , Sync , Free {HasTempBorrow, HasTempBorrowMut} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 8, + arg_borrow_mut_count: 2, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + +# "main" +- Body: Output(Unit) , NotConst , Safe , Sync , Free {NoArguments} +- Stats: BodyStats { + return_relations_signature: 0, + return_relations_found: 0, + arg_relations_signature: 0, + arg_relations_found: 0, + arg_relation_possibly_missed_due_generics: 0, + arg_relation_possibly_missed_due_to_late_bounds: 0, + owned: OwnedStats { + arg_borrow_count: 0, + arg_borrow_mut_count: 0, + arg_borrow_extended_count: 0, + arg_borrow_mut_extended_count: 0, + named_borrow_count: 0, + named_borrow_mut_count: 0, + borrowed_for_closure_count: 0, + borrowed_mut_for_closure_count: 0, + two_phased_borrows: 0, + }, +} + diff --git a/src/tools/clippy/tests/ui/thesis/simple_main.rs b/src/tools/clippy/tests/ui/thesis/simple_main.rs new file mode 100644 index 0000000000000..fe6e812e9c4ea --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/simple_main.rs @@ -0,0 +1,58 @@ +//@rustc-env: CLIPPY_PRINT_MIR=1 + +#![allow(unused)] + +fn magic_1(b: &T) {} +fn magic_2(b: &T, c: &T) {} + +fn print_mir() { + let a = if true { + let mut x = Vec::new(); + x.push(1); + x + } else { + let mut y = Vec::new(); + y.push(89); + y + }; +} + +struct A { + field: String, +} + +impl A { + fn borrow_field_direct(&self) -> &String { + &self.field + } + + fn borrow_field_deref(&self) -> &str { + &self.field + } +} + +// fn simple_ownership(cond: bool) { +// let a = String::new(); +// let b = String::new(); +// +// let x; +// if cond { +// x = &a; +// } else { +// x = &b; +// } +// +// magic_1(x); +// } + +// fn if_fun(a: String, b: String, cond: bool) { +// if cond { +// magic_1(&a); +// } else { +// magic_1(&b); +// } +// +// magic_1(if cond {&a} else {&b}); +// } + +fn main() {} diff --git a/src/tools/clippy/tests/ui/thesis/simple_main.stdout b/src/tools/clippy/tests/ui/thesis/simple_main.stdout new file mode 100644 index 0000000000000..7b13bc4529e90 --- /dev/null +++ b/src/tools/clippy/tests/ui/thesis/simple_main.stdout @@ -0,0 +1,1488 @@ +# print_mir + + +## MIR: +bb0: + StorageLive(_1) + StorageLive(_2) + _2 = const true + switchInt(move _2) -> [0: bb5, otherwise: bb1] + +bb1: + StorageLive(_3) + _3 = std::vec::Vec::::new() -> [return: bb2, unwind: bb13] + +bb2: + FakeRead(ForLet(None), _3) + StorageLive(_4) + StorageLive(_5) + _5 = &mut _3 + _4 = std::vec::Vec::::push(move _5, const 1_i32) -> [return: bb3, unwind: bb12] + +bb3: + StorageDead(_5) + StorageDead(_4) + _1 = move _3 + drop(_3) -> [return: bb4, unwind: bb13] + +bb4: + StorageDead(_3) + goto -> bb9 + +bb5: + StorageLive(_6) + _6 = std::vec::Vec::::new() -> [return: bb6, unwind: bb13] + +bb6: + FakeRead(ForLet(None), _6) + StorageLive(_7) + StorageLive(_8) + _8 = &mut _6 + _7 = std::vec::Vec::::push(move _8, const 89_i32) -> [return: bb7, unwind: bb11] + +bb7: + StorageDead(_8) + StorageDead(_7) + _1 = move _6 + drop(_6) -> [return: bb8, unwind: bb13] + +bb8: + StorageDead(_6) + goto -> bb9 + +bb9: + StorageDead(_2) + FakeRead(ForLet(None), _1) + _0 = const () + drop(_1) -> [return: bb10, unwind: bb13] + +bb10: + StorageDead(_1) + return + +bb11: + drop(_6) -> [return: bb13, unwind terminate(cleanup)] + +bb12: + drop(_3) -> [return: bb13, unwind terminate(cleanup)] + +bb13: + resume + + + +## Body: +Body { + basic_blocks: BasicBlocks { + basic_blocks: [ + BasicBlockData { + statements: [ + StorageLive(_1), + StorageLive(_2), + _2 = const true, + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:26:16: 26:20 (#0), + scope: scope[0], + }, + kind: switchInt(move _2) -> [0: bb5, otherwise: bb1], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageLive(_3), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:27:21: 27:31 (#0), + scope: scope[0], + }, + kind: _3 = std::vec::Vec::::new() -> [return: bb2, unwind: bb13], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + FakeRead(ForLet(None), _3), + StorageLive(_4), + StorageLive(_5), + _5 = &mut _3, + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:28:9: 28:18 (#0), + scope: scope[2], + }, + kind: _4 = std::vec::Vec::::push(move _5, const 1_i32) -> [return: bb3, unwind: bb12], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_5), + StorageDead(_4), + _1 = move _3, + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:30:5: 30:6 (#0), + scope: scope[0], + }, + kind: drop(_3) -> [return: bb4, unwind: bb13], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_3), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:26:13: 34:6 (#0), + scope: scope[0], + }, + kind: goto -> bb9, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageLive(_6), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:31:21: 31:31 (#0), + scope: scope[0], + }, + kind: _6 = std::vec::Vec::::new() -> [return: bb6, unwind: bb13], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + FakeRead(ForLet(None), _6), + StorageLive(_7), + StorageLive(_8), + _8 = &mut _6, + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:32:9: 32:19 (#0), + scope: scope[3], + }, + kind: _7 = std::vec::Vec::::push(move _8, const 89_i32) -> [return: bb7, unwind: bb11], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_8), + StorageDead(_7), + _1 = move _6, + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:34:5: 34:6 (#0), + scope: scope[0], + }, + kind: drop(_6) -> [return: bb8, unwind: bb13], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_6), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:26:13: 34:6 (#0), + scope: scope[0], + }, + kind: goto -> bb9, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_2), + FakeRead(ForLet(None), _1), + _0 = const (), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:35:1: 35:2 (#0), + scope: scope[0], + }, + kind: drop(_1) -> [return: bb10, unwind: bb13], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_1), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:35:2: 35:2 (#0), + scope: scope[0], + }, + kind: return, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:34:5: 34:6 (#0), + scope: scope[0], + }, + kind: drop(_6) -> [return: bb13, unwind terminate(cleanup)], + }, + ), + is_cleanup: true, + }, + BasicBlockData { + statements: [], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:30:5: 30:6 (#0), + scope: scope[0], + }, + kind: drop(_3) -> [return: bb13, unwind terminate(cleanup)], + }, + ), + is_cleanup: true, + }, + BasicBlockData { + statements: [], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:25:1: 35:2 (#0), + scope: scope[0], + }, + kind: resume, + }, + ), + is_cleanup: true, + }, + ], + cache: Cache { + predecessors: OnceLock( + , + ), + switch_sources: OnceLock( + , + ), + is_cyclic: OnceLock( + , + ), + reverse_postorder: OnceLock( + , + ), + dominators: OnceLock( + , + ), + }, + }, + phase: Analysis( + Initial, + ), + pass_count: 1, + source: MirSource { + instance: Item( + DefId(0:12 ~ simple_main[0912]::print_mir), + ), + promoted: None, + }, + source_scopes: [ + SourceScopeData { + span: tests/ui/thesis/simple_main.rs:25:1: 35:2 (#0), + parent_scope: None, + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:12 ~ simple_main[0912]::print_mir).0), + safety: Safe, + }, + ), + }, + SourceScopeData { + span: tests/ui/thesis/simple_main.rs:26:5: 35:2 (#0), + parent_scope: Some( + scope[0], + ), + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:12 ~ simple_main[0912]::print_mir).0), + safety: Safe, + }, + ), + }, + SourceScopeData { + span: tests/ui/thesis/simple_main.rs:27:9: 30:6 (#0), + parent_scope: Some( + scope[0], + ), + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:12 ~ simple_main[0912]::print_mir).0), + safety: Safe, + }, + ), + }, + SourceScopeData { + span: tests/ui/thesis/simple_main.rs:31:9: 34:6 (#0), + parent_scope: Some( + scope[0], + ), + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:12 ~ simple_main[0912]::print_mir).0), + safety: Safe, + }, + ), + }, + ], + coroutine: None, + local_decls: [ + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:25:15: 25:15 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + User( + Var( + VarBindingForm { + binding_mode: BindByValue( + Not, + ), + opt_ty_info: None, + opt_match_place: Some( + ( + None, + tests/ui/thesis/simple_main.rs:26:13: 34:6 (#0), + ), + ), + pat_span: tests/ui/thesis/simple_main.rs:26:9: 26:10 (#0), + }, + ), + ), + ), + ty: std::vec::Vec, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:26:9: 26:10 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: bool, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:26:16: 26:20 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + User( + Var( + VarBindingForm { + binding_mode: BindByValue( + Mut, + ), + opt_ty_info: None, + opt_match_place: Some( + ( + None, + tests/ui/thesis/simple_main.rs:27:21: 27:31 (#0), + ), + ), + pat_span: tests/ui/thesis/simple_main.rs:27:13: 27:18 (#0), + }, + ), + ), + ), + ty: std::vec::Vec, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:27:13: 27:18 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:28:9: 28:18 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: &ReErased mut std::vec::Vec, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:28:9: 28:10 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + User( + Var( + VarBindingForm { + binding_mode: BindByValue( + Mut, + ), + opt_ty_info: None, + opt_match_place: Some( + ( + None, + tests/ui/thesis/simple_main.rs:31:21: 31:31 (#0), + ), + ), + pat_span: tests/ui/thesis/simple_main.rs:31:13: 31:18 (#0), + }, + ), + ), + ), + ty: std::vec::Vec, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:31:13: 31:18 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:32:9: 32:19 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: &ReErased mut std::vec::Vec, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:32:9: 32:10 (#0), + scope: scope[0], + }, + }, + ], + user_type_annotations: [ + CanonicalUserTypeAnnotation { + user_ty: Canonical { + value: TypeOf( + DefId(5:7062 ~ alloc[6fc5]::vec::{impl#0}::new), + UserArgs { + args: [ + ^0, + ], + user_self_ty: Some( + UserSelfTy { + impl_def_id: DefId(5:7060 ~ alloc[6fc5]::vec::{impl#0}), + self_ty: std::vec::Vec<^1, ^2>, + }, + ), + }, + ), + max_universe: U0, + variables: [ + CanonicalVarInfo { + kind: Ty( + General( + U0, + ), + ), + }, + CanonicalVarInfo { + kind: Ty( + General( + U0, + ), + ), + }, + CanonicalVarInfo { + kind: Ty( + General( + U0, + ), + ), + }, + ], + }, + span: tests/ui/thesis/simple_main.rs:27:21: 27:29 (#0), + inferred_ty: FnDef( + DefId(5:7062 ~ alloc[6fc5]::vec::{impl#0}::new), + [ + i32, + ], + ), + }, + CanonicalUserTypeAnnotation { + user_ty: Canonical { + value: TypeOf( + DefId(5:7062 ~ alloc[6fc5]::vec::{impl#0}::new), + UserArgs { + args: [ + ^0, + ], + user_self_ty: Some( + UserSelfTy { + impl_def_id: DefId(5:7060 ~ alloc[6fc5]::vec::{impl#0}), + self_ty: std::vec::Vec<^1, ^2>, + }, + ), + }, + ), + max_universe: U0, + variables: [ + CanonicalVarInfo { + kind: Ty( + General( + U0, + ), + ), + }, + CanonicalVarInfo { + kind: Ty( + General( + U0, + ), + ), + }, + CanonicalVarInfo { + kind: Ty( + General( + U0, + ), + ), + }, + ], + }, + span: tests/ui/thesis/simple_main.rs:31:21: 31:29 (#0), + inferred_ty: FnDef( + DefId(5:7062 ~ alloc[6fc5]::vec::{impl#0}::new), + [ + i32, + ], + ), + }, + ], + arg_count: 0, + spread_arg: None, + var_debug_info: [ + a => _1, + x => _3, + y => _6, + ], + span: tests/ui/thesis/simple_main.rs:25:1: 35:2 (#0), + required_consts: [], + is_polymorphic: false, + injection_phase: None, + tainted_by_errors: None, + function_coverage_info: None, +} +# simple_ownership + + +## MIR: +bb0: + StorageLive(_2) + StorageLive(_3) + StorageLive(_4) + _4 = &_1 + _3 = std::vec::Vec::::is_empty(move _4) -> [return: bb1, unwind: bb10] + +bb1: + switchInt(move _3) -> [0: bb3, otherwise: bb2] + +bb2: + StorageDead(_4) + StorageLive(_5) + _5 = const 17_i32 + FakeRead(ForLet(None), _5) + _2 = const () + StorageDead(_5) + goto -> bb4 + +bb3: + StorageDead(_4) + _2 = const () + goto -> bb4 + +bb4: + StorageDead(_3) + StorageDead(_2) + StorageLive(_6) + StorageLive(_7) + _7 = &_1 + _6 = std::vec::Vec::::is_empty(move _7) -> [return: bb5, unwind: bb10] + +bb5: + switchInt(move _6) -> [0: bb7, otherwise: bb6] + +bb6: + StorageDead(_7) + StorageLive(_8) + _8 = const 89_i32 + FakeRead(ForLet(None), _8) + _0 = const () + StorageDead(_8) + goto -> bb8 + +bb7: + StorageDead(_7) + _0 = const () + goto -> bb8 + +bb8: + StorageDead(_6) + drop(_1) -> [return: bb9, unwind: bb11] + +bb9: + return + +bb10: + drop(_1) -> [return: bb11, unwind terminate(cleanup)] + +bb11: + resume + + + +## Run: + + +## Analysis: +BorrowAnalysis { + body: Body { + basic_blocks: BasicBlocks { + basic_blocks: [ + BasicBlockData { + statements: [ + StorageLive(_2), + StorageLive(_3), + StorageLive(_4), + _4 = &_1, + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:38:8: 38:24 (#0), + scope: scope[0], + }, + kind: _3 = std::vec::Vec::::is_empty(move _4) -> [return: bb1, unwind: bb10], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:38:8: 38:24 (#0), + scope: scope[0], + }, + kind: switchInt(move _3) -> [0: bb3, otherwise: bb2], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_4), + StorageLive(_5), + _5 = const 17_i32, + FakeRead(ForLet(None), _5), + _2 = const (), + StorageDead(_5), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:38:5: 40:6 (#0), + scope: scope[0], + }, + kind: goto -> bb4, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_4), + _2 = const (), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:38:5: 40:6 (#0), + scope: scope[0], + }, + kind: goto -> bb4, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_3), + StorageDead(_2), + StorageLive(_6), + StorageLive(_7), + _7 = &_1, + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:42:8: 42:24 (#0), + scope: scope[0], + }, + kind: _6 = std::vec::Vec::::is_empty(move _7) -> [return: bb5, unwind: bb10], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:42:8: 42:24 (#0), + scope: scope[0], + }, + kind: switchInt(move _6) -> [0: bb7, otherwise: bb6], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_7), + StorageLive(_8), + _8 = const 89_i32, + FakeRead(ForLet(None), _8), + _0 = const (), + StorageDead(_8), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:42:5: 44:6 (#0), + scope: scope[0], + }, + kind: goto -> bb8, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_7), + _0 = const (), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:42:5: 44:6 (#0), + scope: scope[0], + }, + kind: goto -> bb8, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [ + StorageDead(_6), + ], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:45:1: 45:2 (#0), + scope: scope[0], + }, + kind: drop(_1) -> [return: bb9, unwind: bb11], + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:45:2: 45:2 (#0), + scope: scope[0], + }, + kind: return, + }, + ), + is_cleanup: false, + }, + BasicBlockData { + statements: [], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:45:1: 45:2 (#0), + scope: scope[0], + }, + kind: drop(_1) -> [return: bb11, unwind terminate(cleanup)], + }, + ), + is_cleanup: true, + }, + BasicBlockData { + statements: [], + terminator: Some( + Terminator { + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:37:1: 45:2 (#0), + scope: scope[0], + }, + kind: resume, + }, + ), + is_cleanup: true, + }, + ], + cache: Cache { + predecessors: OnceLock( + , + ), + switch_sources: OnceLock( + , + ), + is_cyclic: OnceLock( + , + ), + reverse_postorder: OnceLock( + , + ), + dominators: OnceLock( + , + ), + }, + }, + phase: Analysis( + Initial, + ), + pass_count: 1, + source: MirSource { + instance: Item( + DefId(0:13 ~ simple_main[0912]::simple_ownership), + ), + promoted: None, + }, + source_scopes: [ + SourceScopeData { + span: tests/ui/thesis/simple_main.rs:37:1: 45:2 (#0), + parent_scope: None, + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:13 ~ simple_main[0912]::simple_ownership).0), + safety: Safe, + }, + ), + }, + SourceScopeData { + span: tests/ui/thesis/simple_main.rs:39:9: 40:6 (#0), + parent_scope: Some( + scope[0], + ), + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:13 ~ simple_main[0912]::simple_ownership).0), + safety: Safe, + }, + ), + }, + SourceScopeData { + span: tests/ui/thesis/simple_main.rs:43:9: 44:6 (#0), + parent_scope: Some( + scope[0], + ), + inlined: None, + inlined_parent_scope: None, + local_data: Set( + SourceScopeLocalData { + lint_root: HirId(DefId(0:13 ~ simple_main[0912]::simple_ownership).0), + safety: Safe, + }, + ), + }, + ], + coroutine: None, + local_decls: [ + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:37:41: 37:41 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + User( + Var( + VarBindingForm { + binding_mode: BindByValue( + Mut, + ), + opt_ty_info: Some( + tests/ui/thesis/simple_main.rs:37:32: 37:40 (#0), + ), + opt_match_place: Some( + ( + None, + tests/ui/thesis/simple_main.rs:37:21: 37:30 (#0), + ), + ), + pat_span: tests/ui/thesis/simple_main.rs:37:21: 37:30 (#0), + }, + ), + ), + ), + ty: std::vec::Vec, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:37:21: 37:30 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + Boring, + ), + ty: (), + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:38:5: 40:6 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: bool, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:38:8: 38:24 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + Boring, + ), + ty: &ReErased std::vec::Vec, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:38:8: 38:13 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + User( + Var( + VarBindingForm { + binding_mode: BindByValue( + Not, + ), + opt_ty_info: None, + opt_match_place: Some( + ( + None, + tests/ui/thesis/simple_main.rs:39:18: 39:20 (#0), + ), + ), + pat_span: tests/ui/thesis/simple_main.rs:39:13: 39:15 (#0), + }, + ), + ), + ), + ty: i32, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:39:13: 39:15 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + BlockTailTemp( + BlockTailInfo { + tail_result_is_ignored: true, + span: tests/ui/thesis/simple_main.rs:42:5: 44:6 (#0), + }, + ), + ), + ty: bool, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:42:8: 42:24 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Mut, + local_info: Set( + BlockTailTemp( + BlockTailInfo { + tail_result_is_ignored: true, + span: tests/ui/thesis/simple_main.rs:42:5: 44:6 (#0), + }, + ), + ), + ty: &ReErased std::vec::Vec, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:42:8: 42:13 (#0), + scope: scope[0], + }, + }, + LocalDecl { + mutability: Not, + local_info: Set( + User( + Var( + VarBindingForm { + binding_mode: BindByValue( + Not, + ), + opt_ty_info: None, + opt_match_place: Some( + ( + None, + tests/ui/thesis/simple_main.rs:43:18: 43:20 (#0), + ), + ), + pat_span: tests/ui/thesis/simple_main.rs:43:13: 43:15 (#0), + }, + ), + ), + ), + ty: i32, + user_ty: None, + source_info: SourceInfo { + span: tests/ui/thesis/simple_main.rs:43:13: 43:15 (#0), + scope: scope[0], + }, + }, + ], + user_type_annotations: [], + arg_count: 1, + spread_arg: None, + var_debug_info: [ + owned => _1, + _a => _5, + _b => _8, + ], + span: tests/ui/thesis/simple_main.rs:37:1: 45:2 (#0), + required_consts: [], + is_polymorphic: false, + injection_phase: None, + tainted_by_errors: None, + function_coverage_info: None, + }, + current_bb: bb9, + edges: { + bb0: [ + bb1, + bb10, + ], + bb7: [ + bb8, + ], + bb4: [ + bb5, + bb10, + ], + bb1: [ + bb3, + bb2, + ], + bb8: [ + bb9, + bb11, + ], + bb5: [ + bb7, + bb6, + ], + bb2: [ + bb4, + ], + bb9: [], + bb6: [ + bb8, + ], + bb3: [ + bb4, + ], + }, + autos: [ + Automata { + local: _0, + local_kind: Return, + body: PrintPrevent, + state: Todo, + events: [ + Event { + bb: bb6, + local: _0, + kind: Assign( + Const, + ), + }, + Event { + bb: bb7, + local: _0, + kind: Assign( + Const, + ), + }, + ], + best_pet: Unknown, + }, + Automata { + local: _1, + local_kind: UserArg( + "owned", + ), + body: PrintPrevent, + state: Owned( + Dropped, + ), + events: [ + Event { + bb: bb0, + local: _1, + kind: BorrowInto( + Not, + _4, + AnonVar, + ), + }, + Event { + bb: bb4, + local: _1, + kind: BorrowInto( + Not, + _7, + AnonVar, + ), + }, + Event { + bb: bb8, + local: _1, + kind: Owned( + AutoDrop { + replace: false, + }, + ), + }, + ], + best_pet: TempBorrowed, + }, + Automata { + local: _2, + local_kind: AnonVar, + body: PrintPrevent, + state: Owned( + Filled( + Conditional( + [ + bb2, + bb3, + ], + ), + ), + ), + events: [ + Event { + bb: bb2, + local: _2, + kind: Assign( + Const, + ), + }, + Event { + bb: bb3, + local: _2, + kind: Assign( + Const, + ), + }, + ], + best_pet: Unknown, + }, + Automata { + local: _3, + local_kind: AnonVar, + body: PrintPrevent, + state: Owned( + Moved, + ), + events: [ + Event { + bb: bb0, + local: _3, + kind: Assign( + FnRes( + [ + Spanned { + node: move _4, + span: tests/ui/thesis/simple_main.rs:38:8: 38:13 (#0), + }, + ], + ), + ), + }, + Event { + bb: bb1, + local: _3, + kind: Move( + Switch, + ), + }, + ], + best_pet: Unknown, + }, + Automata { + local: _4, + local_kind: AnonVar, + body: PrintPrevent, + state: AnonRef( + Dead, + ), + events: [ + Event { + bb: bb0, + local: _4, + kind: BorrowFrom( + Not, + _1, + ), + }, + Event { + bb: bb0, + local: _4, + kind: Move( + FnArg, + ), + }, + ], + best_pet: Unknown, + }, + Automata { + local: _5, + local_kind: UserVar( + "_a", + ), + body: PrintPrevent, + state: Owned( + Filled( + Single( + bb2, + ), + ), + ), + events: [ + Event { + bb: bb2, + local: _5, + kind: Assign( + Const, + ), + }, + ], + best_pet: Unknown, + }, + Automata { + local: _6, + local_kind: AnonVar, + body: PrintPrevent, + state: Owned( + Moved, + ), + events: [ + Event { + bb: bb4, + local: _6, + kind: Assign( + FnRes( + [ + Spanned { + node: move _7, + span: tests/ui/thesis/simple_main.rs:42:8: 42:13 (#0), + }, + ], + ), + ), + }, + Event { + bb: bb5, + local: _6, + kind: Move( + Switch, + ), + }, + ], + best_pet: Unknown, + }, + Automata { + local: _7, + local_kind: AnonVar, + body: PrintPrevent, + state: AnonRef( + Dead, + ), + events: [ + Event { + bb: bb4, + local: _7, + kind: BorrowFrom( + Not, + _1, + ), + }, + Event { + bb: bb4, + local: _7, + kind: Move( + FnArg, + ), + }, + ], + best_pet: Unknown, + }, + Automata { + local: _8, + local_kind: UserVar( + "_b", + ), + body: PrintPrevent, + state: Owned( + Filled( + Single( + bb6, + ), + ), + ), + events: [ + Event { + bb: bb6, + local: _8, + kind: Assign( + Const, + ), + }, + ], + best_pet: Unknown, + }, + ], + ret_ctn: 1, +} + + +## Results: +| Name | Kind | Pattern | Final State | +|---|---|---|---| +| _0 | Return | Unknown | [Todo] | +| _1 | UserArg("owned") | TempBorrowed | [Owned(Dropped)] | +| _2 | AnonVar | Unknown | [Owned(Filled(Conditional([bb2, bb3])))] | +| _3 | AnonVar | Unknown | [Owned(Moved)] | +| _4 | AnonVar | Unknown | [AnonRef(Dead)] | +| _5 | UserVar("_a") | Unknown | [Owned(Filled(Single(bb2)))] | +| _6 | AnonVar | Unknown | [Owned(Moved)] | +| _7 | AnonVar | Unknown | [AnonRef(Dead)] | +| _8 | UserVar("_b") | Unknown | [Owned(Filled(Single(bb6)))] | diff --git a/src/tools/clippy/tests/workspace.rs b/src/tools/clippy/tests/workspace.rs index 699ab2be199a8..2ea5dec6c0d93 100644 --- a/src/tools/clippy/tests/workspace.rs +++ b/src/tools/clippy/tests/workspace.rs @@ -47,6 +47,7 @@ fn test_module_style_with_dep_in_subdir() { } #[test] +#[ignore] fn test_no_deps_ignores_path_deps_in_workspaces() { if IS_RUSTC_TEST_SUITE { return; diff --git a/src/tools/opt-dist/src/main.rs b/src/tools/opt-dist/src/main.rs index ffb01210e0455..b9abef9022eeb 100644 --- a/src/tools/opt-dist/src/main.rs +++ b/src/tools/opt-dist/src/main.rs @@ -373,7 +373,6 @@ fn main() -> anyhow::Result<()> { "rust-docs-json", "rust-analyzer", "rustc-src", - "clippy", "miri", "rustfmt", ] {