packages feed

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

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

module Test.Properties.Arb.Expr
  ( genExpr,
    genRhs,
    mkIntExpr,
    mkStringExpr,
    shrinkExpr,
    shrinkGuardQualifier,
  )
where

import Aihc.Parser.Syntax
import Data.Char (isSpace)
import Data.Maybe (isJust, isNothing)
import Data.Text (Text)
import Data.Text qualified as T
import Test.Properties.Arb.Decl (genDeclValue, genWhereDecls, shrinkFunctionHeadPats)
import Test.Properties.Arb.Identifiers
  ( genCharValue,
    genConName,
    genFieldName,
    genModuleQualifier,
    genStringValue,
    genTenths,
    genVarId,
    genVarName,
    showHex,
    shrinkFloat,
    shrinkName,
    shrinkQualifier,
    shrinkUnqualifiedName,
  )
import Test.Properties.Arb.Pattern (genPattern, shrinkPattern)
import {-# SOURCE #-} Test.Properties.Arb.Type (genType, shrinkType)
import Test.Properties.Arb.Utils
import Test.QuickCheck

-- | Generate a random expression. Uses QuickCheck's size parameter
-- to control recursion depth.
genExpr :: Gen Expr
genExpr = scale (`div` 2) $ do
  n <- getSize
  if n <= 0 then genExprLeaf else oneof (baseGenerators <> quoteGenerators)
  where
    baseGenerators =
      [ -- Leaf expressions
        genExprLeaf,
        -- Recursive expressions (reduce size for subexpressions)
        EApp <$> genExpr <*> genExpr,
        EInfix <$> genExpr <*> genVarName <*> genExpr,
        ENegate <$> genExpr,
        ESectionL <$> genExpr <*> genVarName,
        ESectionR <$> genRightSectionOpName <*> genExpr,
        EIf <$> genExpr <*> genExpr <*> genExpr,
        EMultiWayIf <$> genGuardedRhsListWith,
        ECase <$> genExpr <*> genCaseAltsWith,
        ELambdaPats <$> genPatterns <*> genExpr,
        ELambdaCase <$> genCaseAltsWith,
        ELambdaCases <$> genLambdaCaseAltsWith,
        ELetDecls <$> smallList0 (DeclValue <$> genDeclValue) <*> genExpr,
        EDo <$> genDoStmtsWith <*> genDoFlavor,
        EListComp <$> genExpr <*> genCompStmtsWith,
        EListCompParallel <$> genExpr <*> genParallelCompStmtsWith,
        EList <$> genListElemsWith,
        ETuple Boxed . map Just <$> genTupleElemsWith,
        ETuple Unboxed . map Just <$> genUnboxedTupleElemsWith,
        ETuple Boxed <$> genTupleSectionElemsWith,
        ETuple Unboxed <$> genTupleSectionElemsWith,
        genUnboxedSumExprWith,
        EArithSeq <$> genArithSeqWith,
        ERecordCon <$> genConName <*> genRecordFieldsWith <*> pure False,
        ERecordUpd <$> genExpr <*> genRecordFieldsWith,
        ETypeSig <$> genExpr <*> genType,
        ETypeApp <$> genExpr <*> genType,
        EParen . ETypeSyntax TypeSyntaxExplicitNamespace <$> genTypeSyntaxType,
        EParen <$> genExpr,
        EProc <$> genPattern <*> genCmdWith,
        EParen <$> (EPragma <$> genSccPragma <*> genPragmaExpr),
        -- OverloadedRecordDot
        EGetField <$> genExpr <*> genRecordFieldName,
        EGetFieldProjection <$> smallList1 genRecordFieldName,
        -- Template Haskell splices are valid inside quote bodies.
        ETHSplice <$> genSpliceBody,
        ETHTypedSplice <$> genTypedSpliceBody
      ]
    quoteGenerators =
      [ ETHExpQuote <$> genExpr,
        ETHTypedQuote <$> genExpr,
        ETHDeclQuote <$> smallList0 (DeclValue <$> genDeclValue),
        ETHPatQuote <$> genPattern,
        ETHTypeQuote <$> genType,
        ETHNameQuote <$> genNameQuoteExpr,
        ETHTypeNameQuote <$> genTypeNameQuoteType
      ]

-- | Generate a leaf (non-recursive) expression.
genExprLeaf :: Gen Expr
genExprLeaf =
  oneof
    [ EVar <$> genVarName,
      genOverloadedLabel,
      mkIntExpr <$> chooseInteger (0, 999),
      mkHexExpr <$> chooseInteger (0, 255),
      mkFloatExpr <$> genTenths,
      mkCharExpr <$> genCharValue,
      mkStringExpr <$> genStringValue,
      -- MagicHash literals
      (\v -> EInt v TIntHash (T.pack (show v) <> "#")) <$> chooseInteger (0, 999),
      (\v -> ECharHash v (T.pack (show v) <> "#")) <$> genCharValue,
      (\v -> EStringHash v (T.pack (show (T.unpack v)) <> "#")) <$> genStringValue,
      EQuasiQuote <$> genQuasiQuoteName <*> genStringValue,
      pure (EList []),
      pure (ETuple Boxed []),
      pure (ETuple Unboxed []),
      (\n -> ETuple Boxed (replicate n Nothing)) <$> chooseInt (2, 5),
      (\n -> ETuple Unboxed (replicate n Nothing)) <$> chooseInt (2, 5)
    ]

genOverloadedLabel :: Gen Expr
genOverloadedLabel = do
  labelName <- suchThat genVarId (not . T.isSuffixOf "#")
  pure (EOverloadedLabel labelName ("#" <> labelName))

genSccPragma :: Gen Pragma
genSccPragma = do
  sccLabel <- suchThat genVarId (not . T.isSuffixOf "#")
  pure Pragma {pragmaType = PragmaSCC sccLabel, pragmaRawText = ""}

genPragmaExpr :: Gen Expr
genPragmaExpr = EVar <$> genVarName

genTypeSyntaxType :: Gen Type
genTypeSyntaxType =
  oneof
    [ TVar . mkUnqualifiedName NameVarId <$> genVarId,
      (`TCon` Unpromoted) <$> genConName
    ]

-- | Generate a quasi-quote name, excluding TH bracket names (e, d, p, t) which
-- would collide with Template Haskell bracket syntax ([e|...|], [d|...|], etc.).
genQuasiQuoteName :: Gen Text
genQuasiQuoteName = suchThat genVarId isValidQuasiQuoteName

isValidQuasiQuoteName :: Text -> Bool
isValidQuasiQuoteName name = name `notElem` ["e", "d", "p", "t", ""] && not (T.isSuffixOf "#" name)

-- | Generate the body of a TH splice: either a bare variable or a parenthesized expression.
-- Bare variables produce $name syntax; parenthesized produce $(expr) syntax.
genSpliceBody :: Gen Expr
genSpliceBody =
  oneof
    [ EVar <$> genVarName,
      EParen <$> scale (`div` 2) genExpr
    ]

-- | Generate the body of a TH typed splice: always parenthesized.
-- Typed splices require parentheses: $$(expr) is valid, $$expr is invalid.
genTypedSpliceBody :: Gen Expr
genTypedSpliceBody =
  EParen <$> scale (`div` 2) genExpr

-- | Generate a TH value name quote target.
-- Produces unqualified identifiers plus qualified identifiers and operators
-- such as @M.v@ and @M.+@.
genNameQuoteExpr :: Gen Expr
genNameQuoteExpr =
  oneof
    [ EVar <$> genVarName,
      pure (EList []),
      pure (ETuple Boxed []),
      pure (ETuple Unboxed []),
      (\n -> ETuple Boxed (replicate n Nothing))
        <$> chooseInt (2, 5),
      (\n -> ETuple Unboxed (replicate n Nothing))
        <$> chooseInt (2, 5)
    ]

genTypeNameQuoteType :: Gen Type
genTypeNameQuoteType =
  oneof
    [ TCon <$> genConName <*> pure Unpromoted,
      pure (TBuiltinCon TBuiltinList),
      pure (TTuple Boxed Unpromoted []),
      pure (TTuple Unboxed Unpromoted [])
    ]

-- | Generate simple patterns for lambdas
genPatterns :: Gen [Pattern]
genPatterns = smallList1 genPattern

genCmdWith :: Gen Cmd
genCmdWith = scale (`div` 2) $ do
  n <- getSize
  if n <= 0 then genCmdLeafWith else oneof (genCmdLeafWith : genCmdRecursiveWith)

genCmdLeafWith :: Gen Cmd
genCmdLeafWith =
  CmdArrApp <$> genExpr <*> genArrAppType <*> genExpr

genCmdRecursiveWith :: [Gen Cmd]
genCmdRecursiveWith =
  [ CmdInfix <$> genCmdWith <*> genVarName <*> genCmdWith,
    CmdDo <$> genCmdDoStmtsWith,
    CmdIf <$> genExpr <*> genCmdWith <*> genCmdWith,
    CmdCase <$> genExpr <*> genCmdCaseAltsWith,
    CmdLet <$> smallList0 (DeclValue <$> genDeclValue) <*> genCmdWith,
    CmdLam <$> genPatterns <*> genCmdWith,
    CmdPar <$> genCmdWith
  ]

genArrAppType :: Gen ArrAppType
genArrAppType = elements [HsFirstOrderApp, HsHigherOrderApp]

genCmdCaseAltsWith :: Gen [CaseAlt Cmd]
genCmdCaseAltsWith = smallList1 genCmdCaseAltWith

genCmdCaseAltWith :: Gen (CaseAlt Cmd)
genCmdCaseAltWith = scale (`div` 2) $ do
  CaseAlt [] <$> genPattern <*> genCmdRhsWith

genCmdDoStmtsWith :: Gen [DoStmt Cmd]
genCmdDoStmtsWith = smallList1 genCmdDoStmtWith

genCmdDoStmtWith :: Gen (DoStmt Cmd)
genCmdDoStmtWith =
  scale (`div` 2) . oneof $
    [ DoBind <$> genPattern <*> genCmdWith,
      DoLetDecls <$> smallList0 (DeclValue <$> genDeclValue),
      DoExpr <$> genCmdWith,
      DoRecStmt <$> genCmdRecDoStmtsWith
    ]

genCmdRecDoStmtsWith :: Gen [DoStmt Cmd]
genCmdRecDoStmtsWith =
  scale (`div` 2) . smallList1 $
    oneof
      [ DoBind <$> genPattern <*> genCmdWith,
        DoLetDecls <$> smallList0 (DeclValue <$> genDeclValue),
        DoExpr <$> genCmdWith
      ]

genCaseAltsWith :: Gen [CaseAlt Expr]
genCaseAltsWith = smallList0 genCaseAltWith

genCaseAltWith :: Gen (CaseAlt Expr)
genCaseAltWith = CaseAlt [] <$> genPattern <*> genRhs

genLambdaCaseAltsWith :: Gen [LambdaCaseAlt]
genLambdaCaseAltsWith = smallList0 genLambdaCaseAltWith

genLambdaCaseAltWith :: Gen LambdaCaseAlt
genLambdaCaseAltWith = scale (`div` 2) $ do
  LambdaCaseAlt [] <$> genPatterns <*> genRhs

genRhs :: Gen (Rhs Expr)
genRhs =
  oneof
    [ UnguardedRhs [] <$> genExpr <*> genWhereDecls,
      GuardedRhss [] <$> genGuardedRhsListWith <*> genWhereDecls
    ]

genCmdRhsWith :: Gen (Rhs Cmd)
genCmdRhsWith =
  oneof
    [ UnguardedRhs [] <$> genCmdWith <*> genWhereDecls,
      GuardedRhss [] <$> genCmdGuardedRhsListWith <*> genWhereDecls
    ]

genGuardedRhsListWith :: Gen [GuardedRhs Expr]
genGuardedRhsListWith = smallList1 genGuardedRhsWith

genGuardedRhsWith :: Gen (GuardedRhs Expr)
genGuardedRhsWith = GuardedRhs [] <$> smallList1 genGuardQualifierWith <*> genExpr

genCmdGuardedRhsListWith :: Gen [GuardedRhs Cmd]
genCmdGuardedRhsListWith = smallList1 genCmdGuardedRhsWith

genCmdGuardedRhsWith :: Gen (GuardedRhs Cmd)
genCmdGuardedRhsWith = GuardedRhs [] <$> smallList1 genGuardQualifierWith <*> genCmdWith

-- | Generate a guard qualifier.
genGuardQualifierWith :: Gen GuardQualifier
genGuardQualifierWith =
  oneof
    [ -- Boolean guard: | expr = ...
      GuardExpr <$> genExpr,
      -- Pattern guard: | pat <- expr = ...
      -- The guarded-qualifier parser now accepts the full pattern generator,
      -- which includes parenthesized view patterns such as `(view -> pat)`.
      scale (`div` 2) (GuardPat <$> genPattern <*> genExpr),
      -- Let guard: | let decls = ...
      GuardLet <$> smallList0 (DeclValue <$> genDeclValue)
    ]

genDoFlavor :: Gen DoFlavor
genDoFlavor =
  oneof
    [ pure DoPlain,
      pure DoMdo,
      DoQualified <$> genModuleQualifier,
      DoQualifiedMdo <$> genModuleQualifier
    ]

genDoStmtsWith :: Gen [DoStmt Expr]
genDoStmtsWith = do
  stmts <- smallList0 genDoStmtWith
  -- Last statement must be DoExpr
  lastExpr <- genExpr
  pure (stmts <> [DoExpr lastExpr])

genDoStmtWith :: Gen (DoStmt Expr)
genDoStmtWith =
  scale (`div` 2) $
    oneof
      [ DoBind <$> genPattern <*> genExpr,
        DoLetDecls <$> smallList0 (DeclValue <$> genDeclValue),
        DoExpr <$> genExpr,
        DoRecStmt <$> genRecDoStmtsWith
      ]

-- | Generate statements for a @rec@ block inside @mdo@/@do@.
-- At least one statement is required.
genRecDoStmtsWith :: Gen [DoStmt Expr]
genRecDoStmtsWith =
  scale (`div` 2) $
    smallList1 $
      oneof
        [ DoBind <$> genPattern <*> genExpr,
          DoLetDecls <$> smallList0 (DeclValue <$> genDeclValue),
          DoExpr <$> genExpr
        ]

genCompStmtsWith :: Gen [CompStmt]
genCompStmtsWith = do
  firstStmt <- genCompStmtInitialWith
  restStmts <- smallList0 genCompStmtWith
  pure (firstStmt : restStmts)

genCompStmtInitialWith :: Gen CompStmt
genCompStmtInitialWith =
  scale (`div` 2) $
    oneof
      [ CompGen <$> genPattern <*> genExpr,
        CompGuard <$> genExpr,
        CompLetDecls <$> smallList0 (DeclValue <$> genDeclValue)
      ]

genCompStmtWith :: Gen CompStmt
genCompStmtWith =
  scale (`div` 2) $
    oneof
      [ CompGen <$> genPattern <*> genExpr,
        CompGuard <$> genExpr,
        CompLetDecls <$> smallList0 (DeclValue <$> genDeclValue),
        CompThen <$> genExpr,
        CompThenBy <$> genExpr <*> genExpr,
        CompGroupUsing <$> genExpr,
        CompGroupByUsing <$> genExpr <*> genExpr
      ]

genParallelCompStmtsWith :: Gen [[CompStmt]]
genParallelCompStmtsWith = smallList2 genCompStmtsWith

genListElemsWith :: Gen [Expr]
genListElemsWith = smallList0 genExpr

-- | Generate tuple elements
genTupleElemsWith :: Gen [Expr]
genTupleElemsWith = smallList2 genExpr

-- | Generate elements for an unboxed tuple (0-4 elements).
-- Unlike boxed tuples, unboxed tuples with 0 elements are valid Haskell.
-- Sometimes generates layout-sensitive expressions (case, do, if, let, lambda)
-- to ensure proper implicit layout handling with unboxed tuple delimiters.
genUnboxedTupleElemsWith :: Gen [Expr]
genUnboxedTupleElemsWith =
  smallList0 genExpr

genUnboxedSumExprWith :: Gen Expr
genUnboxedSumExprWith = do
  arity <- chooseInt (2, 4)
  altIdx <- chooseInt (0, arity - 1)
  EUnboxedSum altIdx arity <$> genExpr

genTupleSectionElemsWith :: Gen [Maybe Expr]
genTupleSectionElemsWith = do
  elems <- smallList2 genMaybeExprWith
  -- Ensure at least one Nothing (otherwise it's just a tuple)
  if Nothing `notElem` elems
    then do
      idx <- chooseInt (0, length elems - 1)
      pure (take idx elems <> [Nothing] <> drop (idx + 1) elems)
    else pure elems

genMaybeExprWith :: Gen (Maybe Expr)
genMaybeExprWith =
  optional genExpr

genArithSeqWith :: Gen ArithSeq
genArithSeqWith =
  scale (`div` 2) $
    oneof
      [ ArithSeqFrom <$> genExpr,
        ArithSeqFromThen <$> genExpr <*> genExpr,
        ArithSeqFromTo <$> genExpr <*> genExpr,
        ArithSeqFromThenTo <$> genExpr <*> genExpr <*> genExpr
      ]

genRecordFieldsWith :: Gen [RecordField Expr]
genRecordFieldsWith =
  smallList0 $
    RecordField <$> genVarName <*> genExpr <*> pure False

-- | Generate a field name for OverloadedRecordDot.
-- Uses an unqualified variable name (field names are always unqualified).
genRecordFieldName :: Gen Name
genRecordFieldName = qualifyName Nothing . mkUnqualifiedName NameVarId <$> genFieldName

-- | Literal expression constructors
mkHexExpr :: Integer -> Expr
mkHexExpr value = EInt value TInteger ("0x" <> T.pack (showHex value))

mkFloatExpr :: Rational -> Expr
mkFloatExpr value = EFloat value TFractional (renderFloat value)

mkCharExpr :: Char -> Expr
mkCharExpr value = EChar value (T.pack (show value))

mkCharHashExpr :: Char -> Expr
mkCharHashExpr value = ECharHash value (T.pack (show value) <> "#")

mkStringExpr :: Text -> Expr
mkStringExpr value = EString value (T.pack (show (T.unpack value)))

mkStringHashExpr :: Text -> Expr
mkStringHashExpr value = EStringHash value (T.pack (show (T.unpack value)) <> "#")

-- | Create an integer expression with canonical representation.
mkIntExpr :: Integer -> Expr
mkIntExpr value = EInt value TInteger (T.pack (show value))

renderFloat :: Rational -> T.Text
renderFloat value = T.pack (show (fromRational value :: Double))

shrinkOverloadedLabel :: Text -> Text -> [String]
shrinkOverloadedLabel value raw
  | Just unquoted <- T.stripPrefix "#" raw,
    not ("\"" `T.isPrefixOf` unquoted) =
      [ T.unpack candidate
      | candidate <-
          ["a"]
            <> [T.map replaceUnicode value | T.any (> '\x7f') value]
            <> map T.pack (shrink (T.unpack value)),
        candidate /= value,
        isValidLabelName candidate
      ]
  | otherwise = []
  where
    replaceUnicode c = if c > '\x7f' then 'a' else c
    isValidLabelName name =
      case T.uncons name of
        Just (first, rest) ->
          (first `elem` (['a' .. 'z'] <> ['A' .. 'Z'] <> ['_']))
            && T.all isUnquotedLabelChar rest
        Nothing -> False
    isUnquotedLabelChar c =
      not (isSpace c) && c `notElem` ("()[]{},;`#\"" :: String)

-- | Shrink an expression for QuickCheck counterexample minimization.
shrinkExpr :: Expr -> [Expr]
shrinkExpr expr =
  [EList [] | expr /= EList []]
    ++ case expr of
      EVar name -> [EVar name' | name' <- shrinkName name]
      ETypeSyntax form ty -> [ETypeSyntax form ty' | ty' <- shrinkType ty]
      EInt value _ _ -> simpleVarExpr : [mkIntExpr shrunk | shrunk <- shrinkIntegral value]
      EFloat value _ _ -> simpleVarExpr : [mkFloatExpr shrunk | shrunk <- shrinkFloat value]
      EChar value _ ->
        simpleVarExpr
          : [mkCharExpr 'a' | value /= 'a']
      ECharHash value _ ->
        simpleVarExpr
          : [mkCharHashExpr 'a' | value /= 'a']
      EString value _ -> simpleVarExpr : [mkStringExpr (T.pack shrunk) | shrunk <- shrink (T.unpack value)]
      EStringHash value _ -> simpleVarExpr : [mkStringHashExpr (T.pack shrunk) | shrunk <- shrink (T.unpack value)]
      EOverloadedLabel value raw ->
        [EOverloadedLabel (T.pack shrunk) ("#" <> T.pack shrunk) | shrunk <- shrinkOverloadedLabel value raw]
      EPragma pragma inner -> inner : [EPragma pragma inner' | inner' <- shrinkExpr inner]
      EQuasiQuote quoter body ->
        [EVar (qualifyName Nothing (mkUnqualifiedName NameVarId "a"))]
          <> [EQuasiQuote quoter' body | quoter' <- "q" : T.inits quoter, isValidQuasiQuoteName quoter', quoter' /= quoter]
          <> [EQuasiQuote quoter (T.pack shrunk) | shrunk <- shrink (T.unpack body)]
      EApp fn arg ->
        [fn, arg]
          <> [EApp fn' arg | fn' <- shrinkExpr fn]
          <> [EApp fn arg' | arg' <- shrinkExpr arg]
      EInfix lhs op rhs ->
        [lhs, rhs]
          <> [EInfix lhs op' rhs | op' <- shrinkName op]
          <> [EInfix simpleVarExpr op rhs | lhs /= EList [] && not (isSimpleVarExpr lhs)]
          <> [EInfix lhs op rhs' | rhs' <- shrinkExpr rhs]
          <> [EInfix lhs' op rhs | lhs' <- shrinkExpr lhs]
      ENegate inner -> inner : [ENegate inner' | inner' <- shrinkExpr inner]
      ESectionL inner op ->
        inner
          : [ESectionL inner' op | inner' <- shrinkExpr inner]
            <> [ESectionL inner op' | op' <- shrinkName op]
      ESectionR op inner ->
        inner
          : [ESectionR op inner' | isValidRightSectionOpName op, inner' <- shrinkExpr inner]
            <> [ESectionR op' inner | op' <- shrinkName op, isValidRightSectionOpName op']
      EIf cond thenE elseE ->
        [thenE, elseE]
          <> [EIf cond' thenE elseE | cond' <- shrinkExpr cond]
          <> [EIf cond thenE' elseE | thenE' <- shrinkExpr thenE]
          <> [EIf cond thenE elseE' | elseE' <- shrinkExpr elseE]
      EMultiWayIf rhss ->
        [guardedRhsBody grhs | grhs : _ <- [rhss]]
          <> [EMultiWayIf rhss' | rhss' <- shrinkList shrinkGuardedRhs rhss, not (null rhss')]
      ECase scrutinee alts ->
        scrutinee
          : [ECase scrutinee' alts | scrutinee' <- shrinkExpr scrutinee]
            <> [ECase scrutinee alts' | alts' <- shrinkCaseAlts alts, not (null alts')]
      ELambdaPats pats body ->
        body
          : [ELambdaPats pats body' | body' <- shrinkExpr body]
            <> [ELambdaPats pats' body | pats' <- shrinkList shrinkPattern pats, not (null pats')]
      ELambdaCase alts ->
        [body | CaseAlt {caseAltRhs = UnguardedRhs _ body _} <- alts]
          <> [ELambdaCase alts' | alts' <- shrinkCaseAlts alts, not (null alts')]
      ELambdaCases alts ->
        [body | LambdaCaseAlt {lambdaCaseAltRhs = UnguardedRhs _ body _} <- alts]
          <> [ELambdaCases alts' | alts' <- shrinkLambdaCaseAlts alts, not (null alts')]
      ELetDecls decls body ->
        [ELetDecls [simpleLetDecl] simpleUnitExpr | not (isSimpleLetTarget decls body)]
          <> ( body
                 : [ELetDecls decls body' | body' <- shrinkExpr body]
                   <> [ELetDecls decls' body | decls' <- shrinkDecls decls, not (null decls')]
             )
      EDo stmts flavor ->
        [body | [DoExpr body] <- [stmts]]
          <> [EDo stmts flavor' | flavor' <- shrinkDoFlavor flavor]
          <> [EDo stmts' flavor | stmts' <- shrinkDoStmts stmts, not (null stmts')]
      EListComp body stmts ->
        body
          : [EListComp body' stmts | body' <- shrinkExpr body]
            <> [EListComp body stmts' | stmts' <- shrinkCompStmts stmts, not (null stmts')]
      EListCompParallel body stmtss ->
        body
          : [EListCompParallel body' stmtss | body' <- shrinkExpr body]
            -- Each branch needs at least one statement, so filter out empty branches
            <> [EListCompParallel body stmtss' | stmtss' <- shrinkParallelCompStmts stmtss, length stmtss' >= 2]
      EList elems ->
        [EList elems' | elems' <- shrinkList shrinkExpr elems]
      ETuple tupleFlavor elems ->
        [ETuple Boxed elems | tupleFlavor == Unboxed, length elems /= 1]
          <> [ETuple tupleFlavor elems' | elems' <- shrinkTupleMaybeElems shrinkMaybeExpr elems]
      EArithSeq seq' ->
        [EArithSeq seq'' | seq'' <- shrinkArithSeq seq']
      ERecordCon con fields _ ->
        [simpleVarExpr]
          <> [ERecordCon con' fields False | con' <- shrinkName con]
          <> [ERecordCon con fields' False | fields' <- shrinkRecordFields fields]
      ERecordUpd target fields ->
        target
          : [ERecordUpd target' fields | target' <- shrinkExpr target]
            <> [ERecordUpd target fields' | fields' <- shrinkRecordFields fields]
      EGetField base fieldName ->
        base
          : [EGetField base' fieldName | base' <- shrinkExpr base]
            <> [EGetField base fieldName' | fieldName' <- shrinkName fieldName]
      EGetFieldProjection fields ->
        [EGetFieldProjection fields' | fields' <- shrinkList shrinkName fields, not (null fields')]
      ETypeSig inner ty ->
        inner
          : [ETypeSig inner ty' | ty' <- shrinkType ty]
            <> [ETypeSig inner' ty | inner' <- shrinkExpr inner]
      ETypeApp inner ty ->
        inner
          : [ETypeApp inner ty' | ty' <- shrinkType ty]
            <> [ETypeApp inner' ty | inner' <- shrinkExpr inner]
      EUnboxedSum altIdx arity inner ->
        [EUnboxedSum altIdx arity inner' | inner' <- shrinkExpr inner]
      EParen inner -> inner : [EParen inner' | inner' <- shrinkExpr inner]
      ETHExpQuote body -> body : [ETHExpQuote body' | body' <- shrinkExpr body]
      ETHTypedQuote body -> body : [ETHTypedQuote body' | body' <- shrinkExpr body]
      ETHDeclQuote decls ->
        [ETHDeclQuote decls' | decls' <- shrinkDecls decls, not (null decls')]
      ETHTypeQuote ty -> [ETHTypeQuote ty' | ty' <- shrinkType ty]
      ETHPatQuote pat -> [ETHPatQuote pat' | pat' <- shrinkPattern pat]
      ETHNameQuote e -> e : [ETHNameQuote e' | e' <- shrinkExpr e]
      ETHTypeNameQuote ty -> [ETHTypeNameQuote ty' | ty' <- shrinkType ty]
      ETHSplice body -> body : [ETHSplice body' | body' <- shrinkExpr body]
      ETHTypedSplice body -> body : [ETHTypedSplice body' | body' <- shrinkExpr body]
      EProc pat body ->
        [EProc pat' body | pat' <- shrinkPattern pat]
          <> [EProc pat body' | body' <- shrinkCmd body]
      EAnn _ sub -> shrinkExpr sub

shrinkCmd :: Cmd -> [Cmd]
shrinkCmd cmd =
  case peelCmdAnn cmd of
    CmdAnn _ inner -> shrinkCmd inner
    CmdArrApp lhs appTy rhs ->
      [CmdArrApp lhs' appTy rhs | lhs' <- shrinkExpr lhs]
        <> [CmdArrApp lhs appTy rhs' | rhs' <- shrinkExpr rhs]
    CmdInfix lhs op rhs ->
      [lhs, rhs]
        <> [CmdInfix lhs op' rhs | op' <- shrinkName op]
        <> [CmdInfix lhs' op rhs | lhs' <- shrinkCmd lhs]
        <> [CmdInfix lhs op rhs' | rhs' <- shrinkCmd rhs]
    CmdDo stmts -> [CmdDo stmts' | stmts' <- shrinkCmdDoStmts stmts, not (null stmts')]
    CmdIf cond yes no ->
      [yes, no]
        <> [CmdIf cond' yes no | cond' <- shrinkExpr cond]
        <> [CmdIf cond yes' no | yes' <- shrinkCmd yes]
        <> [CmdIf cond yes no' | no' <- shrinkCmd no]
    CmdCase scrut alts ->
      [CmdCase scrut' alts | scrut' <- shrinkExpr scrut]
        <> [CmdCase scrut alts' | alts' <- shrinkCmdCaseAlts alts, not (null alts')]
    CmdLet decls body ->
      body
        : [CmdLet decls' body | decls' <- shrinkDecls decls, not (null decls')]
          <> [CmdLet decls body' | body' <- shrinkCmd body]
    CmdLam pats body ->
      body
        : [CmdLam pats' body | pats' <- shrinkList shrinkPattern pats, not (null pats')]
          <> [CmdLam pats body' | body' <- shrinkCmd body]
    CmdApp cmd' expr ->
      cmd'
        : [CmdApp cmd'' expr | cmd'' <- shrinkCmd cmd']
          <> [CmdApp cmd' expr' | expr' <- shrinkExpr expr]
    CmdPar inner -> inner : [CmdPar inner' | inner' <- shrinkCmd inner]

shrinkCmdDoStmts :: [DoStmt Cmd] -> [[DoStmt Cmd]]
shrinkCmdDoStmts = shrinkList shrinkCmdDoStmt

shrinkCmdDoStmt :: DoStmt Cmd -> [DoStmt Cmd]
shrinkCmdDoStmt stmt =
  case peelDoStmtAnn stmt of
    DoBind pat cmd ->
      [DoBind pat' cmd | pat' <- shrinkPattern pat]
        <> [DoBind pat cmd' | cmd' <- shrinkCmd cmd]
    DoLetDecls decls -> [DoLetDecls decls' | decls' <- shrinkDecls decls, not (null decls')]
    DoExpr cmd -> [DoExpr cmd' | cmd' <- shrinkCmd cmd]
    DoRecStmt stmts -> [DoRecStmt stmts' | stmts' <- shrinkCmdDoStmts stmts, not (null stmts')]
    DoAnn _ _ -> []

shrinkCmdCaseAlts :: [CaseAlt Cmd] -> [[CaseAlt Cmd]]
shrinkCmdCaseAlts = shrinkList shrinkCmdCaseAlt

shrinkCmdCaseAlt :: CaseAlt Cmd -> [CaseAlt Cmd]
shrinkCmdCaseAlt alt =
  [alt {caseAltPattern = pat'} | pat' <- shrinkPattern (caseAltPattern alt)]
    <> [alt {caseAltRhs = rhs'} | rhs' <- shrinkCmdRhs (caseAltRhs alt)]

shrinkCaseAlts :: [CaseAlt Expr] -> [[CaseAlt Expr]]
shrinkCaseAlts = shrinkList shrinkCaseAlt

shrinkLambdaCaseAlts :: [LambdaCaseAlt] -> [[LambdaCaseAlt]]
shrinkLambdaCaseAlts = shrinkList shrinkLambdaCaseAlt

shrinkCaseAlt :: CaseAlt Expr -> [CaseAlt Expr]
shrinkCaseAlt alt =
  [alt {caseAltPattern = pat'} | pat' <- shrinkPattern (caseAltPattern alt)]
    <> [alt {caseAltRhs = rhs'} | rhs' <- shrinkLetRhs (caseAltRhs alt)]

shrinkLambdaCaseAlt :: LambdaCaseAlt -> [LambdaCaseAlt]
shrinkLambdaCaseAlt alt =
  [alt {lambdaCaseAltPats = pats'} | pats' <- shrinkList shrinkPattern (lambdaCaseAltPats alt), not (null pats')]
    <> [alt {lambdaCaseAltRhs = rhs'} | rhs' <- shrinkLetRhs (lambdaCaseAltRhs alt)]

shrinkGuardedRhs :: GuardedRhs Expr -> [GuardedRhs Expr]
shrinkGuardedRhs grhs =
  [grhs {guardedRhsBody = body'} | body' <- shrinkExpr (guardedRhsBody grhs)]
    <> [grhs {guardedRhsGuards = gs'} | gs' <- shrinkList shrinkGuardQualifier (guardedRhsGuards grhs), not (null gs')]

shrinkCmdGuardedRhs :: GuardedRhs Cmd -> [GuardedRhs Cmd]
shrinkCmdGuardedRhs grhs =
  [grhs {guardedRhsBody = body'} | body' <- shrinkCmd (guardedRhsBody grhs)]
    <> [grhs {guardedRhsGuards = gs'} | gs' <- shrinkList shrinkGuardQualifier (guardedRhsGuards grhs), not (null gs')]

-- | Shrink a guard qualifier.
shrinkGuardQualifier :: GuardQualifier -> [GuardQualifier]
shrinkGuardQualifier gq =
  case gq of
    GuardAnn _ inner -> inner : shrinkGuardQualifier inner
    GuardExpr expr -> [GuardExpr expr' | expr' <- shrinkExpr expr]
    GuardPat pat expr ->
      [GuardExpr expr]
        <> [GuardPat pat' expr | pat' <- shrinkPattern pat]
        <> [GuardPat pat expr' | expr' <- shrinkExpr expr]
    GuardLet decls -> [GuardLet decls' | decls' <- shrinkDecls decls, not (null decls')]

shrinkDecls :: [Decl] -> [[Decl]]
shrinkDecls = shrinkList shrinkLetDecl

shrinkLetDecl :: Decl -> [Decl]
shrinkLetDecl decl =
  case decl of
    DeclAnn _ inner -> inner : shrinkLetDecl inner
    DeclValue (PatternBind multTag pat rhs) ->
      [DeclValue (PatternBind multTag pat rhs') | rhs' <- shrinkLetRhs rhs]
        <> [DeclValue (PatternBind multTag pat' rhs) | pat' <- shrinkPattern pat]
    DeclValue (FunctionBind name matches) ->
      [ DeclValue (PatternBind NoMultiplicityTag (PVar name) (matchRhs match))
      | match <- matches
      ]
        <>
        -- Shrink multiple matches to a single match
        [DeclValue (FunctionBind name [m {matchAnns = []}]) | length matches > 1, m <- matches]
        -- Shrink individual matches
        <> [DeclValue (FunctionBind name ms') | ms' <- shrinkList shrinkLetMatch matches, not (null ms')]
        <> [DeclValue (FunctionBind name' matches) | name' <- shrinkUnqualifiedName name]
    DeclTypeSig names ty ->
      [DeclTypeSig names ty' | ty' <- shrinkType ty]
    _ -> []

-- | Shrink a match clause within let/where/TH contexts.
shrinkLetMatch :: Match -> [Match]
shrinkLetMatch match =
  [match {matchAnns = [], matchRhs = rhs'} | rhs' <- shrinkLetRhs (matchRhs match)]
    <> [match {matchAnns = [], matchPats = pats'} | pats' <- shrinkFunctionHeadPats (matchHeadForm match) (matchPats match)]

-- | Shrink an RHS within let/where/TH contexts.
shrinkLetRhs :: Rhs Expr -> [Rhs Expr]
shrinkLetRhs rhs =
  case rhs of
    UnguardedRhs _ expr mWhere ->
      [rhs' | ds <- maybe [] pure mWhere, rhs' <- whereValueRhss ds]
        <> [UnguardedRhs [] expr Nothing | isJust mWhere]
        <> [UnguardedRhs [] expr' mWhere | expr' <- shrinkExpr expr]
        <> [UnguardedRhs [] expr (Just ds') | Just ds <- [mWhere], ds' <- shrinkDecls ds, not (null ds')]
    GuardedRhss _ rhss mWhere ->
      -- Collapse to unguarded using the first guard's body
      [UnguardedRhs [] (guardedRhsBody firstRhs) Nothing | firstRhs : _ <- [rhss]]
        <> [GuardedRhss [] rhss Nothing | isJust mWhere]
        <> [GuardedRhss [] rhss' mWhere | rhss' <- shrinkList shrinkGuardedRhs rhss, not (null rhss')]
        <> [GuardedRhss [] rhss (Just ds') | Just ds <- [mWhere], ds' <- shrinkDecls ds, not (null ds')]

shrinkCmdRhs :: Rhs Cmd -> [Rhs Cmd]
shrinkCmdRhs rhs =
  case rhs of
    UnguardedRhs _ body mWhere ->
      [UnguardedRhs [] body Nothing | isJust mWhere]
        <> [UnguardedRhs [] body' mWhere | body' <- shrinkCmd body]
        <> [UnguardedRhs [] body (Just ds') | Just ds <- [mWhere], ds' <- shrinkDecls ds, not (null ds')]
    GuardedRhss _ rhss mWhere ->
      [UnguardedRhs [] (guardedRhsBody firstRhs) Nothing | firstRhs : _ <- [rhss]]
        <> [GuardedRhss [] rhss Nothing | isJust mWhere]
        <> [GuardedRhss [] rhss' mWhere | rhss' <- shrinkList shrinkCmdGuardedRhs rhss, not (null rhss')]
        <> [GuardedRhss [] rhss (Just ds') | Just ds <- [mWhere], ds' <- shrinkDecls ds, not (null ds')]

whereValueRhss :: [Decl] -> [Rhs Expr]
whereValueRhss decls =
  [ rhs
  | DeclValue valueDecl <- decls,
    rhs <- case valueDecl of
      PatternBind _ _ patternRhs -> [patternRhs]
      FunctionBind _ matches -> map matchRhs matches
  ]

shrinkDoStmts :: [DoStmt Expr] -> [[DoStmt Expr]]
shrinkDoStmts stmts =
  case stmts of
    [stmt] -> [[stmt'] | stmt' <- shrinkDoStmt stmt, isDoExprStmt stmt']
    _ -> [stmts' | stmts' <- shrinkList shrinkDoStmt stmts, doStmtsEndInExpr stmts']

doStmtsEndInExpr :: [DoStmt Expr] -> Bool
doStmtsEndInExpr stmts =
  case reverse stmts of
    stmt : _ -> isDoExprStmt stmt
    [] -> False

isDoExprStmt :: DoStmt Expr -> Bool
isDoExprStmt stmt =
  case peelDoStmtAnn stmt of
    DoExpr {} -> True
    _ -> False

shrinkDoFlavor :: DoFlavor -> [DoFlavor]
shrinkDoFlavor flavor =
  case flavor of
    DoPlain -> []
    DoMdo -> [DoPlain]
    DoQualified name ->
      DoPlain : [DoQualified name' | name' <- shrinkQualifier name]
    DoQualifiedMdo name ->
      DoMdo
        : DoQualified name
        : [DoQualifiedMdo name' | name' <- shrinkQualifier name]

shrinkDoStmt :: DoStmt Expr -> [DoStmt Expr]
shrinkDoStmt stmt =
  case peelDoStmtAnn stmt of
    DoBind pat expr ->
      [DoBind pat' expr | pat' <- shrinkPattern pat]
        <> [DoBind pat expr' | expr' <- shrinkExpr expr]
    DoLetDecls decls -> [DoLetDecls decls' | decls' <- shrinkDecls decls, not (null decls')]
    DoExpr expr -> [DoExpr expr' | expr' <- shrinkExpr expr]
    DoRecStmt stmts -> [DoRecStmt stmts' | stmts' <- shrinkDoStmts stmts, not (null stmts')]
    DoAnn _ _ -> []

shrinkCompStmts :: [CompStmt] -> [[CompStmt]]
shrinkCompStmts =
  filter compStmtsCanStart . shrinkList shrinkCompStmt

compStmtsCanStart :: [CompStmt] -> Bool
compStmtsCanStart (stmt : _) = not (isTransformCompStmt stmt)
compStmtsCanStart [] = True

isTransformCompStmt :: CompStmt -> Bool
isTransformCompStmt stmt =
  case peelCompStmtAnn stmt of
    CompThen {} -> True
    CompThenBy {} -> True
    CompGroupUsing {} -> True
    CompGroupByUsing {} -> True
    _ -> False

-- | Shrink parallel comprehension branches, ensuring each branch has at least one statement
shrinkParallelCompStmts :: [[CompStmt]] -> [[[CompStmt]]]
shrinkParallelCompStmts =
  -- Each branch must have at least one statement
  shrinkList shrinkCompStmtsNonEmpty
  where
    shrinkCompStmtsNonEmpty stmts =
      [stmts' | stmts' <- shrinkCompStmts stmts, not (null stmts'), compStmtsCanStart stmts']

shrinkCompStmt :: CompStmt -> [CompStmt]
shrinkCompStmt stmt =
  case peelCompStmtAnn stmt of
    CompGen pat expr ->
      [CompGen pat' expr | pat' <- shrinkPattern pat]
        <> [CompGen pat expr' | expr' <- shrinkExpr expr]
    CompGuard expr -> [CompGuard expr' | expr' <- shrinkExpr expr]
    CompLetDecls decls -> [CompLetDecls decls' | decls' <- shrinkDecls decls, not (null decls')]
    CompThen f -> [CompThen f' | f' <- shrinkExpr f]
    CompThenBy f e -> [CompThenBy f' e | f' <- shrinkExpr f] <> [CompThenBy f e' | e' <- shrinkExpr e]
    CompGroupUsing f -> [CompGroupUsing f' | f' <- shrinkExpr f]
    CompGroupByUsing e f -> [CompGroupByUsing e' f | e' <- shrinkExpr e] <> [CompGroupByUsing e f' | f' <- shrinkExpr f]
    CompAnn _ _ -> []

shrinkTupleMaybeElems :: (a -> [a]) -> [a] -> [[a]]
shrinkTupleMaybeElems shrinkElem elems =
  case elems of
    [] -> [] -- Unit can't shrink
    _ ->
      [elems' | elems' <- shrinkList shrinkElem elems, length elems' /= 1]

shrinkMaybeExpr :: Maybe Expr -> [Maybe Expr]
shrinkMaybeExpr mExpr =
  case mExpr of
    Nothing -> []
    Just expr -> Nothing : [Just expr' | expr' <- shrinkExpr expr]

shrinkArithSeq :: ArithSeq -> [ArithSeq]
shrinkArithSeq seq' =
  case peelArithSeqAnn seq' of
    ArithSeqFrom from ->
      [ArithSeqFrom from' | from' <- shrinkExpr from]
    ArithSeqFromThen from thenE ->
      ArithSeqFrom from
        : [ArithSeqFromThen from' thenE | from' <- shrinkExpr from]
          <> [ArithSeqFromThen from thenE' | thenE' <- shrinkExpr thenE]
    ArithSeqFromTo from to ->
      ArithSeqFrom from
        : [ArithSeqFromTo from' to | from' <- shrinkExpr from]
          <> [ArithSeqFromTo from to' | to' <- shrinkExpr to]
    ArithSeqFromThenTo from thenE to ->
      ArithSeqFromTo from to
        : [ArithSeqFromThenTo from' thenE to | from' <- shrinkExpr from]
          <> [ArithSeqFromThenTo from thenE' to | thenE' <- shrinkExpr thenE]
          <> [ArithSeqFromThenTo from thenE to' | to' <- shrinkExpr to]
    ArithSeqAnn _ _ -> []

shrinkRecordFields :: [RecordField Expr] -> [[RecordField Expr]]
shrinkRecordFields = shrinkList shrinkRecordField

shrinkRecordField :: RecordField Expr -> [RecordField Expr]
shrinkRecordField field =
  [field {recordFieldName = name'} | name' <- shrinkName (recordFieldName field)]
    <> [field {recordFieldValue = expr'} | expr' <- shrinkExpr (recordFieldValue field)]

instance Arbitrary Expr where
  arbitrary = resize 5 genExpr
  shrink = shrinkExpr

simpleVarExpr :: Expr
simpleVarExpr = EVar simpleVarName

simpleVarName :: Name
simpleVarName = qualifyName Nothing (mkUnqualifiedName NameVarId "a")

genRightSectionOpName :: Gen Name
genRightSectionOpName =
  genVarName `suchThat` isValidRightSectionOpName

isValidRightSectionOpName :: Name -> Bool
isValidRightSectionOpName name =
  not $
    isNothing (nameQualifier name)
      && nameType name == NameVarSym
      && nameText name == "-"

isSimpleVarExpr :: Expr -> Bool
isSimpleVarExpr expr =
  case expr of
    EVar name -> name == simpleVarName
    _ -> False

simpleUnitExpr :: Expr
simpleUnitExpr = EList []

simpleLetDecl :: Decl
simpleLetDecl =
  DeclValue
    ( PatternBind
        NoMultiplicityTag
        (PVar (mkUnqualifiedName NameVarId "a"))
        (UnguardedRhs [] simpleUnitExpr Nothing)
    )

isSimpleLetTarget :: [Decl] -> Expr -> Bool
isSimpleLetTarget decls body =
  body == simpleUnitExpr
    && case decls of
      [DeclValue (PatternBind NoMultiplicityTag pat (UnguardedRhs [] expr Nothing))] ->
        expr == simpleUnitExpr && isSimpleLetPattern pat
      _ -> False

isSimpleLetPattern :: Pattern -> Bool
isSimpleLetPattern pat =
  case pat of
    PVar name -> unqualifiedNameType name == NameVarId && unqualifiedNameText name == "a"
    PWildcard -> True
    _ -> False