about summary refs log tree commit diff
path: root/compiler/rustc_hir_analysis/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-09-21 22:09:47 +0000
committerbors <bors@rust-lang.org>2024-09-21 22:09:47 +0000
commit764e6aec81517cde60214ccd00a709a34eb0c07d (patch)
tree098f6fe0dcc9a44cb5280b813bda474854d0a1e5 /compiler/rustc_hir_analysis/src
parent1d68e6dd1deef26c5aeb91aee554edbee8b6d5e2 (diff)
parent52f146d363504d25b5e2fe468cadc2f50315da91 (diff)
downloadrust-764e6aec81517cde60214ccd00a709a34eb0c07d.tar.gz
rust-764e6aec81517cde60214ccd00a709a34eb0c07d.zip
Auto merge of #130674 - compiler-errors:rollup-yu105fl, r=compiler-errors
Rollup of 8 pull requests

Successful merges:

 - #127766 (add `extern "C-cmse-nonsecure-entry" fn` )
 - #129629 (Implement Return Type Notation (RTN)'s path form in where clauses)
 - #130408 (Avoid re-validating UTF-8 in `FromUtf8Error::into_utf8_lossy`)
 - #130651 (Add --enable-profiler to armhf dist)
 - #130653 (ABI compatibility: mention Result guarantee)
 - #130666 (Assert that `explicit_super_predicates_of` and `explicit_item_super_predicates` truly only contains bounds for the type itself)
 - #130667 (compiler: Accept "improper" ctypes in extern "rust-cold" fn)
 - #130673 (Parser: recover from `:::` to `::`)

r? `@ghost`
`@rustbot` modify labels: rollup
Diffstat (limited to 'compiler/rustc_hir_analysis/src')
-rw-r--r--compiler/rustc_hir_analysis/src/bounds.rs8
-rw-r--r--compiler/rustc_hir_analysis/src/collect.rs5
-rw-r--r--compiler/rustc_hir_analysis/src/collect/item_bounds.rs32
-rw-r--r--compiler/rustc_hir_analysis/src/collect/predicates_of.rs67
-rw-r--r--compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs181
-rw-r--r--compiler/rustc_hir_analysis/src/errors.rs14
-rw-r--r--compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs336
-rw-r--r--compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs128
8 files changed, 632 insertions, 139 deletions
diff --git a/compiler/rustc_hir_analysis/src/bounds.rs b/compiler/rustc_hir_analysis/src/bounds.rs
index d0b0c08aa79..14ea7816291 100644
--- a/compiler/rustc_hir_analysis/src/bounds.rs
+++ b/compiler/rustc_hir_analysis/src/bounds.rs
@@ -9,6 +9,8 @@ use rustc_middle::ty::{self, Ty, TyCtxt, Upcast};
 use rustc_span::def_id::DefId;
 use rustc_span::Span;
 
+use crate::hir_ty_lowering::OnlySelfBounds;
+
 /// Collects together a list of type bounds. These lists of bounds occur in many places
 /// in Rust's syntax:
 ///
