diff --git a/compiler/hsSyn/Convert.hs b/compiler/hsSyn/Convert.hs
index 12f22e8dd37d83ebda1238c2204344fd592141c6..97329aaa553e9e451556c7d28319cd825feea9d9 100644
--- a/compiler/hsSyn/Convert.hs
+++ b/compiler/hsSyn/Convert.hs
@@ -925,7 +925,7 @@ cvtl e = wrapL (cvt e)
                              }
 
     -- Infix expressions
-    cvt (InfixE (Just x) s (Just y)) =
+    cvt (InfixE (Just x) s (Just y)) = ensureValidOpExp s $
       do { x' <- cvtl x
          ; s' <- cvtl s
          ; y' <- cvtl y
@@ -937,20 +937,24 @@ cvtl e = wrapL (cvt e)
            -- to ensure this operator application does
            -- does not get re-associated
            -- See Note [Operator association]
-    cvt (InfixE Nothing  s (Just y)) = do { s' <- cvtl s; y' <- cvtl y
+    cvt (InfixE Nothing  s (Just y)) = ensureValidOpExp s $
+                                       do { s' <- cvtl s; y' <- cvtl y
                                           ; wrapParL (HsPar noExt) $
                                                           SectionR noExt s' y' }
                                             -- See Note [Sections in HsSyn] in HsExpr
-    cvt (InfixE (Just x) s Nothing ) = do { x' <- cvtl x; s' <- cvtl s
+    cvt (InfixE (Just x) s Nothing ) = ensureValidOpExp s $
+                                       do { x' <- cvtl x; s' <- cvtl s
                                           ; wrapParL (HsPar noExt) $
                                                           SectionL noExt x' s' }
 
-    cvt (InfixE Nothing  s Nothing ) = do { s' <- cvtl s
+    cvt (InfixE Nothing  s Nothing ) = ensureValidOpExp s $
+                                       do { s' <- cvtl s
                                           ; return $ HsPar noExt s' }
                                        -- Can I indicate this is an infix thing?
                                        -- Note [Dropping constructors]
 
-    cvt (UInfixE x s y)  = do { x' <- cvtl x
+    cvt (UInfixE x s y)  = ensureValidOpExp s $
+                           do { x' <- cvtl x
                               ; let x'' = case unLoc x' of
                                             OpApp {} -> x'
                                             _ -> mkLHsPar x'
@@ -977,6 +981,24 @@ cvtl e = wrapL (cvt e)
     cvt (LabelE s)       = do { return $ HsOverLabel noExt Nothing (fsLit s) }
     cvt (ImplicitParamVarE n) = do { n' <- ipName n; return $ HsIPVar noExt n' }
 
+{- | #16895 Ensure an infix expression's operator is a variable/constructor.
+Consider this example:
+
+  $(uInfixE [|1|] [|id id|] [|2|])
+
+This infix expression is obviously ill-formed so we use this helper function
+to reject such programs outright.
+
+The constructors `ensureValidOpExp` permits should be in sync with `pprInfixExp`
+in Language.Haskell.TH.Ppr from the template-haskell library.
+-}
+ensureValidOpExp :: TH.Exp -> CvtM a -> CvtM a
+ensureValidOpExp (VarE _n) m = m
+ensureValidOpExp (ConE _n) m = m
+ensureValidOpExp (UnboundVarE _n) m = m
+ensureValidOpExp _e _m =
+    failWith (text "Non-variable expression is not allowed in an infix expression")
+
 {- Note [Dropping constructors]
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 When we drop constructors from the input (for instance, when we encounter @TupE [e]@)
diff --git a/libraries/template-haskell/Language/Haskell/TH/Ppr.hs b/libraries/template-haskell/Language/Haskell/TH/Ppr.hs
index 6eaadd648eaa59247ba7181c771b00d9411a2afb..792a78b6061c78629f861b40b23cc421c6c628fd 100644
--- a/libraries/template-haskell/Language/Haskell/TH/Ppr.hs
+++ b/libraries/template-haskell/Language/Haskell/TH/Ppr.hs
@@ -123,7 +123,10 @@ isSymOcc n
 pprInfixExp :: Exp -> Doc
 pprInfixExp (VarE v) = pprName' Infix v
 pprInfixExp (ConE v) = pprName' Infix v
-pprInfixExp _        = text "<<Non-variable/constructor in infix context>>"
+pprInfixExp (UnboundVarE v) = pprName' Infix v
+-- This case will only ever be reached in exceptional circumstances.
+-- For example, when printing an error message in case of a malformed expression.
+pprInfixExp e = text "`" <> ppr e <> text "`"
 
 pprExp :: Precedence -> Exp -> Doc
 pprExp _ (VarE v)     = pprName' Applied v
diff --git a/libraries/template-haskell/Language/Haskell/TH/Syntax.hs b/libraries/template-haskell/Language/Haskell/TH/Syntax.hs
index 2d79d5a28f6a2a2dd0929148c92197687676009e..f79a8e2b0cf4e19ef3fce1a106832d4265199bce 100644
--- a/libraries/template-haskell/Language/Haskell/TH/Syntax.hs
+++ b/libraries/template-haskell/Language/Haskell/TH/Syntax.hs
@@ -1895,11 +1895,15 @@ data Exp
 
   | InfixE (Maybe Exp) Exp (Maybe Exp) -- ^ @{x + y} or {(x+)} or {(+ x)} or {(+)}@
 
-    -- It's a bit gruesome to use an Exp as the
-    -- operator, but how else can we distinguish
-    -- constructors from non-constructors?
-    -- Maybe there should be a var-or-con type?
-    -- Or maybe we should leave it to the String itself?
+    -- It's a bit gruesome to use an Exp as the operator when a Name
+    -- would suffice. Historically, Exp was used to make it easier to
+    -- distinguish between infix constructors and non-constructors.
+    -- This is a bit overkill, since one could just as well call
+    -- `startsConId` or `startsConSym` (from `GHC.Lexeme`) on a Name.
+    -- Unfortunately, changing this design now would involve lots of
+    -- code churn for consumers of the TH API, so we continue to use
+    -- an Exp as the operator and perform an extra check during conversion
+    -- to ensure that the Exp is a constructor or a variable (#16895).
 
   | UInfixE Exp Exp Exp                -- ^ @{x + y}@
                                        --
diff --git a/testsuite/tests/th/T16895a.hs b/testsuite/tests/th/T16895a.hs
new file mode 100644
index 0000000000000000000000000000000000000000..8bc847d271cd7e03b1d3f42c960d1524263ebb70
--- /dev/null
+++ b/testsuite/tests/th/T16895a.hs
@@ -0,0 +1,7 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module T16895a where
+
+import Language.Haskell.TH
+
+main = print $(uInfixE [|1|] [|id id|] [|2|])
diff --git a/testsuite/tests/th/T16895a.stderr b/testsuite/tests/th/T16895a.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..d4b98c944ab7c4b2313f7fef7604d80a0cae9f5d
--- /dev/null
+++ b/testsuite/tests/th/T16895a.stderr
@@ -0,0 +1,5 @@
+
+T16895a.hs:7:16: error:
+    • Non-variable expression is not allowed in an infix expression
+      When splicing a TH expression: 1 `GHC.Base.id GHC.Base.id` 2
+    • In the untyped splice: $(uInfixE [| 1 |] [| id id |] [| 2 |])
diff --git a/testsuite/tests/th/T16895b.hs b/testsuite/tests/th/T16895b.hs
new file mode 100644
index 0000000000000000000000000000000000000000..d4308ecbaaea097cb8f44588b81fc51d74d10d7f
--- /dev/null
+++ b/testsuite/tests/th/T16895b.hs
@@ -0,0 +1,7 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module T16895b where
+
+import Language.Haskell.TH
+
+main = print $(infixE Nothing [|id id|] (Just [|2|]))
diff --git a/testsuite/tests/th/T16895b.stderr b/testsuite/tests/th/T16895b.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..8309912f6425ee25c35589103802e79b846e53b2
--- /dev/null
+++ b/testsuite/tests/th/T16895b.stderr
@@ -0,0 +1,6 @@
+
+T16895b.hs:7:16:
+     Non-variable expression is not allowed in an infix expression
+      When splicing a TH expression: (`GHC.Base.id GHC.Base.id` 2)
+     In the untyped splice:
+        $(infixE Nothing [| id id |] (Just [| 2 |]))
diff --git a/testsuite/tests/th/T16895c.hs b/testsuite/tests/th/T16895c.hs
new file mode 100644
index 0000000000000000000000000000000000000000..5e262586e2f134df6b5931b4a49d41caa59d8d06
--- /dev/null
+++ b/testsuite/tests/th/T16895c.hs
@@ -0,0 +1,7 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module T16895c where
+
+import Language.Haskell.TH
+
+main = print $(infixE (Just [|1|]) [|id id|] Nothing)
diff --git a/testsuite/tests/th/T16895c.stderr b/testsuite/tests/th/T16895c.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..38475cce3c58e8a131f0edfa1f0f57c0ff14f3db
--- /dev/null
+++ b/testsuite/tests/th/T16895c.stderr
@@ -0,0 +1,6 @@
+
+T16895c.hs:7:16:
+     Non-variable expression is not allowed in an infix expression
+      When splicing a TH expression: (1 `GHC.Base.id GHC.Base.id`)
+     In the untyped splice:
+        $(infixE (Just [| 1 |]) [| id id |] Nothing)
diff --git a/testsuite/tests/th/T16895d.hs b/testsuite/tests/th/T16895d.hs
new file mode 100644
index 0000000000000000000000000000000000000000..6a92043e26f7d4ff0b021f57ea09597ec41637b5
--- /dev/null
+++ b/testsuite/tests/th/T16895d.hs
@@ -0,0 +1,7 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module T16895d where
+
+import Language.Haskell.TH
+
+main = print $(infixE (Just [|1|]) [|(id id)|] (Just [|2|]))
diff --git a/testsuite/tests/th/T16895d.stderr b/testsuite/tests/th/T16895d.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..57ba8725bacbbb5f2dab046c531da1e254a97614
--- /dev/null
+++ b/testsuite/tests/th/T16895d.stderr
@@ -0,0 +1,6 @@
+
+T16895d.hs:7:16:
+     Non-variable expression is not allowed in an infix expression
+      When splicing a TH expression: 1 `GHC.Base.id GHC.Base.id` 2
+     In the untyped splice:
+        $(infixE (Just [| 1 |]) [| (id id) |] (Just [| 2 |]))
diff --git a/testsuite/tests/th/T16895e.hs b/testsuite/tests/th/T16895e.hs
new file mode 100644
index 0000000000000000000000000000000000000000..0ba2325578c51f7856c2e88d4560a716e18f71d3
--- /dev/null
+++ b/testsuite/tests/th/T16895e.hs
@@ -0,0 +1,7 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module T16895e where
+
+import Language.Haskell.TH
+
+main = print $(infixE Nothing [|id id|] Nothing)
diff --git a/testsuite/tests/th/T16895e.stderr b/testsuite/tests/th/T16895e.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..90884a09da719d4269417f5d8405dda9aeca65d2
--- /dev/null
+++ b/testsuite/tests/th/T16895e.stderr
@@ -0,0 +1,5 @@
+
+T16895e.hs:7:16:
+     Non-variable expression is not allowed in an infix expression
+      When splicing a TH expression: (`GHC.Base.id GHC.Base.id`)
+     In the untyped splice: $(infixE Nothing [| id id |] Nothing)
diff --git a/testsuite/tests/th/all.T b/testsuite/tests/th/all.T
index def7a843c85aabb027d69b9cdce87d07c456551c..02902c395610d7aeefec1cb245d8ac64a36d2275 100644
--- a/testsuite/tests/th/all.T
+++ b/testsuite/tests/th/all.T
@@ -480,3 +480,8 @@ test('T16293b', normal, compile, [''])
 test('T16326_TH', normal, compile, ['-v0 -ddump-splices -dsuppress-uniques'])
 test('T14741', normal, compile_and_run, [''])
 test('T16666', normal, compile, ['-v0 -ddump-splices -dsuppress-uniques'])
+test('T16895a', normal, compile_fail, [''])
+test('T16895b', normal, compile_fail, [''])
+test('T16895c', normal, compile_fail, [''])
+test('T16895d', normal, compile_fail, [''])
+test('T16895e', normal, compile_fail, [''])