diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs
index 37caab2da0f5b..22e5be3762df5 100644
--- a/compiler/rustc_lint/src/types.rs
+++ b/compiler/rustc_lint/src/types.rs
@@ -1195,35 +1195,30 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
     }
 
     fn check_for_opaque_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool {
-        struct ProhibitOpaqueTypes<'a, 'tcx> {
-            cx: &'a LateContext<'tcx>,
-        }
-
-        impl<'a, 'tcx> ty::visit::TypeVisitor<'tcx> for ProhibitOpaqueTypes<'a, 'tcx> {
+        struct ProhibitOpaqueTypes;
+        impl<'tcx> ty::visit::TypeVisitor<'tcx> for ProhibitOpaqueTypes {
             type BreakTy = Ty<'tcx>;
 
             fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
-                match ty.kind() {
-                    ty::Opaque(..) => ControlFlow::Break(ty),
-                    // Consider opaque types within projections FFI-safe if they do not normalize
-                    // to more opaque types.
-                    ty::Projection(..) => {
-                        let ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, ty);
-
-                        // If `ty` is an opaque type directly then `super_visit_with` won't invoke
-                        // this function again.
-                        if ty.has_opaque_types() {
-                            self.visit_ty(ty)
-                        } else {
-                            ControlFlow::CONTINUE
-                        }
-                    }
-                    _ => ty.super_visit_with(self),
+                if !ty.has_opaque_types() {
+                    return ControlFlow::CONTINUE;
+                }
+
+                if let ty::Opaque(..) = ty.kind() {
+                    ControlFlow::Break(ty)
+                } else {
+                    ty.super_visit_with(self)
                 }
             }
         }
 
-        if let Some(ty) = ty.visit_with(&mut ProhibitOpaqueTypes { cx: self.cx }).break_value() {
+        if let Some(ty) = self
+            .cx
+            .tcx
+            .normalize_erasing_regions(self.cx.param_env, ty)
+            .visit_with(&mut ProhibitOpaqueTypes)
+            .break_value()
+        {
             self.emit_ffi_unsafe_type_lint(ty, sp, fluent::lint_improper_ctypes_opaque, None);
             true
         } else {
diff --git a/src/test/ui/lint/opaque-ty-ffi-normalization-cycle.rs b/src/test/ui/lint/opaque-ty-ffi-normalization-cycle.rs
new file mode 100644
index 0000000000000..c83bca4a4c570
--- /dev/null
+++ b/src/test/ui/lint/opaque-ty-ffi-normalization-cycle.rs
@@ -0,0 +1,41 @@
+#![feature(type_alias_impl_trait)]
+#![allow(unused)]
+#![deny(improper_ctypes)]
+
+pub trait TraitA {
+    type Assoc;
+}
+
+impl TraitA for u32 {
+    type Assoc = u32;
+}
+
+pub trait TraitB {
+    type Assoc;
+}
+
+impl<T> TraitB for T
+where
+    T: TraitA,
+{
+    type Assoc = <T as TraitA>::Assoc;
+}
+
+type AliasA = impl TraitA<Assoc = u32>;
+
+type AliasB = impl TraitB;
+
+fn use_of_a() -> AliasA {
+    3
+}
+
+fn use_of_b() -> AliasB {
+    3
+}
+
+extern "C" {
+    fn lint_me() -> <AliasB as TraitB>::Assoc;
+    //~^ ERROR `extern` block uses type `AliasB`, which is not FFI-safe
+}
+
+fn main() {}
diff --git a/src/test/ui/lint/opaque-ty-ffi-normalization-cycle.stderr b/src/test/ui/lint/opaque-ty-ffi-normalization-cycle.stderr
new file mode 100644
index 0000000000000..e8d696477ada1
--- /dev/null
+++ b/src/test/ui/lint/opaque-ty-ffi-normalization-cycle.stderr
@@ -0,0 +1,15 @@
+error: `extern` block uses type `AliasB`, which is not FFI-safe
+  --> $DIR/opaque-ty-ffi-normalization-cycle.rs:37:21
+   |
+LL |     fn lint_me() -> <AliasB as TraitB>::Assoc;
+   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe
+   |
+   = note: opaque types have no C equivalent
+note: the lint level is defined here
+  --> $DIR/opaque-ty-ffi-normalization-cycle.rs:3:9
+   |
+LL | #![deny(improper_ctypes)]
+   |         ^^^^^^^^^^^^^^^
+
+error: aborting due to previous error
+