@@ -50,6 +52,7 @@ impl<'tcx> Bounds<'tcx> {
         span: Span,
         polarity: ty::PredicatePolarity,
         constness: ty::BoundConstness,
+        only_self_bounds: OnlySelfBounds,
     ) {
         let clause = (
             bound_trait_ref
@@ -66,7 +69,10 @@ impl<'tcx> Bounds<'tcx> {
             self.clauses.push(clause);
         }
 
-        if !tcx.features().effects {
+        // FIXME(effects): Lift this out of `push_trait_bound`, and move it somewhere else.
+        // Perhaps moving this into `lower_poly_trait_ref`, just like we lower associated
+        // type bounds.
+        if !tcx.features().effects || only_self_bounds.0 {
             return;
         }
         // For `T: ~const Tr` or `T: const Tr`, we need to add an additional bound on the
diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs
index ac9976148e2..4c59f7540ee 100644
--- a/compiler/rustc_hir_analysis/src/collect.rs
+++ b/compiler/rustc_hir_analysis/src/collect.rs
@@ -460,7 +460,7 @@ impl<'tcx> HirTyLowerer<'tcx> for ItemCtxt<'tcx> {
                                 [] => (generics.span, format!("<{lt_name}>")),
                                 [bound, ..] => (bound.span.shrink_to_lo(), format!("{lt_name}, ")),
                             };
-                            mpart_sugg = Some(errors::AssociatedTypeTraitUninferredGenericParamsMultipartSuggestion {
+                            mpart_sugg = Some(errors::AssociatedItemTraitUninferredGenericParamsMultipartSuggestion {
                                 fspan: lt_sp,
                                 first: sugg,
                                 sspan: span.with_hi(item_segment.ident.span.lo()),
@@ -502,11 +502,12 @@ impl<'tcx> HirTyLowerer<'tcx> for ItemCtxt<'tcx> {
             }
             Ty::new_error(
                 self.tcx(),
-                self.tcx().dcx().emit_err(errors::AssociatedTypeTraitUninferredGenericParams {
+                self.tcx().dcx().emit_err(errors::AssociatedItemTraitUninferredGenericParams {
                     span,
                     inferred_sugg,
                     bound,
                     mpart_sugg,
+                    what: "type",
                 }),
             )
         }
diff --git a/compiler/rustc_hir_analysis/src/collect/item_bounds.rs b/compiler/rustc_hir_analysis/src/collect/item_bounds.rs
index d62727e76b5..7a254c884c2 100644
--- a/compiler/rustc_hir_analysis/src/collect/item_bounds.rs
+++ b/compiler/rustc_hir_analysis/src/collect/item_bounds.rs
@@ -10,6 +10,7 @@ use rustc_span::Span;
 use rustc_type_ir::Upcast;
 use tracing::{debug, instrument};
 
+use super::predicates_of::assert_only_contains_predicates_from;
 use super::ItemCtxt;
 use crate::hir_ty_lowering::{HirTyLowerer, PredicateFilter};
 
@@ -56,6 +57,9 @@ fn associated_type_bounds<'tcx>(
         tcx.def_path_str(assoc_item_def_id.to_def_id()),
         all_bounds
     );
+
+    assert_only_contains_predicates_from(filter, all_bounds, item_ty);
+
     all_bounds
 }
 
@@ -108,18 +112,21 @@ pub(super) fn explicit_item_bounds_with_filter(
         Some(ty::ImplTraitInTraitData::Trait { opaque_def_id, .. }) => {
             let item = tcx.hir_node_by_def_id(opaque_def_id.expect_local()).expect_item();
             let opaque_ty = item.expect_opaque_ty();
-            return ty::EarlyBinder::bind(opaque_type_bounds(
+            let item_ty = Ty::new_projection_from_args(
+                tcx,
+                def_id.to_def_id(),
+                ty::GenericArgs::identity_for_item(tcx, def_id),
+            );
+            let bounds = opaque_type_bounds(
                 tcx,
                 opaque_def_id.expect_local(),
                 opaque_ty.bounds,
-                Ty::new_projection_from_args(
-                    tcx,
-                    def_id.to_def_id(),
-                    ty::GenericArgs::identity_for_item(tcx, def_id),
-                ),
+                item_ty,
                 item.span,
                 filter,
-            ));
+            );
+            assert_only_contains_predicates_from(filter, bounds, item_ty);
+            return ty::EarlyBinder::bind(bounds);
         }
         Some(ty::ImplTraitInTraitData::Impl { .. }) => span_bug!(
             tcx.def_span(def_id),
@@ -167,7 +174,9 @@ pub(super) fn explicit_item_bounds_with_filter(
         }) => {
             let args = GenericArgs::identity_for_item(tcx, def_id);
             let item_ty = Ty::new_opaque(tcx, def_id.to_def_id(), args);
-            opaque_type_bounds(tcx, def_id, bounds, item_ty, *span, filter)
+            let bounds = opaque_type_bounds(tcx, def_id, bounds, item_ty, *span, filter);
+            assert_only_contains_predicates_from(filter, bounds, item_ty);
+            bounds
         }
         // Since RPITITs are lowered as projections in `<dyn HirTyLowerer>::lower_ty`, when we're
         // asking for the item bounds of the *opaques* in a trait's default method signature, we
@@ -184,15 +193,18 @@ pub(super) fn explicit_item_bounds_with_filter(
             };
             let args = GenericArgs::identity_for_item(tcx, def_id);
             let item_ty = Ty::new_opaque(tcx, def_id.to_def_id(), args);
-            tcx.arena.alloc_slice(
+            let bounds = &*tcx.arena.alloc_slice(
                 &opaque_type_bounds(tcx, def_id, bounds, item_ty, *span, filter)
                     .to_vec()
                     .fold_with(&mut AssocTyToOpaque { tcx, fn_def_id: fn_def_id.to_def_id() }),
-            )
+            );
+            assert_only_contains_predicates_from(filter, bounds, item_ty);
+            bounds
         }
         hir::Node::Item(hir::Item { kind: hir::ItemKind::TyAlias(..), .. }) => &[],
         _ => bug!("item_bounds called on {:?}", def_id),
     };
+
     ty::EarlyBinder::bind(bounds)
 }
 
diff --git a/compiler/rustc_hir_analysis/src/collect/predicates_of.rs b/compiler/rustc_hir_analysis/src/collect/predicates_of.rs
index 7243e85ce98..67d8813d1f7 100644
--- a/compiler/rustc_hir_analysis/src/collect/predicates_of.rs
+++ b/compiler/rustc_hir_analysis/src/collect/predicates_of.rs
@@ -240,7 +240,8 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen
     for predicate in hir_generics.predicates {
         match predicate {
             hir::WherePredicate::BoundPredicate(bound_pred) => {
-                let ty = icx.lower_ty(bound_pred.bounded_ty);
+                let ty = icx.lowerer().lower_ty_maybe_return_type_notation(bound_pred.bounded_ty);
+
                 let bound_vars = tcx.late_bound_vars(bound_pred.hir_id);
                 // Keep the type around in a dummy predicate, in case of no bounds.
                 // That way, `where Ty:` is not a complete noop (see #53696) and `Ty`
@@ -676,9 +677,63 @@ pub(super) fn implied_predicates_with_filter<'tcx>(
         _ => {}
     }
 
+    assert_only_contains_predicates_from(filter, implied_bounds, tcx.types.self_param);
+
     ty::EarlyBinder::bind(implied_bounds)
 }
 
+// Make sure when elaborating supertraits, probing for associated types, etc.,
+// we really truly are elaborating clauses that have `Self` as their self type.
+// This is very important since downstream code relies on this being correct.
+pub(super) fn assert_only_contains_predicates_from<'tcx>(
+    filter: PredicateFilter,
+    bounds: &'tcx [(ty::Clause<'tcx>, Span)],
+    ty: Ty<'tcx>,
+) {
+    if !cfg!(debug_assertions) {
+        return;
+    }
+
+    match filter {
+        PredicateFilter::SelfOnly | PredicateFilter::SelfThatDefines(_) => {
+            for (clause, _) in bounds {
+                match clause.kind().skip_binder() {
+                    ty::ClauseKind::Trait(trait_predicate) => {
+                        assert_eq!(
+                            trait_predicate.self_ty(),
+                            ty,
+                            "expected `Self` predicate when computing `{filter:?}` implied bounds: {clause:?}"
+                        );
+                    }
+                    ty::ClauseKind::Projection(projection_predicate) => {
+                        assert_eq!(
+                            projection_predicate.self_ty(),
+                            ty,
+                            "expected `Self` predicate when computing `{filter:?}` implied bounds: {clause:?}"
+                        );
+                    }
+                    ty::ClauseKind::TypeOutlives(outlives_predicate) => {
+                        assert_eq!(
+                            outlives_predicate.0, ty,
+                            "expected `Self` predicate when computing `{filter:?}` implied bounds: {clause:?}"
+                        );
+                    }
+
+                    ty::ClauseKind::RegionOutlives(_)
+                    | ty::ClauseKind::ConstArgHasType(_, _)
+                    | ty::ClauseKind::WellFormed(_)
+                    | ty::ClauseKind::ConstEvaluatable(_) => {
+                        bug!(
+                            "unexpected non-`Self` predicate when computing `{filter:?}` implied bounds: {clause:?}"
+                        );
+                    }
+                }
+            }
+        }
+        PredicateFilter::All | PredicateFilter::SelfAndAssociatedTypeBounds => {}
+    }
+}
+
 /// Returns the predicates defined on `item_def_id` of the form
 /// `X: Foo` where `X` is the type parameter `def_id`.
 #[instrument(level = "trace", skip(tcx))]
