From d57e57ca1ff5abe96aaab94913c5c81c6027f39c Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 18 Apr 2024 21:13:35 -0400 Subject: [PATCH] Implement initial IMPL_TRAIT_OVERCAPTURES lint --- compiler/rustc_lint/messages.ftl | 7 + .../rustc_lint/src/impl_trait_overcaptures.rs | 236 ++++++++++++++++++ compiler/rustc_lint/src/lib.rs | 3 + 3 files changed, 246 insertions(+) create mode 100644 compiler/rustc_lint/src/impl_trait_overcaptures.rs diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 676a7c21841..67b2121e5c0 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -9,6 +9,13 @@ lint_array_into_iter = .use_explicit_into_iter_suggestion = or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value +lint_impl_trait_overcaptures = `{$self_ty}` will capture more lifetimes than possibly intended in edition 2024 + .note = specifically, {$num_captured -> + [one] this lifetime is + *[other] these lifetimes are + } in scope but not mentioned in the type's bounds + .note2 = all lifetimes in scope will be captured by `impl Trait`s in edition 2024 + lint_async_fn_in_trait = use of `async fn` in public traits is discouraged as auto trait bounds cannot be specified .note = you can suppress this lint if you plan to use the trait only in your own code, or do not care about auto traits like `Send` on the `Future` .suggestion = you can alternatively desugar to a normal `fn` that returns `impl Future` and add any desired bounds such as `Send`, but these cannot be relaxed without a breaking API change diff --git a/compiler/rustc_lint/src/impl_trait_overcaptures.rs b/compiler/rustc_lint/src/impl_trait_overcaptures.rs new file mode 100644 index 00000000000..a633f355f29 --- /dev/null +++ b/compiler/rustc_lint/src/impl_trait_overcaptures.rs @@ -0,0 +1,236 @@ +use rustc_data_structures::{fx::FxIndexSet, unord::UnordSet}; +use rustc_errors::LintDiagnostic; +use rustc_hir as hir; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::intravisit; +use rustc_middle::ty::{ + self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, +}; +use rustc_span::Span; + +use crate::fluent_generated as fluent; +use crate::{LateContext, LateLintPass}; + +declare_lint! { + /// UwU + pub IMPL_TRAIT_OVERCAPTURES, + Warn, + "will capture more lifetimes than possibly intended in edition 2024", + @future_incompatible = FutureIncompatibleInfo { + reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024), + reference: "", + }; +} + +declare_lint_pass!( + /// Lint for use of `async fn` in the definition of a publicly-reachable + /// trait. + ImplTraitOvercaptures => [IMPL_TRAIT_OVERCAPTURES] +); + +impl<'tcx> LateLintPass<'tcx> for ImplTraitOvercaptures { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + _: intravisit::FnKind<'tcx>, + _: &'tcx hir::FnDecl<'tcx>, + _: &'tcx hir::Body<'tcx>, + _: Span, + parent_def_id: LocalDefId, + ) { + match cx.tcx.def_kind(parent_def_id) { + DefKind::AssocFn => { + // RPITITs already capture all lifetimes in scope, so skip them. + if matches!( + cx.tcx.def_kind(cx.tcx.local_parent(parent_def_id)), + DefKind::Trait | DefKind::Impl { of_trait: true } + ) { + return; + } + } + DefKind::Fn => { + // All freee functions need to check for overcaptures. + } + DefKind::Closure => return, + kind => { + unreachable!( + "expected function item, found {}", + kind.descr(parent_def_id.to_def_id()) + ) + } + } + + let sig = cx.tcx.fn_sig(parent_def_id).instantiate_identity(); + + let mut in_scope_parameters = FxIndexSet::default(); + let mut current_def_id = Some(parent_def_id.to_def_id()); + while let Some(def_id) = current_def_id { + let generics = cx.tcx.generics_of(def_id); + for param in &generics.params { + in_scope_parameters.insert(param.def_id); + } + current_def_id = generics.parent; + } + + sig.visit_with(&mut VisitOpaqueTypes { + tcx: cx.tcx, + parent_def_id, + in_scope_parameters, + seen: Default::default(), + }); + } +} + +struct VisitOpaqueTypes<'tcx> { + tcx: TyCtxt<'tcx>, + parent_def_id: LocalDefId, + in_scope_parameters: FxIndexSet, + seen: FxIndexSet, +} + +impl<'tcx> TypeVisitor> for VisitOpaqueTypes<'tcx> { + fn visit_binder>>( + &mut self, + t: &ty::Binder<'tcx, T>, + ) -> Self::Result { + let mut added = vec![]; + for arg in t.bound_vars() { + let arg: ty::BoundVariableKind = arg; + match arg { + ty::BoundVariableKind::Region(ty::BoundRegionKind::BrNamed(def_id, ..)) => { + added.push(def_id); + let unique = self.in_scope_parameters.insert(def_id); + assert!(unique); + } + ty::BoundVariableKind::Ty(_) => { + todo!("we don't support late-bound type params in `impl Trait`") + } + ty::BoundVariableKind::Region(..) => { + unreachable!("all AST-derived bound regions should have a name") + } + ty::BoundVariableKind::Const => { + unreachable!("non-lifetime binder consts are not allowed") + } + } + } + + t.super_visit_with(self); + + for arg in added.into_iter().rev() { + self.in_scope_parameters.shift_remove(&arg); + } + } + + fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result { + if !t.has_opaque_types() { + return; + } + + if let ty::Alias(ty::Opaque, opaque_ty) = *t.kind() + && let Some(opaque_def_id) = opaque_ty.def_id.as_local() + && self.seen.insert(opaque_def_id) + && let opaque = + self.tcx.hir_node_by_def_id(opaque_def_id).expect_item().expect_opaque_ty() + && let hir::OpaqueTyOrigin::FnReturn(parent_def_id) = opaque.origin + && parent_def_id == self.parent_def_id + && opaque.precise_capturing_args.is_none() + { + let mut captured = UnordSet::default(); + let variances = self.tcx.variances_of(opaque_def_id); + let mut current_def_id = Some(opaque_def_id.to_def_id()); + while let Some(def_id) = current_def_id { + let generics = self.tcx.generics_of(def_id); + for param in &generics.params { + if variances[param.index as usize] != ty::Invariant { + continue; + } + captured.insert(extract_def_id_from_arg( + self.tcx, + generics, + opaque_ty.args[param.index as usize], + )); + } + current_def_id = generics.parent; + } + + let uncaptured_spans: Vec<_> = self + .in_scope_parameters + .iter() + .filter(|def_id| !captured.contains(def_id)) + .map(|def_id| self.tcx.def_span(def_id)) + .collect(); + + if !uncaptured_spans.is_empty() { + self.tcx.emit_node_lint( + IMPL_TRAIT_OVERCAPTURES, + self.tcx.local_def_id_to_hir_id(opaque_def_id), + ImplTraitOvercapturesLint { + opaque_span: self.tcx.def_span(opaque_def_id), + self_ty: t, + num_captured: uncaptured_spans.len(), + uncaptured_spans, + }, + ); + } + + for clause in + self.tcx.item_bounds(opaque_ty.def_id).iter_instantiated(self.tcx, opaque_ty.args) + { + clause.visit_with(self) + } + } + + t.super_visit_with(self); + } +} + +struct ImplTraitOvercapturesLint<'tcx> { + opaque_span: Span, + uncaptured_spans: Vec, + self_ty: Ty<'tcx>, + num_captured: usize, +} + +impl<'a> LintDiagnostic<'a, ()> for ImplTraitOvercapturesLint<'_> { + fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::Diag<'a, ()>) { + diag.arg("self_ty", self.self_ty.to_string()) + .arg("num_captured", self.num_captured) + .span(self.opaque_span) + .span_note(self.uncaptured_spans, fluent::lint_note) + .note(fluent::lint_note2); + } + + fn msg(&self) -> rustc_errors::DiagMessage { + fluent::lint_impl_trait_overcaptures + } +} + +fn extract_def_id_from_arg<'tcx>( + tcx: TyCtxt<'tcx>, + generics: &'tcx ty::Generics, + arg: ty::GenericArg<'tcx>, +) -> DefId { + match arg.unpack() { + ty::GenericArgKind::Lifetime(re) => match *re { + ty::ReEarlyParam(ebr) => generics.region_param(ebr, tcx).def_id, + ty::ReBound( + _, + ty::BoundRegion { kind: ty::BoundRegionKind::BrNamed(def_id, ..), .. }, + ) => def_id, + _ => unreachable!(), + }, + ty::GenericArgKind::Type(ty) => { + let ty::Param(param_ty) = *ty.kind() else { + bug!(); + }; + generics.type_param(param_ty, tcx).def_id + } + ty::GenericArgKind::Const(ct) => { + let ty::ConstKind::Param(param_ct) = ct.kind() else { + bug!(); + }; + generics.const_param(param_ct, tcx).def_id + } + } +} diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index a78b410f500..d93edadcfbc 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -55,6 +55,7 @@ mod for_loops_over_fallibles; mod foreign_modules; pub mod hidden_unicode_codepoints; +mod impl_trait_overcaptures; mod internal; mod invalid_from_utf8; mod late; @@ -94,6 +95,7 @@ use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums; use for_loops_over_fallibles::*; use hidden_unicode_codepoints::*; +use impl_trait_overcaptures::ImplTraitOvercaptures; use internal::*; use invalid_from_utf8::*; use let_underscore::*; @@ -228,6 +230,7 @@ fn lint_mod(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) { MissingDoc: MissingDoc, AsyncFnInTrait: AsyncFnInTrait, NonLocalDefinitions: NonLocalDefinitions::default(), + ImplTraitOvercaptures: ImplTraitOvercaptures, ] ] );