packages feed

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

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

module Test.Properties.Arb.Pattern
  ( genPattern,
    shrinkPattern,
  )
where

import Aihc.Parser.Syntax
import Data.Text (Text)
import Data.Text qualified as T
import {-# SOURCE #-} Test.Properties.Arb.Expr (genExpr, shrinkExpr)
import Test.Properties.Arb.Identifiers
  ( genCharValue,
    genConName,
    genFieldName,
    genOptionalQualifier,
    genQuasiBody,
    genQuoterName,
    genStringValue,
    genTenths,
    genVarId,
    genVarName,
    genVarUnqualifiedName,
    genVarUnqualifiedNameNoHash,
    isValidQuoterName,
    showHex,
    shrinkFloat,
    shrinkIdent,
    shrinkName,
    shrinkUnqualifiedName,
  )
import {-# SOURCE #-} Test.Properties.Arb.Type (shrinkType)
import Test.Properties.Arb.Utils (smallList0, smallList2)
import Test.QuickCheck

instance Arbitrary Pattern where
  arbitrary = genPattern
  shrink = shrinkPattern

genPattern :: Gen Pattern
genPattern = scale (`div` 2) $ do
  n <- getSize
  if n <= 0
    then oneof leafGenerators
    else oneof (leafGenerators <> recursiveGenerators)
  where
    leafGenerators =
      [ PVar <$> genVarUnqualifiedName,
        pure PWildcard,
        PLit <$> genLiteral,
        PQuasiQuote <$> genQuoterName <*> genQuasiBody,
        PNegLit <$> genNumericLiteral,
        PSplice <$> genPatSpliceBody,
        PTuple Boxed <$> elements [[], [PVar (mkUnqualifiedName NameVarId "x"), PWildcard]],
        PTuple Unboxed <$> elements [[], [PVar (mkUnqualifiedName NameVarId "x")], [PVar (mkUnqualifiedName NameVarId "x"), PWildcard]],
        pure (PList []),
        PCon <$> genConName <*> pure [] <*> pure []
      ]
    recursiveGenerators =
      [ PTuple Boxed <$> genTupleElemsWith,
        PTuple Unboxed <$> genUnboxedTupleElemsWith,
        PList <$> genListElemsWith,
        genPatternConWith,
        genPatternInfixWith,
        PParen <$> genPattern,
        genRecordPatternWith,
        genPatternTypeSigWith,
        genUnboxedSumPatternWith,
        genViewPatternWith,
        PAs <$> genVarUnqualifiedNameNoHash <*> genPattern,
        PStrict <$> genPattern,
        PIrrefutable <$> genPattern
      ]

genViewPatternWith :: Gen Pattern
genViewPatternWith =
  PView <$> genExpr <*> genPattern

genPatternConWith :: Gen Pattern
genPatternConWith = PCon <$> genConName <*> pure [] <*> smallList0 genPattern

genPatternTypeSigWith :: Gen Pattern
genPatternTypeSigWith = PTypeSig <$> genPattern <*> genPatternType

-- | Generate a simple type for use in pattern type signatures.
genPatternType :: Gen Type
genPatternType =
  oneof
    [ TVar . mkUnqualifiedName NameVarId <$> genVarId,
      (`TCon` Unpromoted) <$> genConName
    ]

genPatternInfixWith :: Gen Pattern
genPatternInfixWith = PInfix <$> genPattern <*> genConName <*> genPattern

genTupleElemsWith :: Gen [Pattern]
genTupleElemsWith = oneof [pure [], smallList2 genPattern]

-- | Generate elements for an unboxed tuple pattern (0-4 elements).
-- Unlike boxed tuples, unboxed tuples with 0 elements are valid Haskell.
genUnboxedTupleElemsWith :: Gen [Pattern]
genUnboxedTupleElemsWith = smallList0 genPattern

genUnboxedSumPatternWith :: Gen Pattern
genUnboxedSumPatternWith = do
  arity <- chooseInt (2, 4)
  altIdx <- chooseInt (0, arity - 1)
  PUnboxedSum altIdx arity <$> genPattern

genListElemsWith :: Gen [Pattern]
genListElemsWith = smallList0 genPattern

genRecordPatternWith :: Gen Pattern
genRecordPatternWith = PRecord <$> genConName <*> genRecordFieldsWith <*> pure False

genRecordFieldsWith :: Gen [RecordField Pattern]
genRecordFieldsWith = do
  n <- chooseInt (0, 3)
  names <- vectorOf n genFieldName
  pats <- vectorOf n genPattern
  quals <- vectorOf n genOptionalQualifier
  let qualifiedNames = zipWith (\q name -> qualifyName q (mkUnqualifiedName NameVarId name)) quals names
  pure [RecordField fieldName fieldPat False | (fieldName, fieldPat) <- zip qualifiedNames pats]

genLiteral :: Gen Literal
genLiteral =
  oneof
    [ mkIntLiteral <$> chooseInteger (0, 999),
      mkHexLiteral <$> chooseInteger (0, 255),
      mkFloatLiteral <$> genTenths,
      mkCharLiteral <$> genCharValue,
      mkStringLiteral <$> genStringValue
    ]

genNumericLiteral :: Gen Literal
genNumericLiteral =
  oneof
    [ mkIntLiteral <$> chooseInteger (0, 999),
      mkHexLiteral <$> chooseInteger (0, 255),
      mkFloatLiteral <$> genTenths
    ]

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

mkIntLiteral :: Integer -> Literal
mkIntLiteral value = LitInt value TInteger (T.pack (show value))

mkHexLiteral :: Integer -> Literal
mkHexLiteral value = LitInt value TInteger ("0x" <> T.pack (showHex value))

mkFloatLiteral :: Rational -> Literal
mkFloatLiteral value = LitFloat value TFractional (renderFloat value)

mkCharLiteral :: Char -> Literal
mkCharLiteral value = LitChar value (T.pack (show value))

mkStringLiteral :: Text -> Literal
mkStringLiteral value = LitString value (T.pack (show (T.unpack value)))

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

shrinkPattern :: Pattern -> [Pattern]
shrinkPattern pat =
  [PWildcard | pat /= PWildcard]
    <> case pat of
      PAnn _ sub -> shrinkPattern sub
      PVar name ->
        [PVar shrunk | shrunk <- shrinkUnqualifiedName name]
      PTypeBinder binder -> [PTypeBinder binder' | binder' <- shrinkTyVarBinder binder]
      PTypeSyntax form ty -> [PTypeSyntax form ty' | ty' <- shrinkType ty]
      PWildcard -> []
      PLit lit ->
        [PLit shrunk | shrunk <- shrinkLiteral lit]
      PQuasiQuote quoter body ->
        [PQuasiQuote q body | q <- shrinkQuoterName quoter]
          <> [PQuasiQuote quoter b | b <- map T.pack (shrink (T.unpack body))]
      PTuple tupleFlavor elems ->
        shrinkPatternTupleElems tupleFlavor elems
      PList elems ->
        [PList elems' | elems' <- shrinkList shrinkPattern elems]
      PCon con typeArgs args ->
        [PCon con' typeArgs args | con' <- shrinkName con]
          <> [PCon con typeArgs [] | not (null args)]
          <> [PCon con typeArgs args' | args' <- shrinkList shrinkPattern args]
      PInfix lhs op rhs ->
        [lhs, rhs]
          <> [PInfix lhs' op rhs | lhs' <- shrinkPattern lhs]
          <> [PInfix lhs op' rhs | op' <- shrinkName op]
          <> [PInfix lhs op rhs' | rhs' <- shrinkPattern rhs]
      PView expr inner ->
        [inner]
          <> [PView expr' inner | expr' <- shrinkExpr expr]
          <> [PView expr inner' | inner' <- shrinkPattern inner]
      PAs name inner ->
        [inner]
          <> [PAs name' inner | name' <- shrinkUnqualifiedName name]
          <> [PAs name inner' | inner' <- shrinkPattern inner]
      PStrict inner ->
        [inner]
          <> [PStrict inner' | inner' <- shrinkPattern inner]
      PIrrefutable inner ->
        [inner]
          <> [PIrrefutable inner' | inner' <- shrinkPattern inner]
      PNegLit lit ->
        [PLit lit]
          <> [PNegLit shrunk | shrunk <- shrinkNumericLiteral lit]
      PParen inner ->
        [inner] <> [PParen inner' | inner' <- shrinkPattern inner]
      PUnboxedSum altIdx arity inner ->
        [inner]
          <> [PUnboxedSum (max 0 (altIdx - 1)) (arity - 1) inner | arity > 2]
          <> [PUnboxedSum altIdx arity inner' | inner' <- shrinkPattern inner]
      PRecord con fields _ ->
        [PRecord con' fields False | con' <- shrinkName con]
          <> [PRecord con [] False | not (null fields)]
          <> [PRecord con fields' False | fields' <- shrinkList shrinkField fields]
      PTypeSig inner ty ->
        [inner]
          <> [PTypeSig inner' ty | inner' <- shrinkPattern inner]
          <> [PTypeSig inner ty' | ty' <- shrinkType ty]
      PSplice expr ->
        [PSplice expr' | expr' <- shrinkExpr expr]

shrinkTyVarBinder :: TyVarBinder -> [TyVarBinder]
shrinkTyVarBinder tvb =
  [tvb {tyVarBinderName = name'} | name' <- shrinkIdent (tyVarBinderName tvb)]

shrinkPatternTupleElems :: TupleFlavor -> [Pattern] -> [Pattern]
shrinkPatternTupleElems tupleFlavor elems =
  -- For a unit boxed tuple (), try a simple variable as a simpler outer pattern
  ( case (tupleFlavor, elems) of
      (Boxed, []) -> [PVar (mkUnqualifiedName NameVarId "x")]
      _ -> []
  )
    -- For a single-element unboxed tuple, try extracting the element directly
    <> ( case (tupleFlavor, elems) of
           (Unboxed, [e]) -> [e]
           _ -> []
       )
    <> [ candidate
       | shrunk <- shrinkList shrinkPattern elems,
         candidate <- case shrunk of
           [] -> [PTuple tupleFlavor []]
           [_] -> [PTuple tupleFlavor shrunk | tupleFlavor == Unboxed]
           _ -> [PTuple tupleFlavor shrunk]
       ]

shrinkField :: RecordField Pattern -> [RecordField Pattern]
shrinkField field =
  [field {recordFieldName = fieldName'} | fieldName' <- shrinkName (recordFieldName field)]
    <> [field {recordFieldValue = shrunk} | shrunk <- shrinkPattern (recordFieldValue field)]

shrinkLiteral :: Literal -> [Literal]
shrinkLiteral lit =
  case peelLiteralAnn lit of
    LitInt value _ _ -> [mkIntLiteral shrunk | shrunk <- shrinkIntegral value]
    LitFloat value _ _ -> [mkFloatLiteral shrunk | shrunk <- shrinkFloat value, shrunk >= 0]
    LitChar c _ -> [mkCharLiteral shrunk | shrunk <- shrink c]
    LitCharHash c _ -> [LitCharHash shrunk (T.pack (show shrunk) <> "#") | shrunk <- shrink c]
    LitString txt _ -> [mkStringLiteral (T.pack shrunk) | shrunk <- shrink (T.unpack txt)]
    LitStringHash txt _ -> [LitStringHash (T.pack shrunk) (T.pack (show shrunk) <> "#") | shrunk <- shrink (T.unpack txt)]
    LitAnn {} -> error "unreachable"

shrinkNumericLiteral :: Literal -> [Literal]
shrinkNumericLiteral lit =
  case lit of
    LitInt {} -> shrinkLiteral lit
    LitFloat {} -> shrinkLiteral lit
    _ -> []

shrinkQuoterName :: Text -> [Text]
shrinkQuoterName name =
  [ candidate
  | candidate <- map T.pack (shrink (T.unpack name)),
    isValidQuoterName candidate
  ]