diff --git a/compiler/rustc_middle/src/traits/query.rs b/compiler/rustc_middle/src/traits/query.rs
index fb152b63f6344..5ea40868f0916 100644
--- a/compiler/rustc_middle/src/traits/query.rs
+++ b/compiler/rustc_middle/src/traits/query.rs
@@ -109,6 +109,45 @@ impl<'tcx> From<TypeError<'tcx>> for NoSolution {
     }
 }
 
+#[derive(Copy, Clone, Debug, HashStable)]
+pub enum NoSolutionOrAmbiguous {
+    NoSolution,
+    Ambiguous,
+}
+
+impl NoSolutionOrAmbiguous {
+    // Expect unambiguous errors only
+    pub fn expect_unambiguous(self) -> NoSolution {
+        match self {
+            NoSolutionOrAmbiguous::NoSolution => NoSolution,
+            NoSolutionOrAmbiguous::Ambiguous => bug!("unexpected ambiguity"),
+        }
+    }
+
+    /// Delay an ambiguity as a `delay_span_bug`.
+    pub fn delay_ambiguous(self, tcx: TyCtxt<'_>, span: Span) -> NoSolution {
+        match self {
+            NoSolutionOrAmbiguous::NoSolution => NoSolution,
+            NoSolutionOrAmbiguous::Ambiguous => {
+                tcx.sess.delay_span_bug(span, "unexpected ambiguity");
+                NoSolution
+            }
+        }
+    }
+}
+
+impl From<NoSolution> for NoSolutionOrAmbiguous {
+    fn from(_: NoSolution) -> NoSolutionOrAmbiguous {
+        NoSolutionOrAmbiguous::NoSolution
+    }
+}
+
+impl<'tcx> From<TypeError<'tcx>> for NoSolutionOrAmbiguous {
+    fn from(_: TypeError<'tcx>) -> NoSolutionOrAmbiguous {
+        NoSolutionOrAmbiguous::NoSolution
+    }
+}
+
 #[derive(Clone, Debug, Default, HashStable, TypeFoldable, TypeVisitable, Lift)]
 pub struct DropckOutlivesResult<'tcx> {
     pub kinds: Vec<GenericArg<'tcx>>,
diff --git a/compiler/rustc_trait_selection/src/traits/query/normalize.rs b/compiler/rustc_trait_selection/src/traits/query/normalize.rs
index aa8094a60dd08..d6066677a4172 100644
--- a/compiler/rustc_trait_selection/src/traits/query/normalize.rs
+++ b/compiler/rustc_trait_selection/src/traits/query/normalize.rs
@@ -17,12 +17,12 @@ use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitor};
 
 use std::ops::ControlFlow;
 
-use super::NoSolution;
+use super::NoSolutionOrAmbiguous;
 
 pub use rustc_middle::traits::query::NormalizationResult;
 
 pub trait AtExt<'tcx> {
-    fn normalize<T>(&self, value: T) -> Result<Normalized<'tcx, T>, NoSolution>
+    fn normalize<T>(&self, value: T) -> Result<Normalized<'tcx, T>, NoSolutionOrAmbiguous>
     where
         T: TypeFoldable<'tcx>;
 }
@@ -41,7 +41,7 @@ impl<'cx, 'tcx> AtExt<'tcx> for At<'cx, 'tcx> {
     /// normalizing, but for now should be used only when we actually
     /// know that normalization will succeed, since error reporting
     /// and other details are still "under development".
-    fn normalize<T>(&self, value: T) -> Result<Normalized<'tcx, T>, NoSolution>
+    fn normalize<T>(&self, value: T) -> Result<Normalized<'tcx, T>, NoSolutionOrAmbiguous>
     where
         T: TypeFoldable<'tcx>,
     {
@@ -96,7 +96,7 @@ impl<'cx, 'tcx> AtExt<'tcx> for At<'cx, 'tcx> {
             std::any::type_name::<T>(),
             normalizer.obligations,
         );
-        result.map(|value| Normalized { value, obligations: normalizer.obligations })
+        Ok(Normalized { value: result?, obligations: normalizer.obligations })
     }
 }
 
@@ -163,7 +163,7 @@ struct QueryNormalizer<'cx, 'tcx> {
 }
 
 impl<'cx, 'tcx> FallibleTypeFolder<'tcx> for QueryNormalizer<'cx, 'tcx> {
-    type Error = NoSolution;
+    type Error = NoSolutionOrAmbiguous;
 
     fn tcx<'c>(&'c self) -> TyCtxt<'tcx> {
         self.infcx.tcx
