From 3cddc8bff6d5357fc68a87c802a7f4fa3e1642a7 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 29 Oct 2022 09:56:20 +0100 Subject: [PATCH 01/13] More inference-friendly API for lazy The signature for new was ``` fn new(f: F) -> Lazy ``` Notably, with `F` unconstrained, `T` can be literally anything, and just `let _ = Lazy::new(|| 92)` would not typecheck. This historiacally was a necessity -- `new` is a `const` function, it couldn't have any bounds. Today though, we can move `new` under the `F: FnOnce() -> T` bound, which gives the compiler enough data to infer the type of T from closure. --- library/core/src/cell/lazy.rs | 4 +--- library/core/tests/lazy.rs | 6 ++++++ library/std/src/sync/lazy_lock.rs | 5 +---- library/std/src/sync/lazy_lock/tests.rs | 6 ++++++ 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/library/core/src/cell/lazy.rs b/library/core/src/cell/lazy.rs index 7844be5f783..39209772c4e 100644 --- a/library/core/src/cell/lazy.rs +++ b/library/core/src/cell/lazy.rs @@ -31,7 +31,7 @@ pub struct LazyCell T> { init: Cell>, } -impl LazyCell { +impl T> LazyCell { /// Creates a new lazy value with the given initializing function. /// /// # Examples @@ -51,9 +51,7 @@ impl LazyCell { pub const fn new(init: F) -> LazyCell { LazyCell { cell: OnceCell::new(), init: Cell::new(Some(init)) } } -} -impl T> LazyCell { /// Forces the evaluation of this lazy value and returns a reference to /// the result. /// diff --git a/library/core/tests/lazy.rs b/library/core/tests/lazy.rs index 70fcc6d2d4b..c7c3c479b71 100644 --- a/library/core/tests/lazy.rs +++ b/library/core/tests/lazy.rs @@ -106,6 +106,12 @@ fn lazy_new() { assert_eq!(called.get(), 1); } +// Check that we can infer `T` from closure's type. +#[test] +fn lazy_type_inference() { + let _ = LazyCell::new(|| ()); +} + #[test] fn aliasing_in_get() { let x = OnceCell::new(); diff --git a/library/std/src/sync/lazy_lock.rs b/library/std/src/sync/lazy_lock.rs index 535cc1c42fc..4955975ebde 100644 --- a/library/std/src/sync/lazy_lock.rs +++ b/library/std/src/sync/lazy_lock.rs @@ -44,17 +44,14 @@ pub struct LazyLock T> { cell: OnceLock, init: Cell>, } - -impl LazyLock { +impl T> LazyLock { /// Creates a new lazy value with the given initializing /// function. #[unstable(feature = "once_cell", issue = "74465")] pub const fn new(f: F) -> LazyLock { LazyLock { cell: OnceLock::new(), init: Cell::new(Some(f)) } } -} -impl T> LazyLock { /// Forces the evaluation of this lazy value and /// returns a reference to result. This is equivalent /// to the `Deref` impl, but is explicit. diff --git a/library/std/src/sync/lazy_lock/tests.rs b/library/std/src/sync/lazy_lock/tests.rs index f11b66bfca5..a5d4e25c596 100644 --- a/library/std/src/sync/lazy_lock/tests.rs +++ b/library/std/src/sync/lazy_lock/tests.rs @@ -136,6 +136,12 @@ fn sync_lazy_poisoning() { } } +// Check that we can infer `T` from closure's type. +#[test] +fn lazy_type_inference() { + let _ = LazyCell::new(|| ()); +} + #[test] fn is_sync_send() { fn assert_traits() {} From 94d624500316affdbe893597ee249899991f31a6 Mon Sep 17 00:00:00 2001 From: Lukas Markeffsky <@> Date: Thu, 22 Dec 2022 21:37:21 +0100 Subject: [PATCH 02/13] Fix `explicit_outlives_requirements` lint in macros Show the suggestion if and only if the bounds are from the same source context. --- compiler/rustc_ast_lowering/src/item.rs | 17 +------ compiler/rustc_lint/src/builtin.rs | 50 +++++++++++-------- .../edition-lint-infer-outlives-macro.rs | 43 +++++++++++++++- .../edition-lint-infer-outlives-macro.stderr | 26 +++++++++- 4 files changed, 98 insertions(+), 38 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 9d4c2900eaf..bda914d2888 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -1377,21 +1377,8 @@ impl<'hir> LoweringContext<'_, 'hir> { let ident = self.lower_ident(ident); let param_span = ident.span; - let span = bounds - .iter() - .fold(Some(param_span.shrink_to_hi()), |span: Option, bound| { - let bound_span = bound.span(); - // We include bounds that come from a `#[derive(_)]` but point at the user's code, - // as we use this method to get a span appropriate for suggestions. - if !bound_span.can_be_used_for_suggestions() { - None - } else if let Some(span) = span { - Some(span.to(bound_span)) - } else { - Some(bound_span) - } - }) - .unwrap_or(param_span.shrink_to_hi()); + let span = + bounds.iter().fold(param_span.shrink_to_hi(), |span, bound| span.to(bound.span())); match kind { GenericParamKind::Const { .. } => None, GenericParamKind::Type { .. } => { diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index d6de6e70ead..3fc86545ed4 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -55,7 +55,7 @@ use rustc_session::lint::{BuiltinLintDiagnostics, FutureIncompatibilityReason}; use rustc_span::edition::Edition; use rustc_span::source_map::Spanned; use rustc_span::symbol::{kw, sym, Ident, Symbol}; -use rustc_span::{BytePos, InnerSpan, Span}; +use rustc_span::{BytePos, InnerSpan, Span, SyntaxContext}; use rustc_target::abi::{Abi, VariantIdx}; use rustc_trait_selection::infer::{InferCtxtExt, TyCtxtInferExt}; use rustc_trait_selection::traits::{self, misc::can_type_implement_copy, EvaluationResult}; @@ -2184,6 +2184,7 @@ impl ExplicitOutlivesRequirements { tcx: TyCtxt<'tcx>, bounds: &hir::GenericBounds<'_>, inferred_outlives: &[ty::Region<'tcx>], + span_cx: SyntaxContext, ) -> Vec<(usize, Span)> { use rustc_middle::middle::resolve_lifetime::Region; @@ -2191,23 +2192,28 @@ impl ExplicitOutlivesRequirements { .iter() .enumerate() .filter_map(|(i, bound)| { - if let hir::GenericBound::Outlives(lifetime) = bound { - let is_inferred = match tcx.named_region(lifetime.hir_id) { - Some(Region::EarlyBound(def_id)) => inferred_outlives.iter().any(|r| { - if let ty::ReEarlyBound(ebr) = **r { - ebr.def_id == def_id - } else { - false - } - }), - _ => false, - }; - is_inferred.then_some((i, bound.span())) - } else { - None + let hir::GenericBound::Outlives(lifetime) = bound else { + return None; + }; + + let is_inferred = match tcx.named_region(lifetime.hir_id) { + Some(Region::EarlyBound(def_id)) => inferred_outlives + .iter() + .any(|r| matches!(**r, ty::ReEarlyBound(ebr) if { ebr.def_id == def_id })), + _ => false, + }; + + if !is_inferred { + return None; } + + let span = bound.span(); + if span.ctxt() != span_cx || in_external_macro(tcx.sess, span) { + return None; + } + + Some((i, span)) }) - .filter(|(_, span)| !in_external_macro(tcx.sess, *span)) .collect() } @@ -2312,9 +2318,9 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { // FIXME we can also infer bounds on associated types, // and should check for them here. match predicate.bounded_ty.kind { - hir::TyKind::Path(hir::QPath::Resolved(None, ref path)) => { + hir::TyKind::Path(hir::QPath::Resolved(None, path)) => { let Res::Def(DefKind::TyParam, def_id) = path.res else { - continue + continue; }; let index = ty_generics.param_def_id_to_index[&def_id]; ( @@ -2335,8 +2341,12 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { continue; } - let bound_spans = - self.collect_outlives_bound_spans(cx.tcx, bounds, &relevant_lifetimes); + let bound_spans = self.collect_outlives_bound_spans( + cx.tcx, + bounds, + &relevant_lifetimes, + span.ctxt(), + ); bound_count += bound_spans.len(); let drop_predicate = bound_spans.len() == bounds.len(); diff --git a/src/test/ui/rust-2018/edition-lint-infer-outlives-macro.rs b/src/test/ui/rust-2018/edition-lint-infer-outlives-macro.rs index d7a832831c1..d96def8173a 100644 --- a/src/test/ui/rust-2018/edition-lint-infer-outlives-macro.rs +++ b/src/test/ui/rust-2018/edition-lint-infer-outlives-macro.rs @@ -15,7 +15,11 @@ macro_rules! make_foo { struct Foo<$a, 'b> where 'b: $a { foo: &$a &'b (), } - } + + struct Foo2<$a, 'b: $a> { + foo: &$a &'b (), + } + }; } gimme_a! {make_foo!} @@ -25,4 +29,41 @@ struct Bar<'a, 'b: 'a> { bar: &'a &'b (), } +macro_rules! make_quux { + () => { + struct Quux<'a, 'b> where 'b: 'a { + //~^ ERROR: outlives requirements can be inferred + baz: &'a &'b (), + } + + struct Quux2<'a, 'b: 'a> { + //~^ ERROR: outlives requirements can be inferred + baz: &'a &'b (), + } + }; +} + +make_quux!{} + +macro_rules! make_baz { + () => { + make_baz!{ 'a } + }; + ($a:lifetime) => { + struct Baz<$a, 'b> where 'b: $a { + baz: &$a &'b (), + } + + struct Baz2<$a, 'b: $a> { + baz: &$a &'b (), + } + }; +} + +make_baz!{ 'a } + +mod baz { + make_baz!{} +} + fn main() {} diff --git a/src/test/ui/rust-2018/edition-lint-infer-outlives-macro.stderr b/src/test/ui/rust-2018/edition-lint-infer-outlives-macro.stderr index 553b1cd976a..0dd4985244d 100644 --- a/src/test/ui/rust-2018/edition-lint-infer-outlives-macro.stderr +++ b/src/test/ui/rust-2018/edition-lint-infer-outlives-macro.stderr @@ -1,5 +1,5 @@ error: outlives requirements can be inferred - --> $DIR/edition-lint-infer-outlives-macro.rs:23:18 + --> $DIR/edition-lint-infer-outlives-macro.rs:27:18 | LL | struct Bar<'a, 'b: 'a> { | ^^^^ help: remove this bound @@ -10,5 +10,27 @@ note: the lint level is defined here LL | #![deny(explicit_outlives_requirements)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to previous error +error: outlives requirements can be inferred + --> $DIR/edition-lint-infer-outlives-macro.rs:34:28 + | +LL | struct Quux<'a, 'b> where 'b: 'a { + | ^^^^^^^^^^^^^ help: remove this bound +... +LL | make_quux!{} + | ------------ in this macro invocation + | + = note: this error originates in the macro `make_quux` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: outlives requirements can be inferred + --> $DIR/edition-lint-infer-outlives-macro.rs:39:28 + | +LL | struct Quux2<'a, 'b: 'a> { + | ^^^^ help: remove this bound +... +LL | make_quux!{} + | ------------ in this macro invocation + | + = note: this error originates in the macro `make_quux` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 3 previous errors From 83e653920d694a010fad8c7d87d302c2d5b3a177 Mon Sep 17 00:00:00 2001 From: Lukas Markeffsky <@> Date: Sun, 25 Dec 2022 17:41:31 +0100 Subject: [PATCH 03/13] document that `Span::to` can go backwards --- compiler/rustc_span/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index 2181c090027..bee4b0a2332 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -796,6 +796,9 @@ impl Span { /// Returns a `Span` that would enclose both `self` and `end`. /// + /// Note that this can also be used to extend the span "backwards": + /// `start.to(end)` and `end.to(start)` return the same `Span`. + /// /// ```text /// ____ ___ /// self lorem ipsum end From 1eba6c404fa3e64607fc3826214bd61ae10fa5bb Mon Sep 17 00:00:00 2001 From: Lukas Markeffsky <@> Date: Sun, 25 Dec 2022 22:16:04 +0100 Subject: [PATCH 04/13] address review comments + better tests --- compiler/rustc_ast_lowering/src/item.rs | 17 ++- compiler/rustc_ast_lowering/src/lib.rs | 7 +- compiler/rustc_lint/src/builtin.rs | 114 +++++++------- .../edition-lint-infer-outlives-macro.fixed | 137 +++++++++++++++++ .../edition-lint-infer-outlives-macro.rs | 142 +++++++++++++----- .../edition-lint-infer-outlives-macro.stderr | 106 +++++++++++-- 6 files changed, 415 insertions(+), 108 deletions(-) create mode 100644 src/test/ui/rust-2018/edition-lint-infer-outlives-macro.fixed diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index bda914d2888..294ffec8a5e 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -1316,6 +1316,8 @@ impl<'hir> LoweringContext<'_, 'hir> { param.id, ¶m.kind, ¶m.bounds, + param.colon_span, + generics.span, itctx, PredicateOrigin::GenericParam, ) @@ -1365,6 +1367,8 @@ impl<'hir> LoweringContext<'_, 'hir> { id: NodeId, kind: &GenericParamKind, bounds: &[GenericBound], + colon_span: Option, + parent_span: Span, itctx: &ImplTraitContext, origin: PredicateOrigin, ) -> Option> { @@ -1377,8 +1381,17 @@ impl<'hir> LoweringContext<'_, 'hir> { let ident = self.lower_ident(ident); let param_span = ident.span; - let span = - bounds.iter().fold(param_span.shrink_to_hi(), |span, bound| span.to(bound.span())); + + // Reconstruct the span of the entire predicate from the individual generic bounds. + let span_start = colon_span.unwrap_or_else(|| param_span.shrink_to_hi()); + let span = bounds.iter().fold(span_start, |span_accum, bound| { + match bound.span().find_ancestor_inside(parent_span) { + Some(bound_span) => span_accum.to(bound_span), + None => span_accum, + } + }); + let span = self.lower_span(span); + match kind { GenericParamKind::Const { .. } => None, GenericParamKind::Type { .. } => { diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 0ef784a4453..a2b0f112e3b 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -2247,6 +2247,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { ) -> (hir::GenericParam<'hir>, Option>, hir::TyKind<'hir>) { // Add a definition for the in-band `Param`. let def_id = self.local_def_id(node_id); + let span = self.lower_span(span); // Set the name to `impl Bound1 + Bound2`. let param = hir::GenericParam { @@ -2254,7 +2255,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { def_id, name: ParamName::Plain(self.lower_ident(ident)), pure_wrt_drop: false, - span: self.lower_span(span), + span, kind: hir::GenericParamKind::Type { default: None, synthetic: true }, colon_span: None, }; @@ -2264,6 +2265,8 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { node_id, &GenericParamKind::Type { default: None }, bounds, + /* colon_span */ None, + span, &ImplTraitContext::Universal, hir::PredicateOrigin::ImplTrait, ); @@ -2273,7 +2276,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { let ty = hir::TyKind::Path(hir::QPath::Resolved( None, self.arena.alloc(hir::Path { - span: self.lower_span(span), + span, res, segments: arena_vec![self; hir::PathSegment::new(self.lower_ident(ident), hir_id, res)], diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 3fc86545ed4..cdb901b7f86 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -55,7 +55,7 @@ use rustc_session::lint::{BuiltinLintDiagnostics, FutureIncompatibilityReason}; use rustc_span::edition::Edition; use rustc_span::source_map::Spanned; use rustc_span::symbol::{kw, sym, Ident, Symbol}; -use rustc_span::{BytePos, InnerSpan, Span, SyntaxContext}; +use rustc_span::{BytePos, InnerSpan, Span}; use rustc_target::abi::{Abi, VariantIdx}; use rustc_trait_selection::infer::{InferCtxtExt, TyCtxtInferExt}; use rustc_trait_selection::traits::{self, misc::can_type_implement_copy, EvaluationResult}; @@ -2184,7 +2184,7 @@ impl ExplicitOutlivesRequirements { tcx: TyCtxt<'tcx>, bounds: &hir::GenericBounds<'_>, inferred_outlives: &[ty::Region<'tcx>], - span_cx: SyntaxContext, + predicate_span: Span, ) -> Vec<(usize, Span)> { use rustc_middle::middle::resolve_lifetime::Region; @@ -2207,8 +2207,8 @@ impl ExplicitOutlivesRequirements { return None; } - let span = bound.span(); - if span.ctxt() != span_cx || in_external_macro(tcx.sess, span) { + let span = bound.span().find_ancestor_inside(predicate_span)?; + if in_external_macro(tcx.sess, span) { return None; } @@ -2279,9 +2279,9 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { use rustc_middle::middle::resolve_lifetime::Region; let def_id = item.owner_id.def_id; - if let hir::ItemKind::Struct(_, ref hir_generics) - | hir::ItemKind::Enum(_, ref hir_generics) - | hir::ItemKind::Union(_, ref hir_generics) = item.kind + if let hir::ItemKind::Struct(_, hir_generics) + | hir::ItemKind::Enum(_, hir_generics) + | hir::ItemKind::Union(_, hir_generics) = item.kind { let inferred_outlives = cx.tcx.inferred_outlives_of(def_id); if inferred_outlives.is_empty() { @@ -2296,47 +2296,48 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { let mut dropped_predicate_count = 0; let num_predicates = hir_generics.predicates.len(); for (i, where_predicate) in hir_generics.predicates.iter().enumerate() { - let (relevant_lifetimes, bounds, span, in_where_clause) = match where_predicate { - hir::WherePredicate::RegionPredicate(predicate) => { - if let Some(Region::EarlyBound(region_def_id)) = - cx.tcx.named_region(predicate.lifetime.hir_id) - { - ( - Self::lifetimes_outliving_lifetime( - inferred_outlives, - region_def_id, - ), - &predicate.bounds, - predicate.span, - predicate.in_where_clause, - ) - } else { - continue; - } - } - hir::WherePredicate::BoundPredicate(predicate) => { - // FIXME we can also infer bounds on associated types, - // and should check for them here. - match predicate.bounded_ty.kind { - hir::TyKind::Path(hir::QPath::Resolved(None, path)) => { - let Res::Def(DefKind::TyParam, def_id) = path.res else { - continue; - }; - let index = ty_generics.param_def_id_to_index[&def_id]; + let (relevant_lifetimes, bounds, predicate_span, in_where_clause) = + match where_predicate { + hir::WherePredicate::RegionPredicate(predicate) => { + if let Some(Region::EarlyBound(region_def_id)) = + cx.tcx.named_region(predicate.lifetime.hir_id) + { ( - Self::lifetimes_outliving_type(inferred_outlives, index), + Self::lifetimes_outliving_lifetime( + inferred_outlives, + region_def_id, + ), &predicate.bounds, predicate.span, - predicate.origin == PredicateOrigin::WhereClause, + predicate.in_where_clause, ) - } - _ => { + } else { continue; } } - } - _ => continue, - }; + hir::WherePredicate::BoundPredicate(predicate) => { + // FIXME we can also infer bounds on associated types, + // and should check for them here. + match predicate.bounded_ty.kind { + hir::TyKind::Path(hir::QPath::Resolved(None, path)) => { + let Res::Def(DefKind::TyParam, def_id) = path.res else { + continue; + }; + let index = ty_generics.param_def_id_to_index[&def_id]; + ( + Self::lifetimes_outliving_type(inferred_outlives, index), + &predicate.bounds, + predicate.span, + predicate.origin == PredicateOrigin::WhereClause, + ) + } + _ => { + continue; + } + } + } + _ => continue, + }; if relevant_lifetimes.is_empty() { continue; } @@ -2345,7 +2346,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { cx.tcx, bounds, &relevant_lifetimes, - span.ctxt(), + predicate_span, ); bound_count += bound_spans.len(); @@ -2355,15 +2356,15 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { } if drop_predicate && !in_where_clause { - lint_spans.push(span); + lint_spans.push(predicate_span); } else if drop_predicate && i + 1 < num_predicates { // If all the bounds on a predicate were inferable and there are // further predicates, we want to eat the trailing comma. let next_predicate_span = hir_generics.predicates[i + 1].span(); - where_lint_spans.push(span.to(next_predicate_span.shrink_to_lo())); + where_lint_spans.push(predicate_span.to(next_predicate_span.shrink_to_lo())); } else { where_lint_spans.extend(self.consolidate_outlives_bound_spans( - span.shrink_to_lo(), + predicate_span.shrink_to_lo(), bounds, bound_spans, )); @@ -2384,12 +2385,26 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { } else { hir_generics.span.shrink_to_hi().to(where_span) }; - lint_spans.push(full_where_span); + + // Due to macro expansions, the `full_where_span` might not actually contain all predicates. + if where_lint_spans.iter().all(|&sp| full_where_span.contains(sp)) { + lint_spans.push(full_where_span); + } else { + lint_spans.extend(where_lint_spans); + } } else { lint_spans.extend(where_lint_spans); } if !lint_spans.is_empty() { + // Do not automatically delete outlives requirements from macros. + let applicability = if lint_spans.iter().all(|sp| sp.can_be_used_for_suggestions()) + { + Applicability::MachineApplicable + } else { + Applicability::MaybeIncorrect + }; + cx.struct_span_lint( EXPLICIT_OUTLIVES_REQUIREMENTS, lint_spans.clone(), @@ -2397,11 +2412,8 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { |lint| { lint.set_arg("count", bound_count).multipart_suggestion( fluent::suggestion, - lint_spans - .into_iter() - .map(|span| (span, String::new())) - .collect::>(), - Applicability::MachineApplicable, + lint_spans.into_iter().map(|span| (span, String::new())).collect(), + applicability, ) }, ); diff --git a/src/test/ui/rust-2018/edition-lint-infer-outlives-macro.fixed b/src/test/ui/rust-2018/edition-lint-infer-outlives-macro.fixed new file mode 100644 index 00000000000..8cdb08e81b9 --- /dev/null +++ b/src/test/ui/rust-2018/edition-lint-infer-outlives-macro.fixed @@ -0,0 +1,137 @@ +// edition:2018 +// aux-build:edition-lint-infer-outlives-macro.rs +// run-rustfix + +#![deny(explicit_outlives_requirements)] +#![allow(dead_code)] + +#[macro_use] +extern crate edition_lint_infer_outlives_macro; + +// Test that the lint does not fire if the predicate is from the local crate, +// but all the bounds are from an external macro. +macro_rules! make_foo { + ($a:tt) => { + struct Foo<$a, 'b: $a> { + foo: &$a &'b (), + } + + struct FooWhere<$a, 'b> where 'b: $a { + foo: &$a &'b (), + } + } +} + +gimme_a! {make_foo!} + +struct Bar<'a, 'b> { + //~^ ERROR: outlives requirements can be inferred + bar: &'a &'b (), +} + +struct BarWhere<'a, 'b> { + //~^ ERROR: outlives requirements can be inferred + bar: &'a &'b (), +} + +// Test that the lint *does* fire if the predicate is contained in a local macro. +mod everything_inside { + macro_rules! m { + ('b: 'a) => { + struct Foo<'a, 'b>(&'a &'b ()); + //~^ ERROR: outlives requirements can be inferred + struct Bar<'a, 'b>(&'a &'b ()) ; + //~^ ERROR: outlives requirements can be inferred + struct Baz<'a, 'b>(&'a &'b ()) where (): Sized, ; + //~^ ERROR: outlives requirements can be inferred + }; + } + m!('b: 'a); +} + +mod inner_lifetime_outside_colon_inside { + macro_rules! m { + ($b:lifetime: 'a) => { + struct Foo<'a, $b>(&'a &$b ()); + //~^ ERROR: outlives requirements can be inferred + struct Bar<'a, $b>(&'a &$b ()) ; + //~^ ERROR: outlives requirements can be inferred + struct Baz<'a, $b>(&'a &$b ()) where (): Sized, ; + //~^ ERROR: outlives requirements can be inferred + } + } + m!('b: 'a); +} + +mod outer_lifetime_outside_colon_inside { + macro_rules! m { + ('b: $a:lifetime) => { + struct Foo<$a, 'b: $a>(&$a &'b ()); + struct Bar<$a, 'b>(&$a &'b ()) where 'b: $a; + struct Baz<$a, 'b>(&$a &'b ()) where (): Sized, 'b: $a; + } + } + m!('b: 'a); +} + +mod both_lifetimes_outside_colon_inside { + macro_rules! m { + ($b:lifetime: $a:lifetime) => { + struct Foo<$a, $b: $a>(&$a &$b ()); + struct Bar<$a, $b>(&$a &$b ()) where $b: $a; + struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b: $a; + } + } + m!('b: 'a); +} + +mod everything_outside { + macro_rules! m { + ($b:lifetime $colon:tt $a:lifetime) => { + struct Foo<$a, $b $colon $a>(&$a &$b ()); + struct Bar<$a, $b>(&$a &$b ()) where $b $colon $a; + struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b $colon $a; + } + } + m!('b: 'a); +} + +mod everything_outside_with_tt_inner { + macro_rules! m { + ($b:tt $colon:tt $a:lifetime) => { + struct Foo<$a, $b $colon $a>(&$a &$b ()); + struct Bar<$a, $b>(&$a &$b ()) where $b $colon $a; + struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b $colon $a; + } + } + m!('b: 'a); +} + +// FIXME: These should be consistent. +mod everything_outside_with_tt_outer { + macro_rules! m { + ($b:lifetime $colon:tt $a:tt) => { + struct Foo<$a, $b >(&$a &$b ()); + //~^ ERROR: outlives requirements can be inferred + struct Bar<$a, $b>(&$a &$b ()) where $b $colon $a; + struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b $colon $a; + } + } + m!('b: 'a); +} + +mod everything_outside_with_tt_both { + macro_rules! m { + ($b:tt $colon:tt $a:tt) => { + struct Foo<$a, $b >(&$a &$b ()); + //~^ ERROR: outlives requirements can be inferred + struct Bar<$a, $b>(&$a &$b ()) where ; + //~^ ERROR: outlives requirements can be inferred + struct Baz<$a, $b>(&$a &$b ()) where (): Sized, ; + //~^ ERROR: outlives requirements can be inferred + } + } + m!('b: 'a); +} + +fn main() {} diff --git a/src/test/ui/rust-2018/edition-lint-infer-outlives-macro.rs b/src/test/ui/rust-2018/edition-lint-infer-outlives-macro.rs index d96def8173a..647906c2dc2 100644 --- a/src/test/ui/rust-2018/edition-lint-infer-outlives-macro.rs +++ b/src/test/ui/rust-2018/edition-lint-infer-outlives-macro.rs @@ -1,25 +1,25 @@ // edition:2018 // aux-build:edition-lint-infer-outlives-macro.rs - -// Test that the lint does not fire if the where predicate -// is from the local crate, but all the bounds are from an -// external macro. +// run-rustfix #![deny(explicit_outlives_requirements)] +#![allow(dead_code)] #[macro_use] extern crate edition_lint_infer_outlives_macro; +// Test that the lint does not fire if the predicate is from the local crate, +// but all the bounds are from an external macro. macro_rules! make_foo { ($a:tt) => { - struct Foo<$a, 'b> where 'b: $a { + struct Foo<$a, 'b: $a> { foo: &$a &'b (), } - struct Foo2<$a, 'b: $a> { + struct FooWhere<$a, 'b> where 'b: $a { foo: &$a &'b (), } - }; + } } gimme_a! {make_foo!} @@ -29,41 +29,109 @@ struct Bar<'a, 'b: 'a> { bar: &'a &'b (), } -macro_rules! make_quux { - () => { - struct Quux<'a, 'b> where 'b: 'a { - //~^ ERROR: outlives requirements can be inferred - baz: &'a &'b (), - } - - struct Quux2<'a, 'b: 'a> { - //~^ ERROR: outlives requirements can be inferred - baz: &'a &'b (), - } - }; +struct BarWhere<'a, 'b> where 'b: 'a { + //~^ ERROR: outlives requirements can be inferred + bar: &'a &'b (), } -make_quux!{} - -macro_rules! make_baz { - () => { - make_baz!{ 'a } - }; - ($a:lifetime) => { - struct Baz<$a, 'b> where 'b: $a { - baz: &$a &'b (), - } - - struct Baz2<$a, 'b: $a> { - baz: &$a &'b (), - } - }; +// Test that the lint *does* fire if the predicate is contained in a local macro. +mod everything_inside { + macro_rules! m { + ('b: 'a) => { + struct Foo<'a, 'b: 'a>(&'a &'b ()); + //~^ ERROR: outlives requirements can be inferred + struct Bar<'a, 'b>(&'a &'b ()) where 'b: 'a; + //~^ ERROR: outlives requirements can be inferred + struct Baz<'a, 'b>(&'a &'b ()) where (): Sized, 'b: 'a; + //~^ ERROR: outlives requirements can be inferred + }; + } + m!('b: 'a); } -make_baz!{ 'a } +mod inner_lifetime_outside_colon_inside { + macro_rules! m { + ($b:lifetime: 'a) => { + struct Foo<'a, $b: 'a>(&'a &$b ()); + //~^ ERROR: outlives requirements can be inferred + struct Bar<'a, $b>(&'a &$b ()) where $b: 'a; + //~^ ERROR: outlives requirements can be inferred + struct Baz<'a, $b>(&'a &$b ()) where (): Sized, $b: 'a; + //~^ ERROR: outlives requirements can be inferred + } + } + m!('b: 'a); +} -mod baz { - make_baz!{} +mod outer_lifetime_outside_colon_inside { + macro_rules! m { + ('b: $a:lifetime) => { + struct Foo<$a, 'b: $a>(&$a &'b ()); + struct Bar<$a, 'b>(&$a &'b ()) where 'b: $a; + struct Baz<$a, 'b>(&$a &'b ()) where (): Sized, 'b: $a; + } + } + m!('b: 'a); +} + +mod both_lifetimes_outside_colon_inside { + macro_rules! m { + ($b:lifetime: $a:lifetime) => { + struct Foo<$a, $b: $a>(&$a &$b ()); + struct Bar<$a, $b>(&$a &$b ()) where $b: $a; + struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b: $a; + } + } + m!('b: 'a); +} + +mod everything_outside { + macro_rules! m { + ($b:lifetime $colon:tt $a:lifetime) => { + struct Foo<$a, $b $colon $a>(&$a &$b ()); + struct Bar<$a, $b>(&$a &$b ()) where $b $colon $a; + struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b $colon $a; + } + } + m!('b: 'a); +} + +mod everything_outside_with_tt_inner { + macro_rules! m { + ($b:tt $colon:tt $a:lifetime) => { + struct Foo<$a, $b $colon $a>(&$a &$b ()); + struct Bar<$a, $b>(&$a &$b ()) where $b $colon $a; + struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b $colon $a; + } + } + m!('b: 'a); +} + +// FIXME: These should be consistent. +mod everything_outside_with_tt_outer { + macro_rules! m { + ($b:lifetime $colon:tt $a:tt) => { + struct Foo<$a, $b $colon $a>(&$a &$b ()); + //~^ ERROR: outlives requirements can be inferred + struct Bar<$a, $b>(&$a &$b ()) where $b $colon $a; + struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b $colon $a; + } + } + m!('b: 'a); +} + +mod everything_outside_with_tt_both { + macro_rules! m { + ($b:tt $colon:tt $a:tt) => { + struct Foo<$a, $b $colon $a>(&$a &$b ()); + //~^ ERROR: outlives requirements can be inferred + struct Bar<$a, $b>(&$a &$b ()) where $b $colon $a; + //~^ ERROR: outlives requirements can be inferred + struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b $colon $a; + //~^ ERROR: outlives requirements can be inferred + } + } + m!('b: 'a); } fn main() {} diff --git a/src/test/ui/rust-2018/edition-lint-infer-outlives-macro.stderr b/src/test/ui/rust-2018/edition-lint-infer-outlives-macro.stderr index 0dd4985244d..734ae687978 100644 --- a/src/test/ui/rust-2018/edition-lint-infer-outlives-macro.stderr +++ b/src/test/ui/rust-2018/edition-lint-infer-outlives-macro.stderr @@ -5,32 +5,106 @@ LL | struct Bar<'a, 'b: 'a> { | ^^^^ help: remove this bound | note: the lint level is defined here - --> $DIR/edition-lint-infer-outlives-macro.rs:8:9 + --> $DIR/edition-lint-infer-outlives-macro.rs:5:9 | LL | #![deny(explicit_outlives_requirements)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: outlives requirements can be inferred - --> $DIR/edition-lint-infer-outlives-macro.rs:34:28 + --> $DIR/edition-lint-infer-outlives-macro.rs:32:24 | -LL | struct Quux<'a, 'b> where 'b: 'a { - | ^^^^^^^^^^^^^ help: remove this bound -... -LL | make_quux!{} - | ------------ in this macro invocation - | - = note: this error originates in the macro `make_quux` (in Nightly builds, run with -Z macro-backtrace for more info) +LL | struct BarWhere<'a, 'b> where 'b: 'a { + | ^^^^^^^^^^^^^ help: remove this bound error: outlives requirements can be inferred - --> $DIR/edition-lint-infer-outlives-macro.rs:39:28 + --> $DIR/edition-lint-infer-outlives-macro.rs:41:30 | -LL | struct Quux2<'a, 'b: 'a> { - | ^^^^ help: remove this bound +LL | struct Foo<'a, 'b: 'a>(&'a &'b ()); + | ^^^^ help: remove this bound ... -LL | make_quux!{} - | ------------ in this macro invocation +LL | m!('b: 'a); + | ---------- in this macro invocation | - = note: this error originates in the macro `make_quux` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 3 previous errors +error: outlives requirements can be inferred + --> $DIR/edition-lint-infer-outlives-macro.rs:43:44 + | +LL | struct Bar<'a, 'b>(&'a &'b ()) where 'b: 'a; + | ^^^^^^^^^^^^ help: remove this bound +... +LL | m!('b: 'a); + | ---------- in this macro invocation + | + = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: outlives requirements can be inferred + --> $DIR/edition-lint-infer-outlives-macro.rs:45:61 + | +LL | struct Baz<'a, 'b>(&'a &'b ()) where (): Sized, 'b: 'a; + | ^^^^^^ help: remove this bound +... +LL | m!('b: 'a); + | ---------- in this macro invocation + | + = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: outlives requirements can be inferred + --> $DIR/edition-lint-infer-outlives-macro.rs:55:30 + | +LL | struct Foo<'a, $b: 'a>(&'a &$b ()); + | ^^^^ help: remove this bound +... +LL | m!('b: 'a); + | ---------- in this macro invocation + | + = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: outlives requirements can be inferred + --> $DIR/edition-lint-infer-outlives-macro.rs:57:44 + | +LL | struct Bar<'a, $b>(&'a &$b ()) where $b: 'a; + | ^^^^^^^^^^^^ help: remove this bound +... +LL | m!('b: 'a); + | ---------- in this macro invocation + | + = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: outlives requirements can be inferred + --> $DIR/edition-lint-infer-outlives-macro.rs:59:61 + | +LL | struct Baz<'a, $b>(&'a &$b ()) where (): Sized, $b: 'a; + | ^^^^^^ help: remove this bound +... +LL | m!('b: 'a); + | ---------- in this macro invocation + | + = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: outlives requirements can be inferred + --> $DIR/edition-lint-infer-outlives-macro.rs:114:31 + | +LL | struct Foo<$a, $b $colon $a>(&$a &$b ()); + | ^^^^^^^^^ help: remove this bound + +error: outlives requirements can be inferred + --> $DIR/edition-lint-infer-outlives-macro.rs:126:31 + | +LL | struct Foo<$a, $b $colon $a>(&$a &$b ()); + | ^^^^^^^^^ help: remove this bound + +error: outlives requirements can be inferred + --> $DIR/edition-lint-infer-outlives-macro.rs:128:50 + | +LL | struct Bar<$a, $b>(&$a &$b ()) where $b $colon $a; + | ^^^^^^^^^^^^ help: remove this bound + +error: outlives requirements can be inferred + --> $DIR/edition-lint-infer-outlives-macro.rs:130:61 + | +LL | struct Baz<$a, $b>(&$a &$b ()) where (): Sized, $b $colon $a; + | ^^^^^^^^^^^^ help: remove this bound + +error: aborting due to 12 previous errors From c9381fc334cb8db62fdb5a8f75807ebdff3d6e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Thu, 15 Dec 2022 16:24:42 -0800 Subject: [PATCH 05/13] Detect likely `.` -> `..` typo in method calls Fix #65015. --- compiler/rustc_hir_typeck/src/coercion.rs | 1 + compiler/rustc_hir_typeck/src/demand.rs | 42 +++++++++++++++++ compiler/rustc_resolve/src/late.rs | 32 ++++++++++--- .../method-access-to-range-literal-typo.rs | 22 +++++++++ ...method-access-to-range-literal-typo.stderr | 45 +++++++++++++++++++ 5 files changed, 135 insertions(+), 7 deletions(-) create mode 100644 src/test/ui/suggestions/method-access-to-range-literal-typo.rs create mode 100644 src/test/ui/suggestions/method-access-to-range-literal-typo.stderr diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index b0cd4a16e98..706700bc1f4 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -1604,6 +1604,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { None, Some(coercion_error), ); + fcx.check_for_range_as_method_call(&mut err, expr, found, expected); } if visitor.ret_exprs.len() > 0 && let Some(expr) = expression { diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index 042ff0b46a5..4352c50358f 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -1448,4 +1448,46 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { _ => false, } } + + pub fn check_for_range_as_method_call( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + checked_ty: Ty<'tcx>, + // FIXME: We should do analysis to see if we can synthesize an expresion that produces + // this type for always accurate suggestions, or at least marking the suggestion as + // machine applicable. + expected_ty: Ty<'tcx>, + ) { + if !hir::is_range_literal(expr) { + return; + } + let hir::ExprKind::Struct( + hir::QPath::LangItem(LangItem::Range, ..), + [start, end], + _, + ) = expr.kind else { return; }; + let mut expr = end.expr; + while let hir::ExprKind::MethodCall(_, rcvr, ..) = expr.kind { + // Getting to the root receiver and asserting it is a fn call let's us ignore cases in + // `src/test/ui/methods/issues/issue-90315.stderr`. + expr = rcvr; + } + let hir::ExprKind::Call(..) = expr.kind else { return; }; + let ty::Adt(adt, _) = checked_ty.kind() else { return; }; + if self.tcx.lang_items().range_struct() != Some(adt.did()) { + return; + } + if let ty::Adt(adt, _) = expected_ty.kind() + && self.tcx.lang_items().range_struct() == Some(adt.did()) + { + return; + } + err.span_suggestion_verbose( + start.expr.span.between(end.expr.span), + "you might have meant to write a method call instead of a range", + ".".to_string(), + Applicability::MaybeIncorrect, + ); + } } diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 5b7a00101e9..13b001af7ea 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -16,7 +16,7 @@ use rustc_ast::ptr::P; use rustc_ast::visit::{self, AssocCtxt, BoundKind, FnCtxt, FnKind, Visitor}; use rustc_ast::*; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap}; -use rustc_errors::{DiagnosticArgValue, DiagnosticId, IntoDiagnosticArg}; +use rustc_errors::{Applicability, DiagnosticArgValue, DiagnosticId, IntoDiagnosticArg}; use rustc_hir::def::Namespace::{self, *}; use rustc_hir::def::{self, CtorKind, DefKind, LifetimeRes, PartialRes, PerNS}; use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID, LOCAL_CRATE}; @@ -536,6 +536,9 @@ struct DiagnosticMetadata<'ast> { in_assignment: Option<&'ast Expr>, is_assign_rhs: bool, + /// Used to detect possible `.` -> `..` typo when calling methods. + in_range: Option<(&'ast Expr, &'ast Expr)>, + /// If we are currently in a trait object definition. Used to point at the bounds when /// encountering a struct or enum. current_trait_object: Option<&'ast [ast::GenericBound]>, @@ -3320,6 +3323,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { ); } + #[instrument(level = "debug", skip(self))] fn smart_resolve_path_fragment( &mut self, qself: &Option>, @@ -3327,10 +3331,6 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { source: PathSource<'ast>, finalize: Finalize, ) -> PartialRes { - debug!( - "smart_resolve_path_fragment(qself={:?}, path={:?}, finalize={:?})", - qself, path, finalize, - ); let ns = source.namespace(); let Finalize { node_id, path_span, .. } = finalize; @@ -3341,8 +3341,20 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { let def_id = this.parent_scope.module.nearest_parent_mod(); let instead = res.is_some(); - let suggestion = - if res.is_none() { this.report_missing_type_error(path) } else { None }; + let suggestion = if let Some((start, end)) = this.diagnostic_metadata.in_range + && path[0].ident.span.lo() == end.span.lo() + { + Some(( + start.span.between(end.span), + "you might have meant to write a method call instead of a range", + ".".to_string(), + Applicability::MaybeIncorrect, + )) + } else if res.is_none() { + this.report_missing_type_error(path) + } else { + None + }; this.r.use_injections.push(UseError { err, @@ -4005,6 +4017,12 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { self.visit_expr(rhs); self.diagnostic_metadata.is_assign_rhs = false; } + ExprKind::Range(Some(ref start), Some(ref end), RangeLimits::HalfOpen) => { + self.diagnostic_metadata.in_range = Some((start, end)); + self.resolve_expr(start, Some(expr)); + self.resolve_expr(end, Some(expr)); + self.diagnostic_metadata.in_range = None; + } _ => { visit::walk_expr(self, expr); } diff --git a/src/test/ui/suggestions/method-access-to-range-literal-typo.rs b/src/test/ui/suggestions/method-access-to-range-literal-typo.rs new file mode 100644 index 00000000000..545f9c597fd --- /dev/null +++ b/src/test/ui/suggestions/method-access-to-range-literal-typo.rs @@ -0,0 +1,22 @@ +fn as_ref() -> Option> { + None +} +struct Type { + option: Option> +} + +impl Type { + fn method(&self) -> Option> { + self.option..as_ref().map(|x| x) + //~^ ERROR E0308 + } + fn method2(&self) -> Option> { + self.option..foo().map(|x| x) + //~^ ERROR E0425 + //~| ERROR E0308 + } +} + +fn main() { + let _ = Type { option: None }.method(); +} diff --git a/src/test/ui/suggestions/method-access-to-range-literal-typo.stderr b/src/test/ui/suggestions/method-access-to-range-literal-typo.stderr new file mode 100644 index 00000000000..becc825b6cf --- /dev/null +++ b/src/test/ui/suggestions/method-access-to-range-literal-typo.stderr @@ -0,0 +1,45 @@ +error[E0425]: cannot find function `foo` in this scope + --> $DIR/method-access-to-range-literal-typo.rs:14:22 + | +LL | self.option..foo().map(|x| x) + | ^^^ not found in this scope + | +help: you might have meant to write a method call instead of a range + | +LL | self.option.foo().map(|x| x) + | ~ + +error[E0308]: mismatched types + --> $DIR/method-access-to-range-literal-typo.rs:10:9 + | +LL | fn method(&self) -> Option> { + | --------------- expected `Option>` because of return type +LL | self.option..as_ref().map(|x| x) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `Option`, found struct `Range` + | + = note: expected enum `Option<_>` + found struct `std::ops::Range>` +help: you might have meant to write a method call instead of a range + | +LL | self.option.as_ref().map(|x| x) + | ~ + +error[E0308]: mismatched types + --> $DIR/method-access-to-range-literal-typo.rs:14:9 + | +LL | fn method2(&self) -> Option> { + | --------------- expected `Option>` because of return type +LL | self.option..foo().map(|x| x) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `Option`, found struct `Range` + | + = note: expected enum `Option<_>` + found struct `std::ops::Range>` +help: you might have meant to write a method call instead of a range + | +LL | self.option.foo().map(|x| x) + | ~ + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0308, E0425. +For more information about an error, try `rustc --explain E0308`. From 9f18cc9e5179afa656fdf7a19f372d8ca0bb9d49 Mon Sep 17 00:00:00 2001 From: RetroSeven Date: Tue, 27 Dec 2022 11:07:44 +0100 Subject: [PATCH 06/13] Fix a formatting error --- library/core/src/iter/traits/iterator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/iter/traits/iterator.rs b/library/core/src/iter/traits/iterator.rs index bac836292f8..08abb0b0d92 100644 --- a/library/core/src/iter/traits/iterator.rs +++ b/library/core/src/iter/traits/iterator.rs @@ -803,7 +803,7 @@ pub trait Iterator { /// (0..5).map(|x| x * 2 + 1) /// .for_each(move |x| tx.send(x).unwrap()); /// - /// let v: Vec<_> = rx.iter().collect(); + /// let v: Vec<_> = rx.iter().collect(); /// assert_eq!(v, vec![1, 3, 5, 7, 9]); /// ``` /// From ce1e6a46129eb16ca3339c9cc4f71f9beb68ea5e Mon Sep 17 00:00:00 2001 From: kraktus Date: Tue, 27 Dec 2022 13:31:53 +0100 Subject: [PATCH 07/13] Fix doc comment parsing This can actually make a difference for the user if they rely on unicode formating. Prompted by https://github.com/dtolnay/syn/issues/771 --- src/doc/rustdoc/src/write-documentation/the-doc-attribute.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md b/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md index e3b08648999..8ecf05f0e12 100644 --- a/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md +++ b/src/doc/rustdoc/src/write-documentation/the-doc-attribute.md @@ -9,11 +9,11 @@ are the same: ```rust,no_run /// This is a doc comment. -#[doc = " This is a doc comment."] +#[doc = r" This is a doc comment."] # fn f() {} ``` -(Note the leading space in the attribute version.) +(Note the leading space and the raw string literal in the attribute version.) In most cases, `///` is easier to use than `#[doc]`. One case where the latter is easier is when generating documentation in macros; the `collapse-docs` pass will combine multiple From 3cf22de90fcc23100bbecae3925896409d36aaa9 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 17 Dec 2022 23:20:16 +0000 Subject: [PATCH 08/13] Suggest rewriting a malformed hex literal if we expect a float --- .../src/fn_ctxt/suggestions.rs | 26 ++++++++++ src/test/ui/suggestions/bad-hex-float-lit.rs | 13 +++++ .../ui/suggestions/bad-hex-float-lit.stderr | 48 +++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 src/test/ui/suggestions/bad-hex-float-lit.rs create mode 100644 src/test/ui/suggestions/bad-hex-float-lit.stderr diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index 9a7d753e66b..e38d1bccc10 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -19,6 +19,7 @@ use rustc_middle::ty::{ TypeVisitable, }; use rustc_session::errors::ExprParenthesesNeeded; +use rustc_span::source_map::Spanned; use rustc_span::symbol::{sym, Ident}; use rustc_span::{Span, Symbol}; use rustc_trait_selection::infer::InferCtxtExt; @@ -1259,6 +1260,31 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); true } + ExprKind::Lit(Spanned { + node: rustc_ast::LitKind::Int(lit, rustc_ast::LitIntType::Unsuffixed), + span, + }) => { + let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) else { return false; }; + if !(snippet.starts_with("0x") || snippet.starts_with("0X")) { + return false; + } + if snippet.len() <= 5 || !snippet.is_char_boundary(snippet.len() - 3) { + return false; + } + let (_, suffix) = snippet.split_at(snippet.len() - 3); + let value = match suffix { + "f32" => (lit - 0xf32) / (16 * 16 * 16), + "f64" => (lit - 0xf64) / (16 * 16 * 16), + _ => return false, + }; + err.span_suggestions( + expr.span, + "rewrite this as a decimal floating point literal, or use `as` to turn a hex literal into a float", + [format!("0x{value:X} as {suffix}"), format!("{value}_{suffix}")], + Applicability::MaybeIncorrect, + ); + true + } _ => false, } } diff --git a/src/test/ui/suggestions/bad-hex-float-lit.rs b/src/test/ui/suggestions/bad-hex-float-lit.rs new file mode 100644 index 00000000000..cd6fdbde96c --- /dev/null +++ b/src/test/ui/suggestions/bad-hex-float-lit.rs @@ -0,0 +1,13 @@ +fn main() { + let _f: f32 = 0xAAf32; + //~^ ERROR mismatched types + //~| HELP rewrite this + + let _f: f32 = 0xAB_f32; + //~^ ERROR mismatched types + //~| HELP rewrite this + + let _f: f64 = 0xFF_f64; + //~^ ERROR mismatched types + //~| HELP rewrite this +} diff --git a/src/test/ui/suggestions/bad-hex-float-lit.stderr b/src/test/ui/suggestions/bad-hex-float-lit.stderr new file mode 100644 index 00000000000..bc09abb1a56 --- /dev/null +++ b/src/test/ui/suggestions/bad-hex-float-lit.stderr @@ -0,0 +1,48 @@ +error[E0308]: mismatched types + --> $DIR/bad-hex-float-lit.rs:2:19 + | +LL | let _f: f32 = 0xAAf32; + | --- ^^^^^^^ expected `f32`, found integer + | | + | expected due to this + | +help: rewrite this as a decimal floating point literal, or use `as` to turn a hex literal into a float + | +LL | let _f: f32 = 0xAA as f32; + | ~~~~~~~~~~~ +LL | let _f: f32 = 170_f32; + | ~~~~~~~ + +error[E0308]: mismatched types + --> $DIR/bad-hex-float-lit.rs:6:19 + | +LL | let _f: f32 = 0xAB_f32; + | --- ^^^^^^^^ expected `f32`, found integer + | | + | expected due to this + | +help: rewrite this as a decimal floating point literal, or use `as` to turn a hex literal into a float + | +LL | let _f: f32 = 0xAB as f32; + | ~~~~~~~~~~~ +LL | let _f: f32 = 171_f32; + | ~~~~~~~ + +error[E0308]: mismatched types + --> $DIR/bad-hex-float-lit.rs:10:19 + | +LL | let _f: f64 = 0xFF_f64; + | --- ^^^^^^^^ expected `f64`, found integer + | | + | expected due to this + | +help: rewrite this as a decimal floating point literal, or use `as` to turn a hex literal into a float + | +LL | let _f: f64 = 0xFF as f64; + | ~~~~~~~~~~~ +LL | let _f: f64 = 255_f64; + | ~~~~~~~ + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0308`. From e5c159cf9026af6304ef07fd151fe7c500ed2510 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Tue, 27 Dec 2022 17:10:21 +0000 Subject: [PATCH 09/13] Provide local extern function arg names --- compiler/rustc_middle/src/hir/mod.rs | 6 ++++- .../extern-fn-arg-names.rs | 9 +++++++ .../extern-fn-arg-names.stderr | 26 +++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/test/ui/argument-suggestions/extern-fn-arg-names.rs create mode 100644 src/test/ui/argument-suggestions/extern-fn-arg-names.stderr diff --git a/compiler/rustc_middle/src/hir/mod.rs b/compiler/rustc_middle/src/hir/mod.rs index 3f6e29ad611..a633201e3d9 100644 --- a/compiler/rustc_middle/src/hir/mod.rs +++ b/compiler/rustc_middle/src/hir/mod.rs @@ -160,9 +160,13 @@ pub fn provide(providers: &mut Providers) { } else if let Node::TraitItem(&TraitItem { kind: TraitItemKind::Fn(_, TraitFn::Required(idents)), .. + }) + | Node::ForeignItem(&ForeignItem { + kind: ForeignItemKind::Fn(_, idents, _), + .. }) = hir.get(hir_id) { - tcx.arena.alloc_slice(idents) + idents } else { span_bug!(hir.span(hir_id), "fn_arg_names: unexpected item {:?}", id); } diff --git a/src/test/ui/argument-suggestions/extern-fn-arg-names.rs b/src/test/ui/argument-suggestions/extern-fn-arg-names.rs new file mode 100644 index 00000000000..6c925a3d653 --- /dev/null +++ b/src/test/ui/argument-suggestions/extern-fn-arg-names.rs @@ -0,0 +1,9 @@ +extern "Rust" { + fn dstfn(src: i32, dst: err); + //~^ ERROR cannot find type `err` in this scope +} + +fn main() { + dstfn(1); + //~^ ERROR this function takes 2 arguments but 1 argument was supplied +} diff --git a/src/test/ui/argument-suggestions/extern-fn-arg-names.stderr b/src/test/ui/argument-suggestions/extern-fn-arg-names.stderr new file mode 100644 index 00000000000..f6bc84c1203 --- /dev/null +++ b/src/test/ui/argument-suggestions/extern-fn-arg-names.stderr @@ -0,0 +1,26 @@ +error[E0412]: cannot find type `err` in this scope + --> $DIR/extern-fn-arg-names.rs:2:29 + | +LL | fn dstfn(src: i32, dst: err); + | ^^^ not found in this scope + +error[E0061]: this function takes 2 arguments but 1 argument was supplied + --> $DIR/extern-fn-arg-names.rs:7:5 + | +LL | dstfn(1); + | ^^^^^--- an argument is missing + | +note: function defined here + --> $DIR/extern-fn-arg-names.rs:2:8 + | +LL | fn dstfn(src: i32, dst: err); + | ^^^^^ +help: provide the argument + | +LL | dstfn(1, /* dst */); + | ~~~~~~~~~~~~~~ + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0061, E0412. +For more information about an error, try `rustc --explain E0061`. From 0c0685bb68a53636e1347ea93dccfa7ac1e24deb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Tue, 27 Dec 2022 09:25:00 -0800 Subject: [PATCH 10/13] review comments: make suggestion more accurate --- compiler/rustc_hir_typeck/src/coercion.rs | 1 - compiler/rustc_hir_typeck/src/demand.rs | 40 +++++++++++++++---- compiler/rustc_resolve/src/late.rs | 12 +++++- .../method-access-to-range-literal-typo.rs | 12 +++++- ...method-access-to-range-literal-typo.stderr | 39 +++++++++--------- 5 files changed, 73 insertions(+), 31 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index 706700bc1f4..b0cd4a16e98 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -1604,7 +1604,6 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { None, Some(coercion_error), ); - fcx.check_for_range_as_method_call(&mut err, expr, found, expected); } if visitor.ret_exprs.len() > 0 && let Some(expr) = expression { diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index 4352c50358f..91419395838 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -71,6 +71,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.note_type_is_not_clone(err, expected, expr_ty, expr); self.note_need_for_fn_pointer(err, expected, expr_ty); self.note_internal_mutation_in_method(err, expr, expected, expr_ty); + self.check_for_range_as_method_call(err, expr, expr_ty, expected); } /// Requires that the two types unify, and prints an error message if @@ -1449,14 +1450,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + /// Identify when the user has written `foo..bar()` instead of `foo.bar()`. pub fn check_for_range_as_method_call( &self, err: &mut Diagnostic, expr: &hir::Expr<'_>, checked_ty: Ty<'tcx>, - // FIXME: We should do analysis to see if we can synthesize an expresion that produces - // this type for always accurate suggestions, or at least marking the suggestion as - // machine applicable. expected_ty: Ty<'tcx>, ) { if !hir::is_range_literal(expr) { @@ -1467,13 +1466,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { [start, end], _, ) = expr.kind else { return; }; + let parent = self.tcx.hir().get_parent_node(expr.hir_id); + if let Some(hir::Node::ExprField(_)) = self.tcx.hir().find(parent) { + // Ignore `Foo { field: a..Default::default() }` + return; + } let mut expr = end.expr; while let hir::ExprKind::MethodCall(_, rcvr, ..) = expr.kind { // Getting to the root receiver and asserting it is a fn call let's us ignore cases in // `src/test/ui/methods/issues/issue-90315.stderr`. expr = rcvr; } - let hir::ExprKind::Call(..) = expr.kind else { return; }; + let hir::ExprKind::Call(method_name, _) = expr.kind else { return; }; let ty::Adt(adt, _) = checked_ty.kind() else { return; }; if self.tcx.lang_items().range_struct() != Some(adt.did()) { return; @@ -1483,11 +1487,31 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { { return; } + // Check if start has method named end. + let hir::ExprKind::Path(hir::QPath::Resolved(None, p)) = method_name.kind else { return; }; + let [hir::PathSegment { ident, .. }] = p.segments else { return; }; + let self_ty = self.typeck_results.borrow().expr_ty(start.expr); + let Ok(_pick) = self.probe_for_name( + probe::Mode::MethodCall, + *ident, + probe::IsSuggestion(true), + self_ty, + expr.hir_id, + probe::ProbeScope::AllTraits, + ) else { return; }; + let mut sugg = "."; + let mut span = start.expr.span.between(end.expr.span); + if span.lo() + BytePos(2) == span.hi() { + // There's no space between the start, the range op and the end, suggest removal which + // will be more noticeable than the replacement of `..` with `.`. + span = span.with_lo(span.lo() + BytePos(1)); + sugg = ""; + } err.span_suggestion_verbose( - start.expr.span.between(end.expr.span), - "you might have meant to write a method call instead of a range", - ".".to_string(), - Applicability::MaybeIncorrect, + span, + "you likely meant to write a method call instead of a range", + sugg, + Applicability::MachineApplicable, ); } } diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 13b001af7ea..7720d87c04b 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -3344,10 +3344,18 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { let suggestion = if let Some((start, end)) = this.diagnostic_metadata.in_range && path[0].ident.span.lo() == end.span.lo() { + let mut sugg = "."; + let mut span = start.span.between(end.span); + if span.lo() + BytePos(2) == span.hi() { + // There's no space between the start, the range op and the end, suggest + // removal which will look better. + span = span.with_lo(span.lo() + BytePos(1)); + sugg = ""; + } Some(( - start.span.between(end.span), + span, "you might have meant to write a method call instead of a range", - ".".to_string(), + sugg.to_string(), Applicability::MaybeIncorrect, )) } else if res.is_none() { diff --git a/src/test/ui/suggestions/method-access-to-range-literal-typo.rs b/src/test/ui/suggestions/method-access-to-range-literal-typo.rs index 545f9c597fd..ac662edafe6 100644 --- a/src/test/ui/suggestions/method-access-to-range-literal-typo.rs +++ b/src/test/ui/suggestions/method-access-to-range-literal-typo.rs @@ -4,14 +4,22 @@ fn as_ref() -> Option> { struct Type { option: Option> } +trait Trait { + fn foo(&self) -> Vec; +} +impl Trait for Option> { + fn foo(&self) -> Vec { + vec![1, 2, 3] + } +} impl Type { fn method(&self) -> Option> { self.option..as_ref().map(|x| x) //~^ ERROR E0308 } - fn method2(&self) -> Option> { - self.option..foo().map(|x| x) + fn method2(&self) -> &u8 { + self.option..foo().get(0) //~^ ERROR E0425 //~| ERROR E0308 } diff --git a/src/test/ui/suggestions/method-access-to-range-literal-typo.stderr b/src/test/ui/suggestions/method-access-to-range-literal-typo.stderr index becc825b6cf..02db7f81ebd 100644 --- a/src/test/ui/suggestions/method-access-to-range-literal-typo.stderr +++ b/src/test/ui/suggestions/method-access-to-range-literal-typo.stderr @@ -1,16 +1,17 @@ error[E0425]: cannot find function `foo` in this scope - --> $DIR/method-access-to-range-literal-typo.rs:14:22 + --> $DIR/method-access-to-range-literal-typo.rs:22:22 | -LL | self.option..foo().map(|x| x) +LL | self.option..foo().get(0) | ^^^ not found in this scope | help: you might have meant to write a method call instead of a range | -LL | self.option.foo().map(|x| x) - | ~ +LL - self.option..foo().get(0) +LL + self.option.foo().get(0) + | error[E0308]: mismatched types - --> $DIR/method-access-to-range-literal-typo.rs:10:9 + --> $DIR/method-access-to-range-literal-typo.rs:18:9 | LL | fn method(&self) -> Option> { | --------------- expected `Option>` because of return type @@ -19,25 +20,27 @@ LL | self.option..as_ref().map(|x| x) | = note: expected enum `Option<_>` found struct `std::ops::Range>` -help: you might have meant to write a method call instead of a range +help: you likely meant to write a method call instead of a range + | +LL - self.option..as_ref().map(|x| x) +LL + self.option.as_ref().map(|x| x) | -LL | self.option.as_ref().map(|x| x) - | ~ error[E0308]: mismatched types - --> $DIR/method-access-to-range-literal-typo.rs:14:9 + --> $DIR/method-access-to-range-literal-typo.rs:22:9 | -LL | fn method2(&self) -> Option> { - | --------------- expected `Option>` because of return type -LL | self.option..foo().map(|x| x) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected enum `Option`, found struct `Range` +LL | fn method2(&self) -> &u8 { + | --- expected `&u8` because of return type +LL | self.option..foo().get(0) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ expected `&u8`, found struct `Range` | - = note: expected enum `Option<_>` - found struct `std::ops::Range>` -help: you might have meant to write a method call instead of a range + = note: expected reference `&u8` + found struct `std::ops::Range>>` +help: you likely meant to write a method call instead of a range + | +LL - self.option..foo().get(0) +LL + self.option.foo().get(0) | -LL | self.option.foo().map(|x| x) - | ~ error: aborting due to 3 previous errors From 4df5459dd1d65cf15cd9fb8514d11ffbec611d2b Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 27 Dec 2022 19:44:58 +0200 Subject: [PATCH 11/13] Update the documentation of `Vec` to use `extend(array)` instead of `extend(array.iter().copied())` --- library/alloc/src/vec/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index ba34ab6800f..1028bb981b9 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -166,7 +166,7 @@ mod spec_extend; /// vec[0] = 7; /// assert_eq!(vec[0], 7); /// -/// vec.extend([1, 2, 3].iter().copied()); +/// vec.extend([1, 2, 3]); /// /// for x in &vec { /// println!("{x}"); From b026167eb96720c44fc584d2184b3039c44b6932 Mon Sep 17 00:00:00 2001 From: Alex Rao Date: Tue, 27 Dec 2022 12:17:56 -0600 Subject: [PATCH 12/13] Fix UnsafeCell Documentation Spelling Error This fixes the spelling of "deallocated" (instead of the original "deallocted") In the `cell.rs` source file --- library/core/src/cell.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/cell.rs b/library/core/src/cell.rs index b4e173ce03d..ba93165cf53 100644 --- a/library/core/src/cell.rs +++ b/library/core/src/cell.rs @@ -1783,7 +1783,7 @@ impl fmt::Display for RefMut<'_, T> { /// until the reference expires. As a special exception, given an `&T`, any part of it that is /// inside an `UnsafeCell<_>` may be deallocated during the lifetime of the reference, after the /// last time the reference is used (dereferenced or reborrowed). Since you cannot deallocate a part -/// of what a reference points to, this means the memory an `&T` points to can be deallocted only if +/// of what a reference points to, this means the memory an `&T` points to can be deallocated only if /// *every part of it* (including padding) is inside an `UnsafeCell`. /// /// However, whenever a `&UnsafeCell` is constructed or dereferenced, it must still point to From 7e84273b7fcaa0f6578c97fd71e27453ef300c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Tue, 27 Dec 2022 12:16:25 -0800 Subject: [PATCH 13/13] Make resolve suggestion more generic --- compiler/rustc_resolve/src/late.rs | 2 +- .../ui/suggestions/method-access-to-range-literal-typo.stderr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 7720d87c04b..49e069f58c9 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -3354,7 +3354,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { } Some(( span, - "you might have meant to write a method call instead of a range", + "you might have meant to write `.` instead of `..`", sugg.to_string(), Applicability::MaybeIncorrect, )) diff --git a/src/test/ui/suggestions/method-access-to-range-literal-typo.stderr b/src/test/ui/suggestions/method-access-to-range-literal-typo.stderr index 02db7f81ebd..c84f9467891 100644 --- a/src/test/ui/suggestions/method-access-to-range-literal-typo.stderr +++ b/src/test/ui/suggestions/method-access-to-range-literal-typo.stderr @@ -4,7 +4,7 @@ error[E0425]: cannot find function `foo` in this scope LL | self.option..foo().get(0) | ^^^ not found in this scope | -help: you might have meant to write a method call instead of a range +help: you might have meant to write `.` instead of `..` | LL - self.option..foo().get(0) LL + self.option.foo().get(0)