From 7e089f2d7d56fa93ca346a76f1b987d4f0059a39 Mon Sep 17 00:00:00 2001 From: Teo Camarasu Date: Mon, 21 Sep 2026 12:18:21 +0100 Subject: [PATCH] Make Expr's role nominal Previously Expr was inferred to have a phantom role, which meant that we could `coerce :: Expr a -> Expr b` for any `a` and `b`. We set it to nominal, which means we can only do `coerce :: a ~ b => Expr a -> Expr b`. We could have also done representational which would have additionally allowed coercing between types with the same representation, ie, if they had a Coercible instance themselves. This isn't correct since types with the same representation could have different DBType/Serializable instances, which would lead to bugs. --- changelog.d/20260921_122034_teofilcamarasu_expr_role.md | 3 +++ rel8-internal/src/Rel8/Internal/Expr.hs | 2 ++ rel8-internal/src/Rel8/Internal/Expr.hs-boot | 2 ++ 3 files changed, 7 insertions(+) create mode 100644 changelog.d/20260921_122034_teofilcamarasu_expr_role.md diff --git a/changelog.d/20260921_122034_teofilcamarasu_expr_role.md b/changelog.d/20260921_122034_teofilcamarasu_expr_role.md new file mode 100644 index 00000000..708ce69c --- /dev/null +++ b/changelog.d/20260921_122034_teofilcamarasu_expr_role.md @@ -0,0 +1,3 @@ +### Fixed + +- Make `Expr` have a representational role, which disallows freely coercing the type within `Expr`. Users can still use `unsafeCastExpr` if they want this behaviour. diff --git a/rel8-internal/src/Rel8/Internal/Expr.hs b/rel8-internal/src/Rel8/Internal/Expr.hs index f309c22a..794eab4b 100644 --- a/rel8-internal/src/Rel8/Internal/Expr.hs +++ b/rel8-internal/src/Rel8/Internal/Expr.hs @@ -9,6 +9,7 @@ {-# language TypeApplications #-} {-# language TypeFamilies #-} {-# language UndecidableInstances #-} +{-# language RoleAnnotations #-} module Rel8.Internal.Expr ( Expr(..) @@ -53,6 +54,7 @@ import Data.Scientific (fromRationalRepetendLimited) -- | Typed SQL expressions. type Expr :: K.Context +type role Expr nominal newtype Expr a = Expr Opaleye.PrimExpr deriving stock Show diff --git a/rel8-internal/src/Rel8/Internal/Expr.hs-boot b/rel8-internal/src/Rel8/Internal/Expr.hs-boot index f64934eb..6891238d 100644 --- a/rel8-internal/src/Rel8/Internal/Expr.hs-boot +++ b/rel8-internal/src/Rel8/Internal/Expr.hs-boot @@ -1,5 +1,6 @@ {-# language DataKinds #-} {-# language StandaloneKindSignatures #-} +{-# language RoleAnnotations #-} module Rel8.Internal.Expr ( Expr(..) @@ -17,4 +18,5 @@ import Rel8.Internal.Schema.Kind ( Context ) type Expr :: Context +type role Expr nominal newtype Expr a = Expr Opaleye.PrimExpr