@@ -253,7 +253,7 @@ impl<'cx, 'tcx> FallibleTypeFolder<'tcx> for QueryNormalizer<'cx, 'tcx> {
                 let result = tcx.normalize_projection_ty(c_data)?;
                 // We don't expect ambiguity.
                 if result.is_ambiguous() {
-                    bug!("unexpected ambiguity: {:?} {:?}", c_data, result);
+                    return Err(NoSolutionOrAmbiguous::Ambiguous);
                 }
                 let InferOk { value: result, obligations } =
                     self.infcx.instantiate_query_response_and_region_obligations(
@@ -296,7 +296,7 @@ impl<'cx, 'tcx> FallibleTypeFolder<'tcx> for QueryNormalizer<'cx, 'tcx> {
                 let result = tcx.normalize_projection_ty(c_data)?;
                 // We don't expect ambiguity.
                 if result.is_ambiguous() {
-                    bug!("unexpected ambiguity: {:?} {:?}", c_data, result);
+                    return Err(NoSolutionOrAmbiguous::Ambiguous);
                 }
                 let InferOk { value: result, obligations } =
                     self.infcx.instantiate_query_response_and_region_obligations(
diff --git a/compiler/rustc_traits/src/dropck_outlives.rs b/compiler/rustc_traits/src/dropck_outlives.rs
index d5a8ca5ea784a..cce9007c08360 100644
--- a/compiler/rustc_traits/src/dropck_outlives.rs
+++ b/compiler/rustc_traits/src/dropck_outlives.rs
@@ -131,8 +131,8 @@ fn dropck_outlives<'tcx>(
 
                 // We don't actually expect to fail to normalize.
                 // That implies a WF error somewhere else.
-                Err(NoSolution) => {
-                    return Err(NoSolution);
+                Err(err) => {
+                    return Err(err.expect_unambiguous());
                 }
             }
         }
diff --git a/compiler/rustc_traits/src/normalize_erasing_regions.rs b/compiler/rustc_traits/src/normalize_erasing_regions.rs
index 2da64d73d34ac..d676f3faadc7f 100644
--- a/compiler/rustc_traits/src/normalize_erasing_regions.rs
+++ b/compiler/rustc_traits/src/normalize_erasing_regions.rs
@@ -48,7 +48,7 @@ fn try_normalize_after_erasing_regions<'tcx, T: TypeFoldable<'tcx> + PartialEq +
             debug_assert!(!erased.needs_infer(), "{:?}", erased);
             Ok(erased)
         }
-        Err(NoSolution) => Err(NoSolution),
+        Err(err) => Err(err.expect_unambiguous()),
     }
 }
 
diff --git a/compiler/rustc_traits/src/type_op.rs b/compiler/rustc_traits/src/type_op.rs
index bca7458ed332b..fa9c71d1d02fd 100644
--- a/compiler/rustc_traits/src/type_op.rs
+++ b/compiler/rustc_traits/src/type_op.rs
@@ -218,8 +218,10 @@ where
     T: fmt::Debug + TypeFoldable<'tcx> + Lift<'tcx>,
 {
     let (param_env, Normalize { value }) = key.into_parts();
-    let Normalized { value, obligations } =
-        infcx.at(&ObligationCause::dummy(), param_env).normalize(value)?;
+    let Normalized { value, obligations } = infcx
+        .at(&ObligationCause::dummy(), param_env)
+        .normalize(value)
+        .map_err(|err| err.delay_ambiguous(infcx.tcx, DUMMY_SP))?;
     fulfill_cx.register_predicate_obligations(infcx, obligations);
     Ok(value)
 }
