From bc05530979c32dc44321f195c1bc4e07faa0a222 Mon Sep 17 00:00:00 2001 From: Luke Worth Date: Mon, 30 Mar 2026 10:53:42 +1100 Subject: [PATCH 01/12] test: broken scenario --- test/Spec.hs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/Spec.hs b/test/Spec.hs index 79546ec..c650f78 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -2,6 +2,7 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE FunctionalDependencies #-} +{-# LANGUAGE ImpredicativeTypes #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TemplateHaskell #-} @@ -23,5 +24,10 @@ $(banInstance [t|TestClass Char Int|] "because it's really bad") instance TestClass Int Int where testFunction = const 0 +class TestClass2 a where + testFunction2 :: a + +$(banInstance [t|forall a. TestClass2 (Maybe a)|] "no instances allowed") + main :: IO () main = pure () From 68684193f8186f6630d1f2bdce0232e3606d879b Mon Sep 17 00:00:00 2001 From: Luke Worth Date: Mon, 30 Mar 2026 10:53:42 +1100 Subject: [PATCH 02/12] fix: test --- src/Language/Haskell/Instance/Ban.hs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Language/Haskell/Instance/Ban.hs b/src/Language/Haskell/Instance/Ban.hs index 739be2d..6914c96 100644 --- a/src/Language/Haskell/Instance/Ban.hs +++ b/src/Language/Haskell/Instance/Ban.hs @@ -53,7 +53,7 @@ banInstance constraintQ message = do ':$$: 'Text "Instance banned at " ':<>: 'Text $(symbol $ formatLocation loc) ':$$: 'Text "" )|]] - pure <$> instanceD context constraintQ (convertClassDecs classDecs) + pure <$> instanceD context (withoutForall <$> constraintQ) (convertClassDecs classDecs) symbol :: String -> TypeQ symbol = litT . strTyLit @@ -61,10 +61,20 @@ symbol = litT . strTyLit formatLocation :: Loc -> String formatLocation Loc{..} = concat ["[", loc_package, ":", loc_module, "] ", loc_filename, ":", show $ fst loc_start] +withoutForall :: Type -> Type +withoutForall topTy = go topTy where + go (ForallT _ _ ty) = ty + go ty = ty + className :: Type -> Name className topTy = go topTy where - go (AppT ty _) = className ty - go (ConT name) = name + go (ForallT _ _ ty) = className ty + go (ForallVisT _ ty) = className ty + go (AppT ty _) = className ty + go (AppKindT ty _) = className ty + go (SigT ty _) = className ty + go (ConT name) = name + go (ParensT ty) = className ty go _ = error $ "Cannot determine class name for type: " ++ pprint topTy convertClassDecs :: [Dec] -> [DecQ] From 4041a5f657a0466338b7c9b990b088de3a4fcee8 Mon Sep 17 00:00:00 2001 From: Luke Worth Date: Mon, 30 Mar 2026 16:52:39 +1100 Subject: [PATCH 03/12] feat: permit nested toplevel foralls GHC itself doesn't permit this, but if it ever does we'll be ready. --- src/Language/Haskell/Instance/Ban.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Language/Haskell/Instance/Ban.hs b/src/Language/Haskell/Instance/Ban.hs index 6914c96..65256d1 100644 --- a/src/Language/Haskell/Instance/Ban.hs +++ b/src/Language/Haskell/Instance/Ban.hs @@ -63,7 +63,7 @@ formatLocation Loc{..} = concat ["[", loc_package, ":", loc_module, "] ", loc_fi withoutForall :: Type -> Type withoutForall topTy = go topTy where - go (ForallT _ _ ty) = ty + go (ForallT _ _ ty) = go ty go ty = ty className :: Type -> Name From 8a9e9cfa19650a5a186aa7f0cd94136bf7fdd627 Mon Sep 17 00:00:00 2001 From: Luke Worth Date: Mon, 30 Mar 2026 16:57:12 +1100 Subject: [PATCH 04/12] Update changelog, bump version --- ChangeLog.md | 4 ++++ ban-instance.cabal | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 9a02bc8..d78e7c2 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,9 @@ # Changelog for ban-instance +## 0.1.1.0 - 2026-03-30 + +* Enable banning instances including type variables. + ## 0.1.0.1 - 2019-11-08 * Remove broken test. diff --git a/ban-instance.cabal b/ban-instance.cabal index c001313..f835502 100644 --- a/ban-instance.cabal +++ b/ban-instance.cabal @@ -1,5 +1,5 @@ name: ban-instance -version: 0.1.0.1 +version: 0.1.1.0 synopsis: For when a type should never be an instance of a class description: <> From c353d8b44bce8080bc173aee99da601b973c4c9d Mon Sep 17 00:00:00 2001 From: Luke Worth Date: Mon, 30 Mar 2026 16:52:20 +1100 Subject: [PATCH 05/12] remove redundant limitation --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index f3bd256..96aea7f 100644 --- a/README.md +++ b/README.md @@ -66,8 +66,3 @@ data V2 a = V2 a instance ToJSON (V2 Foo) where -- ... instance FromJSON (V2 Foo) where -- ... ``` - -## Limitations - -* Type quotations `[t|...|]` do not support free variables - ([GHC#5616](https://gitlab.haskell.org/ghc/ghc/issues/5616)). From ee0be6a249702730bfac6fa90c22eb37ec167b77 Mon Sep 17 00:00:00 2001 From: Luke Worth Date: Mon, 30 Mar 2026 17:10:59 +1100 Subject: [PATCH 06/12] fix: require template-haskell providing all the constructors we match --- ban-instance.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ban-instance.cabal b/ban-instance.cabal index f835502..51b7ce4 100644 --- a/ban-instance.cabal +++ b/ban-instance.cabal @@ -49,7 +49,7 @@ library hs-source-dirs: src ghc-options: -Wall build-depends: base >= 4.7 && < 4.22 - , template-haskell >= 2.11 && < 2.24 + , template-haskell >= 2.16 && < 2.24 exposed-modules: Language.Haskell.Instance.Ban default-language: Haskell2010 From 20958f9e73bf54351f1d94dd747f826615d3e67f Mon Sep 17 00:00:00 2001 From: Luke Worth Date: Tue, 31 Mar 2026 11:47:25 +1100 Subject: [PATCH 07/12] Revert "remove redundant limitation" This reverts commit c353d8b44bce8080bc173aee99da601b973c4c9d. --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 96aea7f..f3bd256 100644 --- a/README.md +++ b/README.md @@ -66,3 +66,8 @@ data V2 a = V2 a instance ToJSON (V2 Foo) where -- ... instance FromJSON (V2 Foo) where -- ... ``` + +## Limitations + +* Type quotations `[t|...|]` do not support free variables + ([GHC#5616](https://gitlab.haskell.org/ghc/ghc/issues/5616)). From 03ba1e5b127a60280c85ebec83c723691d6d55a1 Mon Sep 17 00:00:00 2001 From: Luke Worth Date: Tue, 31 Mar 2026 11:59:34 +1100 Subject: [PATCH 08/12] fix: .md files incorrectly listed in extra-source-files --- ban-instance.cabal | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ban-instance.cabal b/ban-instance.cabal index 51b7ce4..9cfe26c 100644 --- a/ban-instance.cabal +++ b/ban-instance.cabal @@ -1,3 +1,4 @@ +cabal-version: 1.18 name: ban-instance version: 0.1.1.0 synopsis: For when a type should never be an instance of a class @@ -23,7 +24,6 @@ copyright: (c) 2017, Commonwealth Scientific and Industrial Research Organi license: BSD3 license-file: LICENSE build-type: Simple -cabal-version: >= 1.10 tested-with: GHC == 8.0.2 || == 8.2.2 || == 8.4.4 @@ -38,8 +38,8 @@ tested-with: GHC == 8.0.2 || == 9.10.2 || == 9.12.2 -extra-source-files: ChangeLog.md - README.md +extra-doc-files: ChangeLog.md + README.md source-repository head type: git From ee37c039be71941b15895a7f0435fb53d0b1ccf4 Mon Sep 17 00:00:00 2001 From: Luke Worth Date: Tue, 31 Mar 2026 11:55:24 +1100 Subject: [PATCH 09/12] fix: tested-with included incompatible GHC versions --- ban-instance.cabal | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ban-instance.cabal b/ban-instance.cabal index 51b7ce4..1fe46ff 100644 --- a/ban-instance.cabal +++ b/ban-instance.cabal @@ -24,12 +24,7 @@ license: BSD3 license-file: LICENSE build-type: Simple cabal-version: >= 1.10 -tested-with: GHC == 8.0.2 - || == 8.2.2 - || == 8.4.4 - || == 8.6.5 - || == 8.8.1 - || == 8.10.7 +tested-with: GHC == 8.10.7 || == 9.0.1 || == 9.2.7 || == 9.4.5 From 7f3de998e4220b34ece34ba7451c095155332e13 Mon Sep 17 00:00:00 2001 From: Luke Worth Date: Tue, 31 Mar 2026 11:50:47 +1100 Subject: [PATCH 10/12] doc: explain forall workaround --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index f3bd256..cf829db 100644 --- a/README.md +++ b/README.md @@ -71,3 +71,9 @@ instance FromJSON (V2 Foo) where -- ... * Type quotations `[t|...|]` do not support free variables ([GHC#5616](https://gitlab.haskell.org/ghc/ghc/issues/5616)). + To overcome this limitation, `banInstance` allows you to use an explicit + `forall`. For example: + + ```haskell + $(banInstance [t|forall a. ToJSON (V1 a)|] "use a newtype wrapper at the API layer") + ``` From 34fb484f6fd5bbd617a52ad3f4d13e4ace4d0ab3 Mon Sep 17 00:00:00 2001 From: Luke Worth Date: Tue, 31 Mar 2026 12:11:19 +1100 Subject: [PATCH 11/12] doc: use Maybe instead of V1 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cf829db..8e72506 100644 --- a/README.md +++ b/README.md @@ -75,5 +75,5 @@ instance FromJSON (V2 Foo) where -- ... `forall`. For example: ```haskell - $(banInstance [t|forall a. ToJSON (V1 a)|] "use a newtype wrapper at the API layer") + $(banInstance [t|forall a. ToJSON (Maybe a)|] "use a newtype wrapper at the API layer") ``` From 9b4155366ecdfbd89c140a36e2e3acdeaf13ba4e Mon Sep 17 00:00:00 2001 From: Luke Worth Date: Tue, 31 Mar 2026 12:08:14 +1100 Subject: [PATCH 12/12] doc: explain forall workaround in haddock --- src/Language/Haskell/Instance/Ban.hs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Language/Haskell/Instance/Ban.hs b/src/Language/Haskell/Instance/Ban.hs index 6914c96..9894715 100644 --- a/src/Language/Haskell/Instance/Ban.hs +++ b/src/Language/Haskell/Instance/Ban.hs @@ -38,6 +38,12 @@ import Language.Haskell.TH.Syntax -- @ -- \$(banInstance [t|ToJSON Foo|] "why ToJSON Foo should never be defined") -- @ +-- +-- To ban instances containing type variables: +-- +-- @ +-- \$(banInstance [t|forall a. ToJSON (Maybe a)|] "why ToJSON (Maybe a) should never be defined") +-- @ banInstance :: TypeQ -- ^ The instance you want to ban.