@@ -770,6 +825,10 @@ impl<'tcx> ItemCtxt<'tcx> {
                 continue;
             };
 
+            // Subtle: If we're collecting `SelfAndAssociatedTypeBounds`, then we
+            // want to only consider predicates with `Self: ...`, but we don't want
+            // `OnlySelfBounds(true)` since we want to collect the nested associated
+            // type bound as well.
             let (only_self_bounds, assoc_name) = match filter {
                 PredicateFilter::All | PredicateFilter::SelfAndAssociatedTypeBounds => {
                     (OnlySelfBounds(false), None)
@@ -780,14 +839,10 @@ impl<'tcx> ItemCtxt<'tcx> {
                 }
             };
 
-            // Subtle: If we're collecting `SelfAndAssociatedTypeBounds`, then we
-            // want to only consider predicates with `Self: ...`, but we don't want
-            // `OnlySelfBounds(true)` since we want to collect the nested associated
-            // type bound as well.
             let bound_ty = if predicate.is_param_bound(param_def_id.to_def_id()) {
                 ty
             } else if matches!(filter, PredicateFilter::All) {
-                self.lower_ty(predicate.bounded_ty)
+                self.lowerer().lower_ty_maybe_return_type_notation(predicate.bounded_ty)
             } else {
                 continue;
             };
diff --git a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs
index 53dcede91c3..d1601446b6f 100644
--- a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs
+++ b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs
@@ -889,7 +889,12 @@ impl<'a, 'tcx> Visitor<'tcx> for BoundVarContext<'a, 'tcx> {
                             (pair, r)
                         })
                         .unzip();
+
                 self.record_late_bound_vars(hir_id, binders);
+
+                // If this is an RTN type in the self type, then append those to the binder.
+                self.try_append_return_type_notation_params(hir_id, bounded_ty);
+
                 // Even if there are no lifetimes defined here, we still wrap it in a binder
                 // scope. If there happens to be a nested poly trait ref (an error), that
                 // will be `Concatenating` anyways, so we don't have to worry about the depth