diff --git a/src/test/rustdoc/issue-102835.rs b/src/test/rustdoc/issue-102835.rs
new file mode 100644
index 0000000000000..253dd8d6ce6f8
--- /dev/null
+++ b/src/test/rustdoc/issue-102835.rs
@@ -0,0 +1,21 @@
+// compile-flags: -Znormalize-docs
+
+#![feature(type_alias_impl_trait)]
+
+trait Allocator {
+    type Buffer;
+}
+
+struct DefaultAllocator;
+
+impl<T> Allocator for DefaultAllocator {
+    type Buffer = ();
+}
+
+type A = impl Fn(<DefaultAllocator as Allocator>::Buffer);
+
+fn foo() -> A {
+    |_| ()
+}
+
+fn main() {}
diff --git a/src/test/ui/impl-trait/issue-103181-1.rs b/src/test/ui/impl-trait/issue-103181-1.rs
new file mode 100644
index 0000000000000..197aedf9d98bc
--- /dev/null
+++ b/src/test/ui/impl-trait/issue-103181-1.rs
@@ -0,0 +1,85 @@
+// edition:2021
+
+mod hyper {
+    use std::{fmt::Debug, future::Future, marker::PhantomData, pin::Pin, task::Poll};
+
+    pub trait HttpBody {
+        type Error;
+    }
+    impl HttpBody for () {
+        //~^ ERROR not all trait items implemented, missing: `Error`
+        // don't implement `Error` here for the ICE
+    }
+
+    pub struct Server<I, S>(I, S);
+
+    pub fn serve<I, S>(_: S) -> Server<I, S> {
+        todo!()
+    }
+
+    impl<S, B> Future for Server<(), S>
+    where
+        S: MakeServiceRef<(), (), ResBody = B>,
+        B: HttpBody,
+        B::Error: Debug,
+    {
+        type Output = ();
+
+        fn poll(self: Pin<&mut Self>, _: &mut std::task::Context<'_>) -> Poll<Self::Output> {
+            todo!()
+        }
+    }
+
+    pub trait MakeServiceRef<Target, ReqBody> {
+        type ResBody;
+    }
+
+    impl<T, S> MakeServiceRef<(), ()> for T
+    where
+        T: for<'a> Service<&'a (), Response = S>,
+        S: Service<()>,
+    {
+        type ResBody = ();
+    }
+
+    pub struct MakeServiceFn<F>(pub F);
+    pub struct ServiceFn<F, R>(pub PhantomData<(F, R)>);
+
+    pub trait Service<Request> {
+        type Response;
+    }
+
+    impl<'t, F, Ret, Target, Svc> Service<&'t Target> for MakeServiceFn<F>
+    where
+        F: Fn() -> Ret,
+        Ret: Future<Output = Result<Svc, ()>>,
+    {
+        type Response = Svc;
+    }
+
+    impl<F, ReqBody, Ret, ResBody, E> Service<ReqBody> for ServiceFn<F, ReqBody>
+    where
+        F: Fn() -> Ret,
+        Ret: Future<Output = Result<ResBody, E>>,
+    {
+        type Response = ResBody;
+    }
+}
+
+async fn smarvice() -> Result<(), ()> {
+    Ok(())
+}
+
+fn service_fn<F, R, S>(f: F) -> hyper::ServiceFn<F, R>
+where
+    F: Fn() -> S,
+{
+    hyper::ServiceFn(std::marker::PhantomData)
+}
+
+async fn iceice() {
+    let service = hyper::MakeServiceFn(|| async { Ok::<_, ()>(service_fn(|| smarvice())) });
+    hyper::serve::<(), _>(service).await;
+}
+
+fn main() {}
diff --git a/src/test/ui/impl-trait/issue-103181-1.stderr b/src/test/ui/impl-trait/issue-103181-1.stderr
new file mode 100644
index 0000000000000..cd026607d52fc
--- /dev/null
+++ b/src/test/ui/impl-trait/issue-103181-1.stderr
@@ -0,0 +1,12 @@
+error[E0046]: not all trait items implemented, missing: `Error`
+  --> $DIR/issue-103181-1.rs:9:5
+   |
+LL |         type Error;
+   |         ---------- `Error` from trait
+LL |     }
+LL |     impl HttpBody for () {
+   |     ^^^^^^^^^^^^^^^^^^^^ missing `Error` in implementation
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0046`.
diff --git a/src/test/ui/impl-trait/issue-103181-2.rs b/src/test/ui/impl-trait/issue-103181-2.rs
new file mode 100644
index 0000000000000..b43ac45075e2b
--- /dev/null
+++ b/src/test/ui/impl-trait/issue-103181-2.rs
@@ -0,0 +1,29 @@
+// edition:2021
+
+trait SendFuture: Send {
+    type Output;
+}
+
+impl<Fut: Send> SendFuture for Fut {
+    type Output = ();
+}
+
+async fn broken_fut() {
+    ident_error;
+    //~^ ERROR cannot find value `ident_error` in this scope
+}
+
+// triggers normalization of `<Fut as SendFuture>::Output`,
+// which requires `Fut: Send`.
+fn normalize<Fut: SendFuture>(_: Fut, _: Fut::Output) {}
+
+async fn iceice<A, B>()
+// <- async fn is necessary
+where
+    A: Send,
+    B: Send, // <- a second bound
+{
+    normalize(broken_fut(), ());
+}
+
+fn main() {}
diff --git a/src/test/ui/impl-trait/issue-103181-2.stderr b/src/test/ui/impl-trait/issue-103181-2.stderr
new file mode 100644
index 0000000000000..5eb2dd9184bec
--- /dev/null
+++ b/src/test/ui/impl-trait/issue-103181-2.stderr
@@ -0,0 +1,9 @@
+error[E0425]: cannot find value `ident_error` in this scope
+  --> $DIR/issue-103181-2.rs:12:5
+   |
+LL |     ident_error;
+   |     ^^^^^^^^^^^ not found in this scope
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0425`.