packages feed

aihc-parser-1.0.0.2: test/Test/Properties/Arb/Type.hs

{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wno-orphans #-}

module Test.Properties.Arb.Type
  ( genType,
    shrinkType,
    shrinkTyVarBinders,
    shrinkForallTelescope,
  )
where

import Aihc.Parser.Syntax
import Data.Text (Text)
import Data.Text qualified as T
import Test.Properties.Arb.Expr (shrinkExpr)
import Test.Properties.Arb.Identifiers
  ( genCharValue,
    genConName,
    genQuasiBody,
    genQuoterName,
    genVarId,
    genVarIdNoHash,
    shrinkIdent,
    shrinkName,
  )
import Test.Properties.Arb.Utils (smallList0)
import Test.QuickCheck

instance Arbitrary Type where
  arbitrary = genType
  shrink = shrinkType

-- | Generate a random type. Uses QuickCheck's size parameter to control
-- recursion depth.
genType :: Gen Type
genType = scale (`div` 2) $ do
  n <- getSize
  if n <= 0
    then
      oneof
        [ TVar <$> genTypeVarName,
          (`TCon` Unpromoted) <$> genConName,
          (`TCon` Promoted) <$> genConName,
          TBuiltinCon <$> genTypeBuiltinCon,
          TTypeLit <$> genTypeLiteral,
          pure (TStar "*"),
          pure TWildcard,
          TQuasiQuote <$> genQuoterName <*> genQuasiBody,
          TTuple Boxed Unpromoted <$> elements [[], [TVar "a", TCon (qualifyName Nothing (mkUnqualifiedName NameConId "B")) Unpromoted]],
          TTuple Unboxed Unpromoted <$> elements [[], [TVar "a", TCon (qualifyName Nothing (mkUnqualifiedName NameConId "B")) Unpromoted]],
          TList Unpromoted <$> genTypeListElems,
          TUnboxedSum <$> genUnboxedSumElems
        ]
    else
      oneof
        [ TVar <$> genTypeVarName,
          (`TCon` Unpromoted) <$> genConName,
          (`TCon` Promoted) <$> genConName,
          TBuiltinCon <$> genTypeBuiltinCon,
          TTypeLit <$> genTypeLiteral,
          pure (TStar "*"),
          pure TWildcard,
          TQuasiQuote <$> genQuoterName <*> genQuasiBody,
          TForall <$> genForallTelescope <*> genType,
          genTypeApp,
          genTypeTypeApp,
          genTypeInfix,
          genTypeFun,
          TTuple Boxed Unpromoted <$> genTypeTupleElems,
          TTuple Boxed Promoted <$> genPromotedTupleElems,
          TTuple Unboxed Unpromoted <$> genTypeTupleElems,
          TUnboxedSum <$> genUnboxedSumElems,
          TList Unpromoted <$> genTypeListElems,
          TList Promoted <$> genPromotedListElems,
          TParen <$> genType,
          TSplice <$> genTypeSpliceBody,
          genTypeContext,
          genTypeImplicitParam,
          TKindSig <$> genKindSigSubject <*> genKindSigKind
        ]

genTypeApp :: Gen Type
genTypeApp = TApp <$> genType <*> genType

genTypeTypeApp :: Gen Type
genTypeTypeApp = TTypeApp <$> genType <*> genType

genTypeInfix :: Gen Type
genTypeInfix = do
  lhs <- genType
  rhs <- genType
  op <- genConName
  pure (TInfix lhs op Unpromoted rhs)

genTypeFun :: Gen Type
genTypeFun =
  oneof
    [ TFun ArrowUnrestricted <$> genType <*> genType,
      TFun ArrowLinear <$> genType <*> genType,
      (TFun . ArrowExplicit <$> genMultiplicityType) <*> genType <*> genType
    ]

-- | Generate a multiplicity type for explicit multiplicity annotations.
-- Keep it simple to avoid overly complex nesting.
genMultiplicityType :: Gen Type
genMultiplicityType =
  oneof
    [ TVar <$> genTypeVarName,
      (`TCon` Unpromoted) <$> genConName
    ]

-- | Generate the body of a TH type splice: either a bare variable or a parenthesized expression.
genTypeSpliceBody :: Gen Expr
genTypeSpliceBody =
  oneof
    [ EVar <$> genTypeVarExprName,
      EParen . EVar <$> genTypeVarExprName
    ]

genTypeContext :: Gen Type
genTypeContext = do
  n <- chooseInt (1, 3)
  constraints <- vectorOf n genConstraintType
  TContext constraints <$> genType

-- | Generate a constraint type (used in contexts).
-- Typically a type constructor applied to some arguments.
genConstraintType :: Gen Type
genConstraintType = do
  s <- getSize
  className <- (`TCon` Unpromoted) <$> genConName
  oneof
    [ -- Simple constraint: ClassName tyvar
      TApp className . TVar <$> genTypeVarName,
      -- Applied constraint: ClassName (Type)
      TApp className . TParen <$> resize s genType
    ]

genTypeImplicitParam :: Gen Type
genTypeImplicitParam = do
  name <- ("?" <>) <$> genVarIdNoHash
  TImplicitParam name <$> genType

genTypeTupleElems :: Gen [Type]
genTypeTupleElems = do
  isUnit <- arbitrary
  if isUnit
    then pure []
    else do
      n <- chooseInt (2, 4)
      scale (`div` n) $ vectorOf n genType

genTypeListElems :: Gen [Type]
genTypeListElems = do
  n <- chooseInt (1, 4)
  scale (`div` n) $ vectorOf n genType

genUnboxedSumElems :: Gen [Type]
genUnboxedSumElems = do
  n <- chooseInt (2, 4)
  scale (`div` n) $ vectorOf n genType

-- | Generate elements for a promoted tuple or list. Uses simple types only
-- to avoid nesting ambiguities with kind signatures and unboxed tuples
-- inside promoted containers.
genPromotedTupleElems :: Gen [Type]
genPromotedTupleElems = do
  isUnit <- arbitrary
  if isUnit
    then pure []
    else do
      n <- chooseInt (2, 3)
      vectorOf n genPromotedElem

genPromotedListElems :: Gen [Type]
genPromotedListElems = do
  n <- chooseInt (1, 3)
  vectorOf n genPromotedElem

-- | Generate a simple type suitable for use inside promoted tuples/lists.
-- Avoids character type literals since 'c' conflicts with the promotion tick.
genPromotedElem :: Gen Type
genPromotedElem =
  oneof
    [ TVar <$> genTypeVarName,
      (`TCon` Unpromoted) <$> genConName,
      genPromotedSafeTypeLiteral,
      pure (TStar "*")
    ]

-- | Generate type literals safe for use in promoted contexts (no char literals).
genPromotedSafeTypeLiteral :: Gen Type
genPromotedSafeTypeLiteral =
  TTypeLit
    <$> oneof
      [ do
          n <- chooseInteger (0, 1000)
          pure (TypeLitInteger n (T.pack (show n))),
        do
          txt <- genSymbolText
          pure (TypeLitSymbol txt (T.pack (show (T.unpack txt))))
      ]

genSimpleTypeAtom :: Gen Type
genSimpleTypeAtom =
  oneof
    [ TVar <$> genTypeVarName,
      (`TCon` Unpromoted) <$> genConName,
      TTypeLit <$> genTypeLiteral,
      pure (TStar "*"),
      pure TWildcard,
      TQuasiQuote <$> genQuoterName <*> genQuasiBody,
      TTuple Boxed Unpromoted <$> genTypeTupleElems,
      TTuple Unboxed Unpromoted <$> smallList0 genType,
      TUnboxedSum <$> genUnboxedSumElems,
      TList Unpromoted <$> genTypeListElems,
      TParen <$> genType
    ]

genKindSigSubject :: Gen Type
genKindSigSubject = genSimpleTypeAtom

genKindSigKind :: Gen Type
genKindSigKind =
  frequency
    [ (3, genSimpleTypeAtom),
      (1, TFun ArrowUnrestricted <$> genSimpleTypeAtom <*> genSimpleTypeAtom)
    ]

genForallTelescope :: Gen ForallTelescope
genForallTelescope = do
  vis <- elements [ForallInvisible, ForallVisible]
  -- Visible foralls (forall a -> T) require specified binders; inferred ({a}) are only valid in invisible foralls
  binders <- genTypeBindersFor vis
  pure (ForallTelescope vis binders)

genTypeBindersFor :: ForallVis -> Gen [TyVarBinder]
genTypeBindersFor vis = do
  n <- chooseInt (1, 3)
  vectorOf n (genTyVarBinderFor vis)

genTyVarBinderFor :: ForallVis -> Gen TyVarBinder
genTyVarBinderFor vis = do
  name <- genTypeVarName
  case vis of
    ForallVisible ->
      oneof
        [ -- Plain specified binder: a
          pure (TyVarBinder [] (renderUnqualifiedName name) Nothing TyVarBSpecified TyVarBVisible),
          -- Kinded specified binder: (a :: Kind)
          do
            kind <- resize 0 genSimpleTypeAtom
            pure (TyVarBinder [] (renderUnqualifiedName name) (Just kind) TyVarBSpecified TyVarBVisible)
        ]
    ForallInvisible ->
      oneof
        [ -- Plain specified binder: a
          pure (TyVarBinder [] (renderUnqualifiedName name) Nothing TyVarBSpecified TyVarBVisible),
          -- Plain inferred binder: {a}
          pure (TyVarBinder [] (renderUnqualifiedName name) Nothing TyVarBInferred TyVarBVisible),
          -- Kinded inferred binder: {a :: Kind}
          do
            kind <- resize 0 genSimpleTypeAtom
            pure (TyVarBinder [] (renderUnqualifiedName name) (Just kind) TyVarBInferred TyVarBVisible),
          -- Kinded specified binder: (a :: Kind)
          do
            kind <- resize 0 genSimpleTypeAtom
            pure (TyVarBinder [] (renderUnqualifiedName name) (Just kind) TyVarBSpecified TyVarBVisible)
        ]

genTypeVarName :: Gen UnqualifiedName
genTypeVarName = mkUnqualifiedName NameVarId <$> genVarId

genTypeVarExprName :: Gen Name
genTypeVarExprName = qualifyName Nothing . mkUnqualifiedName NameVarId <$> genVarId

genTypeLiteral :: Gen TypeLiteral
genTypeLiteral =
  oneof
    [ do
        n <- chooseInteger (0, 1000)
        pure (TypeLitInteger n (T.pack (show n))),
      do
        txt <- genSymbolText
        pure (TypeLitSymbol txt (T.pack (show (T.unpack txt)))),
      do
        c <- genCharValue
        pure (TypeLitChar c (T.pack (show c)))
    ]

genTypeBuiltinCon :: Gen TypeBuiltinCon
genTypeBuiltinCon =
  elements
    [ TBuiltinTuple 2,
      TBuiltinTuple 3,
      TBuiltinArrow,
      TBuiltinList,
      TBuiltinCons
    ]

genSymbolText :: Gen Text
genSymbolText = do
  len <- chooseInt (0, 8)
  chars <- vectorOf len genCharValue
  pure (T.pack chars)

shrinkType :: Type -> [Type]
shrinkType ty =
  [TWildcard | ty /= TWildcard]
    ++ case ty of
      TVar name ->
        [TVar $ unqualifiedNameFromText "a" | renderUnqualifiedName name /= "a"]
          <> [TVar (mkUnqualifiedName NameVarId shrunk) | shrunk <- shrinkIdent (renderUnqualifiedName name)]
      TCon name promoted ->
        [ TCon shrunk promoted
        | shrunk <- shrinkName name
        ]
      TBuiltinCon {} ->
        []
      TImplicitParam name inner ->
        [inner]
          <> [TImplicitParam name' inner | name' <- shrinkImplicitParamName name]
          <> [TImplicitParam name inner' | inner' <- shrinkType inner]
      TTypeLit {} ->
        []
      TStar {} ->
        []
      TQuasiQuote quoter body ->
        [TQuasiQuote q body | q <- shrinkIdent quoter]
          <> [TQuasiQuote quoter b | b <- map T.pack (shrink (T.unpack body))]
      TForall telescope inner ->
        [inner]
          <> [TForall telescope' inner | telescope' <- shrinkForallTelescope telescope]
          <> [TForall telescope inner' | inner' <- shrinkType inner]
      TApp fn arg ->
        [fn, arg]
          <> [TApp fn' arg | fn' <- shrinkType fn]
          <> [TApp fn arg' | arg' <- shrinkType arg]
      TTypeApp fn arg ->
        [fn, arg]
          <> [TTypeApp fn' arg | fn' <- shrinkType fn]
          <> [TTypeApp fn arg' | arg' <- shrinkType arg]
      TFun arrowKind lhs rhs ->
        [lhs, rhs]
          <> [TFun arrowKind lhs' rhs | lhs' <- shrinkType lhs]
          <> [TFun arrowKind lhs rhs' | rhs' <- shrinkType rhs]
      TInfix lhs op promoted rhs ->
        [lhs, rhs]
          <> [TInfix lhs' op promoted rhs | lhs' <- shrinkType lhs]
          <> [TInfix lhs op promoted rhs' | rhs' <- shrinkType rhs]
      TTuple tupleFlavor _ elems ->
        shrinkTypeTupleElems tupleFlavor elems
      TList _ elems ->
        [TList Unpromoted elems' | elems' <- shrinkList shrinkType elems, not (null elems')]
          <> elems
      TParen inner ->
        [inner]
          <> [TParen inner' | inner' <- shrinkType inner]
      TKindSig ty' kind ->
        [ty', kind]
          <> [TKindSig ty'' kind | ty'' <- shrinkType ty']
          <> [TKindSig ty' kind' | kind' <- shrinkType kind]
      TUnboxedSum elems ->
        [TUnboxedSum elems' | elems' <- shrinkList shrinkType elems, length elems' >= 2]
      TContext constraints inner ->
        [inner]
          <> [TContext constraints' inner | constraints' <- shrinkList shrinkType constraints, not (null constraints')]
          <> [TContext constraints inner' | inner' <- shrinkType inner]
      TSplice e ->
        [TSplice e' | e' <- shrinkExpr e]
      TWildcard ->
        []
      TAnn _ sub -> shrinkType sub

shrinkTyVarBinders :: [TyVarBinder] -> [[TyVarBinder]]
shrinkTyVarBinders binders =
  [ shrunk
  | shrunk <- shrinkList shrinkTyVarBinder binders,
    not (null shrunk)
  ]

shrinkTyVarBinder :: TyVarBinder -> [TyVarBinder]
shrinkTyVarBinder tvb =
  [tvb {tyVarBinderName = name'} | name' <- shrinkIdent (tyVarBinderName tvb)]
    <> [tvb {tyVarBinderKind = Just kind'} | Just kind <- [tyVarBinderKind tvb], kind' <- shrinkType kind]

shrinkForallTelescope :: ForallTelescope -> [ForallTelescope]
shrinkForallTelescope telescope =
  [ telescope {forallTelescopeBinders = binders'}
  | binders' <- shrinkTyVarBinders (forallTelescopeBinders telescope)
  ]
    <> [ telescope {forallTelescopeVisibility = ForallInvisible}
       | forallTelescopeVisibility telescope == ForallVisible
       ]

shrinkTypeTupleElems :: TupleFlavor -> [Type] -> [Type]
shrinkTypeTupleElems tupleFlavor elems =
  elems
    <> [TTuple Boxed Unpromoted elems | tupleFlavor == Unboxed, length elems /= 1]
    <> [ candidate
       | shrunk <- shrinkList shrinkType elems,
         candidate <- case shrunk of
           [] -> [TTuple tupleFlavor Unpromoted []]
           [_] | tupleFlavor == Boxed -> []
           _ -> [TTuple tupleFlavor Unpromoted shrunk]
       ]

shrinkImplicitParamName :: Text -> [Text]
shrinkImplicitParamName name =
  case T.stripPrefix "?" name of
    Nothing -> []
    Just inner -> ["?" <> candidate | candidate <- shrinkIdent inner]