@@ -1635,9 +1640,9 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> {
             // }
             // ```
             // and a bound that looks like:
-            //    `for<'a> T::Trait<'a, x(): for<'b> Other<'b>>`
+            //    `for<'a> T::Trait<'a, x(..): for<'b> Other<'b>>`
             // this is going to expand to something like:
-            //    `for<'a> for<'r, T> <T as Trait<'a>>::x::<'r, T>::{opaque#0}: for<'b> Other<'b>`.
+            //    `for<'a> for<'r> <T as Trait<'a>>::x::<'r, T>::{opaque#0}: for<'b> Other<'b>`.
             if constraint.gen_args.parenthesized == hir::GenericArgsParentheses::ReturnTypeNotation
             {
                 let bound_vars = if let Some(type_def_id) = type_def_id
@@ -1839,6 +1844,178 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> {
         let old_value = self.map.defs.swap_remove(&lifetime_ref.hir_id);
         assert_eq!(old_value, Some(bad_def));
     }
+
+    // When we have a return type notation type in a where clause, like
+    // `where <T as Trait>::method(..): Send`, we need to introduce new bound
+    // vars to the existing where clause's binder, to represent the lifetimes
+    // elided by the return-type-notation syntax.
+    //
+    // For example, given
+    // ```
+    // trait Foo {
+    //     async fn x<'r>();
+    // }
+    // ```
+    // and a bound that looks like:
+    //    `for<'a, 'b> <T as Trait<'a>>::x(): Other<'b>`
+    // this is going to expand to something like:
+    //    `for<'a, 'b, 'r> <T as Trait<'a>>::x::<'r, T>::{opaque#0}: Other<'b>`.
+    //
+    // We handle this similarly for associated-type-bound style return-type-notation
+    // in `visit_segment_args`.
+    fn try_append_return_type_notation_params(
+        &mut self,
+        hir_id: HirId,
+        hir_ty: &'tcx hir::Ty<'tcx>,
+    ) {
+        let hir::TyKind::Path(qpath) = hir_ty.kind else {
+            // We only care about path types here. All other self types
+            // (including nesting the RTN type in another type) don't do
+            // anything.
+            return;
+        };
+
+        let (mut bound_vars, item_def_id, item_segment) = match qpath {
+            // If we have a fully qualified method, then we don't need to do any special lookup.
+            hir::QPath::Resolved(_, path)
+                if let [.., item_segment] = &path.segments[..]
+                    && item_segment.args.is_some_and(|args| {
+                        matches!(
+                            args.parenthesized,
+                            hir::GenericArgsParentheses::ReturnTypeNotation
+                        )
+                    }) =>
+            {
+                match path.res {
+                    Res::Err => return,
+                    Res::Def(DefKind::AssocFn, item_def_id) => (vec![], item_def_id, item_segment),
+                    _ => bug!("only expected method resolution for fully qualified RTN"),
+                }
+            }
+
+            // If we have a type-dependent path, then we do need to do some lookup.
+            hir::QPath::TypeRelative(qself, item_segment)
+                if item_segment.args.is_some_and(|args| {
+                    matches!(args.parenthesized, hir::GenericArgsParentheses::ReturnTypeNotation)
+                }) =>
+            {
+                // First, ignore a qself that isn't a type or `Self` param. Those are the
+                // only ones that support `T::Assoc` anyways in HIR lowering.
+                let hir::TyKind::Path(hir::QPath::Resolved(None, path)) = qself.kind else {
+                    return;
+                };
+                match path.res {
+                    Res::Def(DefKind::TyParam, _) | Res::SelfTyParam { trait_: _ } => {
+                        // Get the generics of this type's hir owner. This is *different*
+                        // from the generics of the parameter's definition, since we want
+                        // to be able to resolve an RTN path on a nested body (e.g. method
+                        // inside an impl) using the where clauses on the method.
+                        // FIXME(return_type_notation): Think of some better way of doing this.
+                        let Some(generics) = self.tcx.hir_owner_node(hir_id.owner).generics()
+                        else {
+                            return;
+                        };
+
+                        // Look for the first bound that contains an associated type that
+                        // matches the segment that we're looking for. We ignore any subsequent
+                        // bounds since we'll be emitting a hard error in HIR lowering, so this
+                        // is purely speculative.
+                        let one_bound = generics.predicates.iter().find_map(|predicate| {
+                            let hir::WherePredicate::BoundPredicate(predicate) = predicate else {
+                                return None;
+                            };
+                            let hir::TyKind::Path(hir::QPath::Resolved(None, bounded_path)) =
+                                predicate.bounded_ty.kind
+                            else {
+                                return None;
+                            };
+                            if bounded_path.res != path.res {
+                                return None;
+                            }
+                            predicate.bounds.iter().find_map(|bound| {
+                                let hir::GenericBound::Trait(trait_, _) = bound else {
+                                    return None;
+                                };
+                                BoundVarContext::supertrait_hrtb_vars(
+                                    self.tcx,
+                                    trait_.trait_ref.trait_def_id()?,
+                                    item_segment.ident,
+                                    ty::AssocKind::Fn,
+                                )
+                            })
+                        });
+                        let Some((bound_vars, assoc_item)) = one_bound else {
+                            return;
+                        };
+                        (bound_vars, assoc_item.def_id, item_segment)
+                    }
+                    // If we have a self type alias (in an impl), try to resolve an
+                    // associated item from one of the supertraits of the impl's trait.
+                    Res::SelfTyAlias { alias_to: impl_def_id, is_trait_impl: true, .. } => {
+                        let hir::ItemKind::Impl(hir::Impl { of_trait: Some(trait_ref), .. }) = self
+                            .tcx
+                            .hir_node_by_def_id(impl_def_id.expect_local())
+                            .expect_item()
+                            .kind
+                        else {
+                            return;
+                        };
+                        let Some(trait_def_id) = trait_ref.trait_def_id() else {
+                            return;
+                        };
+                        let Some((bound_vars, assoc_item)) = BoundVarContext::supertrait_hrtb_vars(
+                            self.tcx,
+                            trait_def_id,
+                            item_segment.ident,
+                            ty::AssocKind::Fn,
+                        ) else {
+                            return;
+                        };
+                        (bound_vars, assoc_item.def_id, item_segment)
+                    }
+                    _ => return,
+                }
+            }
+
+            _ => return,
+        };
+
+        // Append the early-bound vars on the function, and then the late-bound ones.
+        // We actually turn type parameters into higher-ranked types here, but we
+        // deny them later in HIR lowering.
+        bound_vars.extend(self.tcx.generics_of(item_def_id).own_params.iter().map(|param| {
+            match param.kind {
+                ty::GenericParamDefKind::Lifetime => ty::BoundVariableKind::Region(
+                    ty::BoundRegionKind::BrNamed(param.def_id, param.name),
+                ),
+                ty::GenericParamDefKind::Type { .. } => {
+                    ty::BoundVariableKind::Ty(ty::BoundTyKind::Param(param.def_id, param.name))
+                }
+                ty::GenericParamDefKind::Const { .. } => ty::BoundVariableKind::Const,
+            }
+        }));
+        bound_vars.extend(self.tcx.fn_sig(item_def_id).instantiate_identity().bound_vars());
+
+        // SUBTLE: Stash the old bound vars onto the *item segment* before appending
+        // the new bound vars. We do this because we need to know how many bound vars
+        // are present on the binder explicitly (i.e. not return-type-notation vars)
+        // to do bound var shifting correctly in HIR lowering.
+        //
+        // For example, in `where for<'a> <T as Trait<'a>>::method(..): Other`,
+        // the `late_bound_vars` of the where clause predicate (i.e. this HIR ty's
+        // parent) will include `'a` AND all the early- and late-bound vars of the
+        // method. But when lowering the RTN type, we just want the list of vars
+        // we used to resolve the trait ref. We explicitly stored those back onto
+        // the item segment, since there's no other good place to put them.
+        //
+        // See where these vars are used in `HirTyLowerer::lower_ty_maybe_return_type_notation`.
+        // And this is exercised in:
+        // `tests/ui/associated-type-bounds/return-type-notation/higher-ranked-bound-works.rs`.
+        let existing_bound_vars = self.map.late_bound_vars.get_mut(&hir_id).unwrap();
+        let existing_bound_vars_saved = existing_bound_vars.clone();
+        existing_bound_vars.extend(bound_vars);
+        self.record_late_bound_vars(item_segment.hir_id, existing_bound_vars_saved);
+    }
 }
 
 /// Detects late-bound lifetimes and inserts them into
diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs
index e4e5f76ae32..4edb68e6199 100644
--- a/compiler/rustc_hir_analysis/src/errors.rs
+++ b/compiler/rustc_hir_analysis/src/errors.rs
@@ -780,14 +780,15 @@ pub(crate) struct PlaceholderNotAllowedItemSignatures {
 
 #[derive(Diagnostic)]
 #[diag(hir_analysis_associated_type_trait_uninferred_generic_params, code = E0212)]
-pub(crate) struct AssociatedTypeTraitUninferredGenericParams {
+pub(crate) struct AssociatedItemTraitUninferredGenericParams {
     #[primary_span]
     pub span: Span,
     #[suggestion(style = "verbose", applicability = "maybe-incorrect", code = "{bound}")]
     pub inferred_sugg: Option<Span>,
     pub bound: String,
     #[subdiagnostic]
-    pub mpart_sugg: Option<AssociatedTypeTraitUninferredGenericParamsMultipartSuggestion>,
+    pub mpart_sugg: Option<AssociatedItemTraitUninferredGenericParamsMultipartSuggestion>,
+    pub what: &'static str,
 }
 
 #[derive(Subdiagnostic)]
@@ -795,7 +796,7 @@ pub(crate) struct AssociatedTypeTraitUninferredGenericParams {
     hir_analysis_associated_type_trait_uninferred_generic_params_multipart_suggestion,
     applicability = "maybe-incorrect"
 )]
-pub(crate) struct AssociatedTypeTraitUninferredGenericParamsMultipartSuggestion {
+pub(crate) struct AssociatedItemTraitUninferredGenericParamsMultipartSuggestion {
     #[suggestion_part(code = "{first}")]
     pub fspan: Span,
     pub first: String,
@@ -1693,3 +1694,10 @@ pub(crate) struct CmseCallGeneric {
     #[primary_span]
     pub span: Span,
 }
+
+#[derive(Diagnostic)]
+#[diag(hir_analysis_bad_return_type_notation_position)]
+pub(crate) struct BadReturnTypeNotation {
+    #[primary_span]
+    pub span: Span,
+}
diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs
index bffe68f9b74..6f7f3128347 100644
--- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs
+++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs
@@ -6,6 +6,7 @@ use rustc_errors::struct_span_code_err;
 use rustc_hir as hir;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::{DefId, LocalDefId};
+use rustc_hir::HirId;
 use rustc_middle::bug;
 use rustc_middle::ty::{self as ty, IsSuggestable, Ty, TyCtxt};
 use rustc_span::symbol::Ident;
@@ -15,6 +16,7 @@ use rustc_type_ir::visit::{TypeSuperVisitable, TypeVisitable, TypeVisitableExt,
 use smallvec::SmallVec;
 use tracing::{debug, instrument};
 
+use super::errors::GenericsArgsErrExtend;
 use crate::bounds::Bounds;
 use crate::errors;
 use crate::hir_ty_lowering::{
@@ -332,74 +334,11 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
             .or_insert(constraint.span);
 
         let projection_term = if let ty::AssocKind::Fn = assoc_kind {
-            let mut emitted_bad_param_err = None;
-            // If we have an method return type bound, then we need to instantiate
-            // the method's early bound params with suitable late-bound params.
-            let mut num_bound_vars = candidate.bound_vars().len();
-            let args =
-                candidate.skip_binder().args.extend_to(tcx, assoc_item.def_id, |param, _| {
-                    let arg = match param.kind {
-                        ty::GenericParamDefKind::Lifetime => ty::Region::new_bound(
-                            tcx,
-                            ty::INNERMOST,
-                            ty::BoundRegion {
-                                var: ty::BoundVar::from_usize(num_bound_vars),
-                                kind: ty::BoundRegionKind::BrNamed(param.def_id, param.name),
-                            },
-                        )
-                        .into(),
-                        ty::GenericParamDefKind::Type { .. } => {
-                            let guar = *emitted_bad_param_err.get_or_insert_with(|| {
-                                self.dcx().emit_err(
-                                    crate::errors::ReturnTypeNotationIllegalParam::Type {
-                                        span: path_span,
-                                        param_span: tcx.def_span(param.def_id),
-                                    },
-                                )
-                            });
-                            Ty::new_error(tcx, guar).into()
-                        }
-                        ty::GenericParamDefKind::Const { .. } => {
-                            let guar = *emitted_bad_param_err.get_or_insert_with(|| {
-                                self.dcx().emit_err(
-                                    crate::errors::ReturnTypeNotationIllegalParam::Const {
-                                        span: path_span,
-                                        param_span: tcx.def_span(param.def_id),
-                                    },
-                                )
-                            });
-                            ty::Const::new_error(tcx, guar).into()
-                        }
-                    };
-                    num_bound_vars += 1;
-                    arg
-                });
-
-            // Next, we need to check that the return-type notation is being used on
-            // an RPITIT (return-position impl trait in trait) or AFIT (async fn in trait).
-            let output = tcx.fn_sig(assoc_item.def_id).skip_binder().output();
-            let output = if let ty::Alias(ty::Projection, alias_ty) = *output.skip_binder().kind()
-                && tcx.is_impl_trait_in_trait(alias_ty.def_id)
-            {
-                alias_ty.into()
-            } else {
-                return Err(self.dcx().emit_err(crate::errors::ReturnTypeNotationOnNonRpitit {
-                    span: constraint.span,
-                    ty: tcx.liberate_late_bound_regions(assoc_item.def_id, output),
-                    fn_span: tcx.hir().span_if_local(assoc_item.def_id),
-                    note: (),
-                }));
-            };
-
-            // Finally, move the fn return type's bound vars over to account for the early bound
-            // params (and trait ref's late bound params). This logic is very similar to
-            // `rustc_middle::ty::predicate::Clause::instantiate_supertrait`
-            // and it's no coincidence why.
-            let shifted_output = tcx.shift_bound_var_indices(num_bound_vars, output);
-            let instantiation_output = ty::EarlyBinder::bind(shifted_output).instantiate(tcx, args);
-
             let bound_vars = tcx.late_bound_vars(constraint.hir_id);
-            ty::Binder::bind_with_vars(instantiation_output, bound_vars)
+            ty::Binder::bind_with_vars(
+                self.lower_return_type_notation_ty(candidate, assoc_item.def_id, path_span)?.into(),
+                bound_vars,
+            )
         } else {
             // Create the generic arguments for the associated type or constant by joining the
             // parent arguments (the arguments of the trait) and the own arguments (the ones of
@@ -525,6 +464,269 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
         }
         Ok(())
     }
+
+    /// Lower a type, possibly specially handling the type if it's a return type notation
+    /// which we otherwise deny in other positions.
+    pub fn lower_ty_maybe_return_type_notation(&self, hir_ty: &hir::Ty<'tcx>) -> Ty<'tcx> {
+        let hir::TyKind::Path(qpath) = hir_ty.kind else {
+            return self.lower_ty(hir_ty);
+        };
+
+        let tcx = self.tcx();
+        match qpath {
+            hir::QPath::Resolved(opt_self_ty, path)
+                if let [mod_segments @ .., trait_segment, item_segment] = &path.segments[..]
+                    && item_segment.args.is_some_and(|args| {
+                        matches!(
+                            args.parenthesized,
+                            hir::GenericArgsParentheses::ReturnTypeNotation
+                        )
+                    }) =>
+            {
+                // We don't allow generics on the module segments.
+                let _ =
+                    self.prohibit_generic_args(mod_segments.iter(), GenericsArgsErrExtend::None);
+
+                let item_def_id = match path.res {
+                    Res::Def(DefKind::AssocFn, item_def_id) => item_def_id,
+                    Res::Err => {
+                        return Ty::new_error_with_message(
+                            tcx,
+                            hir_ty.span,
+                            "failed to resolve RTN",
+                        );
+                    }
+                    _ => bug!("only expected method resolution for fully qualified RTN"),
+                };
+                let trait_def_id = tcx.parent(item_def_id);
+
+                // Good error for `where Trait::method(..): Send`.
+                let Some(self_ty) = opt_self_ty else {
+                    return self.error_missing_qpath_self_ty(
+                        trait_def_id,
+                        hir_ty.span,
+                        item_segment,
+                    );
+                };
+                let self_ty = self.lower_ty(self_ty);
+
+                let trait_ref = self.lower_mono_trait_ref(
+                    hir_ty.span,
+                    trait_def_id,
+                    self_ty,
+                    trait_segment,
+                    false,
+                    ty::BoundConstness::NotConst,
+                );
+
+                // SUBTLE: As noted at the end of `try_append_return_type_notation_params`
+                // in `resolve_bound_vars`, we stash the explicit bound vars of the where
+                // clause onto the item segment of the RTN type. This allows us to know
+                // how many bound vars are *not* coming from the signature of the function
+                // from lowering RTN itself.
+                //
+                // For example, in `where for<'a> <T as Trait<'a>>::method(..): Other`,
+                // the `late_bound_vars` of the where clause predicate (i.e. this HIR ty's
+                // parent) will include `'a` AND all the early- and late-bound vars of the
+                // method. But when lowering the RTN type, we just want the list of vars
+                // we used to resolve the trait ref. We explicitly stored those back onto
+                // the item segment, since there's no other good place to put them.
+                let candidate =
+                    ty::Binder::bind_with_vars(trait_ref, tcx.late_bound_vars(item_segment.hir_id));
+
+                match self.lower_return_type_notation_ty(candidate, item_def_id, hir_ty.span) {
+                    Ok(ty) => Ty::new_alias(tcx, ty::Projection, ty),
+                    Err(guar) => Ty::new_error(tcx, guar),
+                }
+            }
+            hir::QPath::TypeRelative(qself, item_segment)
+                if item_segment.args.is_some_and(|args| {
+                    matches!(args.parenthesized, hir::GenericArgsParentheses::ReturnTypeNotation)
+                }) =>
+            {
+                match self
+                    .resolve_type_relative_return_type_notation(
+                        qself,
+                        item_segment,
+                        hir_ty.hir_id,
+                        hir_ty.span,
+                    )
+                    .and_then(|(candidate, item_def_id)| {
+                        self.lower_return_type_notation_ty(candidate, item_def_id, hir_ty.span)
+                    }) {
+                    Ok(ty) => Ty::new_alias(tcx, ty::Projection, ty),
+                    Err(guar) => Ty::new_error(tcx, guar),
+                }
+            }
+            _ => self.lower_ty(hir_ty),
+        }
+    }
+
+    /// Perform type-dependent lookup for a *method* for return type notation.
+    /// This generally mirrors `<dyn HirTyLowerer>::lower_assoc_path`.
+    fn resolve_type_relative_return_type_notation(
+        &self,
+        qself: &'tcx hir::Ty<'tcx>,
+        item_segment: &'tcx hir::PathSegment<'tcx>,
+        qpath_hir_id: HirId,
+        span: Span,
+    ) -> Result<(ty::PolyTraitRef<'tcx>, DefId), ErrorGuaranteed> {
+        let tcx = self.tcx();
+        let qself_ty = self.lower_ty(qself);
+        let assoc_ident = item_segment.ident;
+        let qself_res = if let hir::TyKind::Path(hir::QPath::Resolved(_, path)) = &qself.kind {
+            path.res
+        } else {
+            Res::Err
+        };
+
+        let bound = match (qself_ty.kind(), qself_res) {
+            (_, Res::SelfTyAlias { alias_to: impl_def_id, is_trait_impl: true, .. }) => {
+                // `Self` in an impl of a trait -- we have a concrete self type and a
+                // trait reference.
+                let Some(trait_ref) = tcx.impl_trait_ref(impl_def_id) else {
+                    // A cycle error occurred, most likely.
+                    self.dcx().span_bug(span, "expected cycle error");
+                };
+
+                self.probe_single_bound_for_assoc_item(
+                    || {
+                        traits::supertraits(
+                            tcx,
+                            ty::Binder::dummy(trait_ref.instantiate_identity()),
+                        )
+                    },
+                    AssocItemQSelf::SelfTyAlias,
+                    ty::AssocKind::Fn,
+                    assoc_ident,
+                    span,
+                    None,
+                )?
+            }
+            (
+                &ty::Param(_),
+                Res::SelfTyParam { trait_: param_did } | Res::Def(DefKind::TyParam, param_did),
+            ) => self.probe_single_ty_param_bound_for_assoc_item(
+                param_did.expect_local(),
+                qself.span,
+                ty::AssocKind::Fn,
+                assoc_ident,
+                span,
+            )?,
+            _ => {
+                if let Err(reported) = qself_ty.error_reported() {
+                    return Err(reported);
+                } else {
+                    // FIXME(return_type_notation): Provide some structured suggestion here.
+                    let err = struct_span_code_err!(
+                        self.dcx(),
+                        span,
+                        E0223,
+                        "ambiguous associated function"
+                    );
+                    return Err(err.emit());
+                }
+            }
+        };
+
+        // Don't let `T::method` resolve to some `for<'a> <T as Tr<'a>>::method`,
+        // which may happen via a higher-ranked where clause or supertrait.
+        // This is the same restrictions as associated types; even though we could
+        // support it, it just makes things a lot more difficult to support in
+        // `resolve_bound_vars`, since we'd need to introduce those as elided
+        // bound vars on the where clause too.
+        if bound.has_bound_vars() {
+            return Err(self.tcx().dcx().emit_err(
+                errors::AssociatedItemTraitUninferredGenericParams {
+                    span,
+                    inferred_sugg: Some(span.with_hi(item_segment.ident.span.lo())),
+                    bound: format!("{}::", tcx.anonymize_bound_vars(bound).skip_binder(),),
+                    mpart_sugg: None,
+                    what: "function",
+                },
+            ));
+        }
+
+        let trait_def_id = bound.def_id();
+        let assoc_ty = self
+            .probe_assoc_item(assoc_ident, ty::AssocKind::Fn, qpath_hir_id, span, trait_def_id)
+            .expect("failed to find associated type");
+
+        Ok((bound, assoc_ty.def_id))
+    }
+
+    /// Do the common parts of lowering an RTN type. This involves extending the
+    /// candidate binder to include all of the early- and late-bound vars that are
+    /// defined on the function itself, and constructing a projection to the RPITIT
+    /// return type of that function.
+    fn lower_return_type_notation_ty(
+        &self,
+        candidate: ty::PolyTraitRef<'tcx>,
+        item_def_id: DefId,
+        path_span: Span,
+    ) -> Result<ty::AliasTy<'tcx>, ErrorGuaranteed> {
+        let tcx = self.tcx();
+        let mut emitted_bad_param_err = None;
+        // If we have an method return type bound, then we need to instantiate
+        // the method's early bound params with suitable late-bound params.
+        let mut num_bound_vars = candidate.bound_vars().len();
+        let args = candidate.skip_binder().args.extend_to(tcx, item_def_id, |param, _| {
+            let arg = match param.kind {
+                ty::GenericParamDefKind::Lifetime => ty::Region::new_bound(
+                    tcx,
+                    ty::INNERMOST,
+                    ty::BoundRegion {
+                        var: ty::BoundVar::from_usize(num_bound_vars),
+                        kind: ty::BoundRegionKind::BrNamed(param.def_id, param.name),
+                    },
+                )
+                .into(),
+                ty::GenericParamDefKind::Type { .. } => {
+                    let guar = *emitted_bad_param_err.get_or_insert_with(|| {
+                        self.dcx().emit_err(crate::errors::ReturnTypeNotationIllegalParam::Type {
+                            span: path_span,
+                            param_span: tcx.def_span(param.def_id),
+                        })
+                    });
+                    Ty::new_error(tcx, guar).into()
+                }
+                ty::GenericParamDefKind::Const { .. } => {
+                    let guar = *emitted_bad_param_err.get_or_insert_with(|| {
+                        self.dcx().emit_err(crate::errors::ReturnTypeNotationIllegalParam::Const {
+                            span: path_span,
+                            param_span: tcx.def_span(param.def_id),
+                        })
+                    });
+                    ty::Const::new_error(tcx, guar).into()
+                }
+            };
+            num_bound_vars += 1;
+            arg
+        });
+
+        // Next, we need to check that the return-type notation is being used on
+        // an RPITIT (return-position impl trait in trait) or AFIT (async fn in trait).
+        let output = tcx.fn_sig(item_def_id).skip_binder().output();
+        let output = if let ty::Alias(ty::Projection, alias_ty) = *output.skip_binder().kind()
+            && tcx.is_impl_trait_in_trait(alias_ty.def_id)
+        {
+            alias_ty
+        } else {
+            return Err(self.dcx().emit_err(crate::errors::ReturnTypeNotationOnNonRpitit {
+                span: path_span,
+                ty: tcx.liberate_late_bound_regions(item_def_id, output),
+                fn_span: tcx.hir().span_if_local(item_def_id),
+                note: (),
+            }));
+        };
+
+        // Finally, move the fn return type's bound vars over to account for the early bound
+        // params (and trait ref's late bound params). This logic is very similar to
+        // `rustc_middle::ty::predicate::Clause::instantiate_supertrait`
+        // and it's no coincidence why.
+        let shifted_output = tcx.shift_bound_var_indices(num_bound_vars, output);
+        Ok(ty::EarlyBinder::bind(shifted_output).instantiate(tcx, args))
+    }
 }
 
 /// Detect and reject early-bound & escaping late-bound generic params in the type of assoc const bindings.
diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
index 7163352e8a4..35eebcb6be1 100644
--- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
+++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
@@ -53,7 +53,7 @@ use rustc_trait_selection::traits::{self, ObligationCtxt};
 use tracing::{debug, debug_span, instrument};
 
 use crate::bounds::Bounds;
-use crate::errors::{AmbiguousLifetimeBound, WildPatTy};
+use crate::errors::{AmbiguousLifetimeBound, BadReturnTypeNotation, WildPatTy};
 use crate::hir_ty_lowering::errors::{prohibit_assoc_item_constraint, GenericsArgsErrExtend};
 use crate::hir_ty_lowering::generics::{check_generic_arg_count, lower_generic_args};
 use crate::middle::resolve_bound_vars as rbv;
@@ -719,6 +719,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
             span,
             polarity,
             constness,
+            only_self_bounds,
         );
 
         let mut dup_constraints = FxIndexMap::default();
@@ -813,17 +814,19 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
         }
     }
 
-    /// Search for a trait bound on a type parameter whose trait defines the associated type given by `assoc_name`.
+    /// Search for a trait bound on a type parameter whose trait defines the associated item
+    /// given by `assoc_name` and `kind`.
     ///
     /// This fails if there is no such bound in the list of candidates or if there are multiple
     /// candidates in which case it reports ambiguity.
     ///
     /// `ty_param_def_id` is the `LocalDefId` of the type parameter.
     #[instrument(level = "debug", skip_all, ret)]
-    fn probe_single_ty_param_bound_for_assoc_ty(
+    fn probe_single_ty_param_bound_for_assoc_item(
         &self,
         ty_param_def_id: LocalDefId,
         ty_param_span: Span,
+        kind: ty::AssocKind,
         assoc_name: Ident,
         span: Span,
     ) -> Result<ty::PolyTraitRef<'tcx>, ErrorGuaranteed> {
@@ -841,7 +844,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
                 traits::transitive_bounds_that_define_assoc_item(tcx, trait_refs, assoc_name)
             },
             AssocItemQSelf::TyParam(ty_param_def_id, ty_param_span),
-            ty::AssocKind::Type,
+            kind,
             assoc_name,
             span,
             None,
@@ -1081,9 +1084,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
             (
                 &ty::Param(_),
                 Res::SelfTyParam { trait_: param_did } | Res::Def(DefKind::TyParam, param_did),
-            ) => self.probe_single_ty_param_bound_for_assoc_ty(
+            ) => self.probe_single_ty_param_bound_for_assoc_item(
                 param_did.expect_local(),
                 qself.span,
+                ty::AssocKind::Type,
                 assoc_ident,
                 span,
             )?,
@@ -1545,48 +1549,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
         debug!(?trait_def_id);
 
         let Some(self_ty) = opt_self_ty else {
-            let path_str = tcx.def_path_str(trait_def_id);
-
-            let def_id = self.item_def_id();
-            debug!(item_def_id = ?def_id);
-
-            // FIXME: document why/how this is different from `tcx.local_parent(def_id)`
-            let parent_def_id =
-                tcx.hir().get_parent_item(tcx.local_def_id_to_hir_id(def_id)).to_def_id();
-            debug!(?parent_def_id);
-
-            // If the trait in segment is the same as the trait defining the item,
-            // use the `<Self as ..>` syntax in the error.
-            let is_part_of_self_trait_constraints = def_id.to_def_id() == trait_def_id;
-            let is_part_of_fn_in_self_trait = parent_def_id == trait_def_id;
-
-            let type_names = if is_part_of_self_trait_constraints || is_part_of_fn_in_self_trait {
-                vec!["Self".to_string()]
-            } else {
-                // Find all the types that have an `impl` for the trait.
-                tcx.all_impls(trait_def_id)
-                    .filter_map(|impl_def_id| tcx.impl_trait_header(impl_def_id))
-                    .filter(|header| {
-                        // Consider only accessible traits
-                        tcx.visibility(trait_def_id).is_accessible_from(self.item_def_id(), tcx)
-                            && header.polarity != ty::ImplPolarity::Negative
-                    })
-                    .map(|header| header.trait_ref.instantiate_identity().self_ty())
-                    // We don't care about blanket impls.
-                    .filter(|self_ty| !self_ty.has_non_region_param())
-                    .map(|self_ty| tcx.erase_regions(self_ty).to_string())
-                    .collect()
-            };
-            // FIXME: also look at `tcx.generics_of(self.item_def_id()).params` any that
-            // references the trait. Relevant for the first case in
-            // `src/test/ui/associated-types/associated-types-in-ambiguous-context.rs`
-            let reported = self.report_ambiguous_assoc_ty(
-                span,
-                &type_names,
-                &[path_str],
-                item_segment.ident.name,
-            );
-            return Ty::new_error(tcx, reported);
+            return self.error_missing_qpath_self_ty(trait_def_id, span, item_segment);
         };
         debug!(?self_ty);
 
@@ -1600,6 +1563,53 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
         Ty::new_projection_from_args(tcx, item_def_id, item_args)
     }
 
