diff --git a/.hlint.yaml b/.hlint.yaml
--- a/.hlint.yaml
+++ b/.hlint.yaml
@@ -6,3 +6,4 @@
 - ignore: {name: Use const}
 - ignore: {name: Use module export list}
 - ignore: {name: Use newtype instead of data}
+- ignore: {name: Avoid lambda using `infix`}
diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown
--- a/CHANGELOG.markdown
+++ b/CHANGELOG.markdown
@@ -1,3 +1,6 @@
+## 0.1.4 [2020.10.02]
+* Allow building with `template-haskell-2.17.0.0` (GHC 9.0).
+
 ## 0.1.3 [2020.01.29]
 * Achieve forward compatibility with
   [GHC proposal 229](https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0229-whitespace-bang-patterns.rst).
diff --git a/src/Data/Struct.hs b/src/Data/Struct.hs
--- a/src/Data/Struct.hs
+++ b/src/Data/Struct.hs
@@ -18,9 +18,7 @@
   , eqStruct
   , alloc
   -- * Nil
-#ifndef HLINT
   , pattern Nil
-#endif
   , isNil
   , NullPointerException(..)
   -- * Slots and Fields
diff --git a/src/Data/Struct/TH.hs b/src/Data/Struct/TH.hs
--- a/src/Data/Struct/TH.hs
+++ b/src/Data/Struct/TH.hs
@@ -11,6 +11,7 @@
 import           Data.Struct.Internal (Dict(Dict), initializeUnboxedField, st)
 import           Data.List (groupBy, nub)
 import           Language.Haskell.TH
+import           Language.Haskell.TH.Datatype.TyVarBndr
 import           Language.Haskell.TH.Syntax (VarStrictType)
 
 #ifdef HLINT
@@ -20,7 +21,7 @@
 data StructRep = StructRep
   { srState       :: Name
   , srName        :: Name
-  , srTyVars      :: [TyVarBndr]
+  , srTyVars      :: [TyVarBndrUnit]
 #if MIN_VERSION_template_haskell(2,12,0)
   , srDerived     :: [DerivClause]
 #else
@@ -98,15 +99,15 @@
 
 -- A struct type's final type variable should be suitable for
 -- use as the ('PrimState' m) argument.
-validateStateType :: [TyVarBndr] -> Q Name
+validateStateType :: [TyVarBndrUnit] -> Q Name
 validateStateType xs =
   do when (null xs) (fail "state type expected but no type variables found")
-     case last xs of
-       PlainTV n -> return n
-       KindedTV n k
-         | k == starK -> return n
-         | otherwise  -> fail "state type should have kind *"
-
+     elimTV return validateKindedTV (last xs)
+  where
+    validateKindedTV :: Name -> Kind -> Q Name
+    validateKindedTV n k
+      | k == starK = return n
+      | otherwise  = fail "state type should have kind *"
 
 -- | Figure out which record fields are Slots and which are
 -- Fields. Slots will have types ending in the state type
@@ -190,13 +191,12 @@
 repType :: StructRep -> TypeQ
 repType rep = repTypeHelper (srName rep) (srTyVars rep)
 
-repTypeHelper :: Name -> [TyVarBndr] -> TypeQ
+repTypeHelper :: Name -> [TyVarBndrUnit] -> TypeQ
 repTypeHelper c vs = foldl appT (conT c) (tyVarBndrT <$> vs)
 
 -- Construct a 'TypeQ' from a 'TyVarBndr'
-tyVarBndrT :: TyVarBndr -> TypeQ
-tyVarBndrT (PlainTV  n  ) = varT n
-tyVarBndrT (KindedTV n k) = sigT (varT n) k
+tyVarBndrT :: TyVarBndrUnit -> TypeQ
+tyVarBndrT = elimTV varT (sigT . varT)
 
 generateStructInstance :: StructRep -> DecsQ
 generateStructInstance rep =
@@ -208,12 +208,14 @@
 generateAlloc :: StructRep -> DecsQ
 generateAlloc rep =
   do mName <- newName "m"
-     let m = varT mName
+     let m :: TypeQ
+         m = varT mName
+
          n = length (groupBy isNeighbor (srMembers rep))
          allocName = mkAllocName rep
 
      simpleDefinition rep allocName
-       (forallT [PlainTV mName] (cxt [])
+       (forallT [plainTVSpecified mName] (cxt [])
           [t| PrimMonad $m => $m ( $(repType1 rep) (PrimState $m) ) |])
        [| alloc n |]
 
@@ -271,7 +273,9 @@
 newStructType :: StructRep -> TypeQ
 newStructType rep =
   do mName <- newName "m"
-     let m = varT mName
+     let m :: TypeQ
+         m = varT mName
+
          s = [t| PrimState $m |]
          obj = repType1 rep
 
@@ -285,7 +289,7 @@
 
          primPreds = primPred <$> nub [ t | Member UnboxedField _ (VarT t) <- srMembers rep ]
 
-     forallRepT rep $ forallT [PlainTV mName] (cxt primPreds)
+     forallRepT rep $ forallT [plainTVSpecified mName] (cxt primPreds)
        [t| PrimMonad $m => $r |]
 
 -- generates a slot, field, or unboxedField definition per member
@@ -352,7 +356,7 @@
 -- Quantifies over all of the type variables in a struct data type
 -- except the state variable which is likely to be ('PrimState' s)
 forallRepT :: StructRep -> TypeQ -> TypeQ
-forallRepT rep = forallT (init (srTyVars rep)) (cxt [])
+forallRepT rep = forallT (init (changeTVFlags SpecifiedSpec (srTyVars rep))) (cxt [])
 
 (-->) :: TypeQ -> TypeQ -> TypeQ
 f --> x = arrowT `appT` f `appT` x
diff --git a/structs.cabal b/structs.cabal
--- a/structs.cabal
+++ b/structs.cabal
@@ -1,6 +1,6 @@
 name:          structs
 category:      Data
-version:       0.1.3
+version:       0.1.4
 license:       BSD3
 cabal-version: 1.22
 license-file:  LICENSE
@@ -15,7 +15,7 @@
              , GHC == 8.2.2
              , GHC == 8.4.4
              , GHC == 8.6.5
-             , GHC == 8.8.1
+             , GHC == 8.8.3
              , GHC == 8.10.1
 synopsis:      Strict GC'd imperative object-oriented programming with cheap pointers.
 description:
@@ -46,7 +46,8 @@
   build-depends:
     base >= 4.9 && < 5,
     deepseq,
-    template-haskell >= 2.11 && < 2.17,
+    template-haskell >= 2.11 && < 2.18,
+    th-abstraction >= 0.4 && < 0.5,
     ghc-prim,
     primitive
 