+    fn error_missing_qpath_self_ty(
+        &self,
+        trait_def_id: DefId,
+        span: Span,
+        item_segment: &hir::PathSegment<'tcx>,
+    ) -> Ty<'tcx> {
+        let tcx = self.tcx();
+        let path_str = tcx.def_path_str(trait_def_id);
+
+        let def_id = self.item_def_id();
+        debug!(item_def_id = ?def_id);
+
+        // FIXME: document why/how this is different from `tcx.local_parent(def_id)`
+        let parent_def_id =
+            tcx.hir().get_parent_item(tcx.local_def_id_to_hir_id(def_id)).to_def_id();
+        debug!(?parent_def_id);
+
+        // If the trait in segment is the same as the trait defining the item,
+        // use the `<Self as ..>` syntax in the error.
+        let is_part_of_self_trait_constraints = def_id.to_def_id() == trait_def_id;
+        let is_part_of_fn_in_self_trait = parent_def_id == trait_def_id;
+
+        let type_names = if is_part_of_self_trait_constraints || is_part_of_fn_in_self_trait {
+            vec!["Self".to_string()]
+        } else {
+            // Find all the types that have an `impl` for the trait.
+            tcx.all_impls(trait_def_id)
+                .filter_map(|impl_def_id| tcx.impl_trait_header(impl_def_id))
+                .filter(|header| {
+                    // Consider only accessible traits
+                    tcx.visibility(trait_def_id).is_accessible_from(self.item_def_id(), tcx)
+                        && header.polarity != ty::ImplPolarity::Negative
+                })
+                .map(|header| header.trait_ref.instantiate_identity().self_ty())
+                // We don't care about blanket impls.
+                .filter(|self_ty| !self_ty.has_non_region_param())
+                .map(|self_ty| tcx.erase_regions(self_ty).to_string())
+                .collect()
+        };
+        // FIXME: also look at `tcx.generics_of(self.item_def_id()).params` any that
+        // references the trait. Relevant for the first case in
+        // `src/test/ui/associated-types/associated-types-in-ambiguous-context.rs`
+        let reported =
+            self.report_ambiguous_assoc_ty(span, &type_names, &[path_str], item_segment.ident.name);
+        Ty::new_error(tcx, reported)
+    }
+
     pub fn prohibit_generic_args<'a>(
         &self,
         segments: impl Iterator<Item = &'a hir::PathSegment<'a>> + Clone,
@@ -1930,7 +1940,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
                     .tcx()
                     .dcx()
                     .span_delayed_bug(path.span, "path with `Res::Err` but no error emitted");
-                Ty::new_error(self.tcx(), e)
+                Ty::new_error(tcx, e)
             }
             Res::Def(..) => {
                 assert_eq!(
@@ -2061,6 +2071,17 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
                 };
                 self.lower_trait_object_ty(hir_ty.span, hir_ty.hir_id, bounds, lifetime, repr)
             }
+            // If we encounter a fully qualified path with RTN generics, then it must have
+            // *not* gone through `lower_ty_maybe_return_type_notation`, and therefore
+            // it's certainly in an illegal position.
+            hir::TyKind::Path(hir::QPath::Resolved(_, path))
+                if path.segments.last().and_then(|segment| segment.args).is_some_and(|args| {
+                    matches!(args.parenthesized, hir::GenericArgsParentheses::ReturnTypeNotation)
+                }) =>
+            {
+                let guar = self.dcx().emit_err(BadReturnTypeNotation { span: hir_ty.span });
+                Ty::new_error(tcx, guar)
+            }
             hir::TyKind::Path(hir::QPath::Resolved(maybe_qself, path)) => {
                 debug!(?maybe_qself, ?path);
                 let opt_self_ty = maybe_qself.as_ref().map(|qself| self.lower_ty(qself));
@@ -2085,6 +2106,17 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
                     ref i => bug!("`impl Trait` pointed to non-opaque type?? {:#?}", i),
                 }
             }
+            // If we encounter a type relative path with RTN generics, then it must have
+            // *not* gone through `lower_ty_maybe_return_type_notation`, and therefore
+            // it's certainly in an illegal position.
+            hir::TyKind::Path(hir::QPath::TypeRelative(_, segment))
+                if segment.args.is_some_and(|args| {
+                    matches!(args.parenthesized, hir::GenericArgsParentheses::ReturnTypeNotation)
+                }) =>
+            {
+                let guar = self.dcx().emit_err(BadReturnTypeNotation { span: hir_ty.span });
+                Ty::new_error(tcx, guar)
+            }
             hir::TyKind::Path(hir::QPath::TypeRelative(qself, segment)) => {
                 debug!(?qself, ?segment);
                 let ty = self.lower_ty(qself);