packages feed

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

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

module Test.Properties.Arb.Module
  ( genModuleName,
    shrinkModuleName,
    genUnqualifiedVarName,
    genTypeName,
    shrinkTypeName,
  )
where

import Aihc.Parser.Syntax
import Data.Text (Text)
import Data.Text qualified as T
import Test.Properties.Arb.Decl ()
import Test.Properties.Arb.Identifiers (genVarId, genVarSym, shrinkName, shrinkUnqualifiedName)
import Test.QuickCheck

instance Arbitrary Module where
  arbitrary = do
    n <- chooseInt (0, 6)
    imports <- genImportDecls
    mHead <- genMaybeModuleHead
    decls <- vectorOf n arbitrary
    pure $
      Module
        { moduleAnns = [],
          moduleHead = mHead,
          moduleLanguagePragmas = [],
          moduleImports = imports,
          moduleDecls = decls
        }

  shrink modu =
    [modu {moduleHead = Nothing} | Just _ <- [moduleHead modu]]
      <> [modu {moduleImports = []} | not (null (moduleImports modu))]
      <> [modu {moduleDecls = []} | not (null (moduleDecls modu))]
      <> [ modu {moduleDecls = shrunk}
         | shrunk <- shrinkList shrink (moduleDecls modu),
           not (null shrunk)
         ]
      <> [ modu {moduleImports = shrunk}
         | shrunk <- shrinkList shrinkImportDecl (moduleImports modu)
         ]
      <> [ modu {moduleHead = shrunk}
         | shrunk <- shrinkMaybeModuleHead (moduleHead modu)
         ]

-- | Generate an optional module head.
-- Most modules have explicit headers, but implicit modules (Nothing) are also valid.
genMaybeModuleHead :: Gen (Maybe ModuleHead)
genMaybeModuleHead =
  frequency
    [ (9, Just <$> genModuleHead), -- 90% explicit module header
      (1, pure Nothing) -- 10% implicit module (no module declaration)
    ]

genModuleHead :: Gen ModuleHead
genModuleHead = do
  name <- genModuleName
  exports <- genMaybeExportSpecs
  warningText <- frequency [(4, pure Nothing), (1, Just <$> genWarningPragma)]
  pure $
    ModuleHead
      { moduleHeadAnns = [],
        moduleHeadName = name,
        moduleHeadWarningPragma = warningText,
        moduleHeadExports = exports
      }

-- | Shrink an optional module head.
shrinkMaybeModuleHead :: Maybe ModuleHead -> [Maybe ModuleHead]
shrinkMaybeModuleHead mHead =
  case mHead of
    Nothing -> []
    Just head' ->
      Nothing : [Just shrunk | shrunk <- shrinkModuleHead head']

shrinkModuleHead :: ModuleHead -> [ModuleHead]
shrinkModuleHead head' =
  [ head' {moduleHeadName = shrunk}
  | shrunk <- shrinkModuleName (moduleHeadName head')
  ]
    <> [ head' {moduleHeadExports = shrunk}
       | shrunk <- shrinkMaybeExportSpecs (moduleHeadExports head')
       ]
    <> [ head' {moduleHeadWarningPragma = shrunk}
       | Just warning <- [moduleHeadWarningPragma head'],
         shrunk <- Nothing : map Just (shrinkWarningPragma warning)
       ]

genMaybeExportSpecs :: Gen (Maybe [ExportSpec])
genMaybeExportSpecs =
  frequency
    [ (3, pure Nothing),
      (2, Just <$> genExportSpecs)
    ]

shrinkMaybeExportSpecs :: Maybe [ExportSpec] -> [Maybe [ExportSpec]]
shrinkMaybeExportSpecs mSpecs =
  case mSpecs of
    Nothing -> []
    Just specs ->
      Nothing : [Just shrunk | shrunk <- shrinkList shrink specs]

genExportSpecs :: Gen [ExportSpec]
genExportSpecs = do
  n <- chooseInt (0, 3)
  vectorOf n arbitrary

genExportVarName :: Gen Name
genExportVarName = qualifyName Nothing <$> genUnqualifiedVarName

genExportTypeName :: Gen Name
genExportTypeName = qualifyName Nothing <$> genTypeName

genWarningMessage :: Gen Text
genWarningMessage = do
  n <- chooseInt (1, 8)
  chars <- vectorOf n (elements (['a' .. 'z'] <> ['A' .. 'Z'] <> [' ']))
  pure (T.pack chars)

shrinkWarningMessage :: Text -> [Text]
shrinkWarningMessage msg =
  [T.pack candidate | candidate <- shrink (T.unpack msg), not (null candidate)]

genMaybeWarningPragma :: Gen (Maybe Pragma)
genMaybeWarningPragma = frequency [(4, pure Nothing), (1, Just <$> genWarningPragma)]

genWarningPragma :: Gen Pragma
genWarningPragma = do
  msg <- genWarningMessage
  pt <- elements [PragmaWarning msg, PragmaDeprecated msg]
  pure Pragma {pragmaType = pt, pragmaRawText = ""}

shrinkWarningPragma :: Pragma -> [Pragma]
shrinkWarningPragma p = case pragmaType p of
  PragmaWarning msg -> [p {pragmaType = PragmaWarning msg'} | msg' <- shrinkWarningMessage msg]
  PragmaDeprecated msg -> [p {pragmaType = PragmaDeprecated msg'} | msg' <- shrinkWarningMessage msg]
  _ -> []

instance Arbitrary ExportSpec where
  arbitrary =
    oneof
      [ ExportModule <$> genMaybeWarningPragma <*> genModuleName,
        ExportVar <$> genMaybeWarningPragma <*> pure Nothing <*> genExportVarName,
        ExportAbs <$> genMaybeWarningPragma <*> arbitrary <*> genExportTypeName,
        -- Namespace keywords (pattern/type/data) before T(..) are not valid GHC syntax
        ExportAll <$> genMaybeWarningPragma <*> pure Nothing <*> genExportTypeName,
        -- Namespace keywords (pattern/type/data) are not valid in ExportWith/ExportWithAll
        ExportWith <$> genMaybeWarningPragma <*> pure Nothing <*> genExportTypeName <*> genExportMembers,
        genExportWithAll
      ]

  shrink spec =
    case spec of
      ExportAnn _ sub -> sub : shrink sub
      ExportModule _ modName ->
        [ExportModule Nothing shrunk | shrunk <- shrinkModuleName modName]
      ExportVar mWarning namespace name ->
        [ExportVar Nothing namespace name | Just _ <- [mWarning]]
          <> [ExportVar mWarning namespace shrunk | shrunk <- shrinkName name]
      ExportAbs mWarning namespace name ->
        [ExportAbs Nothing namespace name | Just _ <- [mWarning]]
          <> [ExportAbs mWarning namespace shrunk | shrunk <- shrinkName name]
      ExportAll mWarning namespace name ->
        [ExportAbs mWarning namespace name]
          <> [ExportAll Nothing namespace name | Just _ <- [mWarning]]
          <> [ExportAll mWarning namespace shrunk | shrunk <- shrinkName name]
      ExportWith mWarning _namespace name members ->
        -- Always use Nothing namespace (pattern/type/data not valid in ExportWith)
        [ExportAbs mWarning Nothing name | not (null members)]
          <> [ExportWith Nothing Nothing name members | Just _ <- [mWarning]]
          <> [ExportWith mWarning Nothing shrunk members | shrunk <- shrinkName name]
          <> [ExportWith mWarning Nothing name shrunk | shrunk <- shrinkList shrink members, not (null shrunk)]
      ExportWithAll mWarning _namespace name wildcardIndex members ->
        -- Always use Nothing namespace (pattern/type/data not valid in ExportWithAll)
        [ExportWith mWarning Nothing name members]
          <> [ExportWithAll Nothing Nothing name wildcardIndex members | Just _ <- [mWarning]]
          <> [ExportWithAll mWarning Nothing shrunk wildcardIndex members | shrunk <- shrinkName name]
          <> [ExportWithAll mWarning Nothing name shrunkIndex members | shrunkIndex <- shrinkWildcardIndex wildcardIndex members]
          <> [ExportWithAll mWarning Nothing name (min wildcardIndex (length shrunk)) shrunk | shrunk <- shrinkList shrink members, not (null shrunk)]

instance Arbitrary IEEntityNamespace where
  arbitrary = elements [IEEntityNamespaceType, IEEntityNamespacePattern, IEEntityNamespaceData]

  shrink namespace =
    case namespace of
      IEEntityNamespaceType -> []
      IEEntityNamespacePattern -> [IEEntityNamespaceType]
      IEEntityNamespaceData -> [IEEntityNamespaceType]

instance Arbitrary IEBundledNamespace where
  arbitrary = elements [IEBundledNamespaceType, IEBundledNamespaceData]

  shrink namespace =
    case namespace of
      IEBundledNamespaceType -> []
      IEBundledNamespaceData -> [IEBundledNamespaceType]

instance Arbitrary ImportSpec where
  arbitrary =
    ImportSpec []
      <$> arbitrary
      <*> genImportItems

  shrink spec =
    [spec {importSpecHiding = shrunk} | shrunk <- shrink (importSpecHiding spec)]
      <> [spec {importSpecItems = shrunk} | shrunk <- shrinkList shrink (importSpecItems spec)]

instance Arbitrary ImportItem where
  arbitrary =
    oneof
      [ ImportItemVar Nothing <$> genUnqualifiedVarName,
        ImportItemAbs <$> genTypeNamespace <*> genTypeName,
        ImportItemAll <$> genTypeNamespace <*> genTypeName,
        ImportItemWith <$> genBundledNamespace <*> genTypeName <*> genImportMembers
      ]

  shrink item =
    case item of
      ImportAnn _ sub -> sub : shrink sub
      ImportItemVar namespace name ->
        [ImportItemVar namespace shrunk | shrunk <- shrinkUnqualifiedName name]
      ImportItemAbs namespace name ->
        [ImportItemAbs namespace shrunk | shrunk <- shrinkTypeName name]
      ImportItemAll namespace name ->
        [ImportItemAbs namespace name]
          <> [ImportItemAll namespace shrunk | shrunk <- shrinkTypeName name]
      ImportItemWith namespace name members ->
        [ImportItemAbs namespace name | not (null members)]
          <> [ImportItemWith namespace shrunk members | shrunk <- shrinkTypeName name]
          <> [ImportItemWith namespace name shrunk | shrunk <- shrinkList shrink members, not (null shrunk)]
      ImportItemAllWith namespace name _wildcardIndex members ->
        [ImportItemWith namespace name members]

instance Arbitrary IEBundledMember where
  arbitrary = do
    namespace <- genMemberNamespace
    name <- genMemberNameFor namespace
    pure (IEBundledMember namespace name)

  shrink (IEBundledMember namespace name) =
    [IEBundledMember shrunkNamespace name | shrunkNamespace <- shrink namespace]
      <> [IEBundledMember namespace shrunkName | shrunkName <- shrinkMemberNameFor namespace name]

genMemberName :: Gen Name
genMemberName =
  frequency
    [ (3, qualifyName Nothing <$> genUnqualifiedMemberName),
      (1, genQualifiedMemberName)
    ]

genUnqualifiedMemberName :: Gen UnqualifiedName
genUnqualifiedMemberName =
  oneof [mkUnqualifiedName NameVarId <$> genVarId, genTypeName]

genQualifiedMemberName :: Gen Name
genQualifiedMemberName = do
  modName <- genModuleName
  qualifyName (Just modName) <$> oneof [mkUnqualifiedName NameVarId <$> genVarId, genTypeName]

genMemberNameFor :: Maybe IEBundledNamespace -> Gen Name
genMemberNameFor namespace =
  case namespace of
    Nothing -> genMemberName
    Just _ -> qualifyName Nothing <$> genTypeName

genExportWithAll :: Gen ExportSpec
genExportWithAll = do
  mWarning <- genMaybeWarningPragma
  -- Namespace keywords (pattern/type/data) are not valid before T(..) in exports
  let namespace = Nothing
  name <- genExportTypeName
  members <- listOf1 arbitrary
  wildcardIndex <- chooseInt (0, length members)
  pure (ExportWithAll mWarning namespace name wildcardIndex members)

genImportMembers :: Gen [IEBundledMember]
genImportMembers =
  frequency
    [ (1, pure []),
      (4, genExportMembers)
    ]

shrinkWildcardIndex :: Int -> [a] -> [Int]
shrinkWildcardIndex wildcardIndex members =
  [shrunk | shrunk <- shrink wildcardIndex, shrunk >= 0, shrunk <= length members]

shrinkMemberNameFor :: Maybe IEBundledNamespace -> Name -> [Name]
shrinkMemberNameFor _namespace = shrinkName

instance Arbitrary ImportDecl where
  arbitrary = do
    modName <- genModuleName
    spec <- genMaybeImportSpec
    pure $
      ImportDecl
        { importDeclAnns = [],
          importDeclLevel = Nothing,
          importDeclPackage = Nothing,
          importDeclSourcePragma = Nothing,
          importDeclSafe = False,
          importDeclQualified = False,
          importDeclQualifiedPost = False,
          importDeclModule = modName,
          importDeclAs = Nothing,
          importDeclSpec = spec
        }

  shrink decl =
    [ decl {importDeclModule = shrunk}
    | shrunk <- shrinkModuleName (importDeclModule decl)
    ]
      <> [ decl {importDeclSpec = shrunk}
         | shrunk <- shrinkMaybeImportSpec (importDeclSpec decl)
         ]

genImportDecls :: Gen [ImportDecl]
genImportDecls = do
  n <- chooseInt (0, 3)
  vectorOf n arbitrary

shrinkImportDecl :: ImportDecl -> [ImportDecl]
shrinkImportDecl = shrink

genModuleName :: Gen Text
genModuleName = do
  first <- elements ['A' .. 'Z']
  restLen <- chooseInt (0, 5)
  rest <- vectorOf restLen (elements (['a' .. 'z'] <> ['A' .. 'Z'] <> ['0' .. '9'] <> "_'"))
  pure (T.pack (first : rest))

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

isValidModuleName :: Text -> Bool
isValidModuleName name =
  case T.uncons name of
    Just (first, rest) ->
      (first `elem` ['A' .. 'Z'])
        && T.all (`elem` (['a' .. 'z'] <> ['A' .. 'Z'] <> ['0' .. '9'] <> "_'")) rest
    Nothing -> False

genTypeName :: Gen UnqualifiedName
genTypeName = do
  first <- elements ['A' .. 'Z']
  restLen <- chooseInt (0, 5)
  rest <- vectorOf restLen (elements (['a' .. 'z'] <> ['A' .. 'Z'] <> ['0' .. '9'] <> "_'"))
  pure (mkUnqualifiedName NameConId (T.pack (first : rest)))

shrinkTypeName :: UnqualifiedName -> [UnqualifiedName]
shrinkTypeName name =
  [ mkUnqualifiedName NameConId candidate
  | candidate <- map T.pack (shrink (T.unpack (renderUnqualifiedName name))),
    isValidTypeName candidate
  ]

isValidTypeName :: Text -> Bool
isValidTypeName name =
  case T.uncons name of
    Just (first, rest) ->
      (first `elem` ['A' .. 'Z'])
        && T.all (`elem` (['a' .. 'z'] <> ['A' .. 'Z'] <> ['0' .. '9'] <> "_'")) rest
    Nothing -> False

genExportMembers :: Gen [IEBundledMember]
genExportMembers = do
  n <- chooseInt (0, 3)
  vectorOf n arbitrary

genTypeNamespace :: Gen (Maybe IEEntityNamespace)
genTypeNamespace =
  frequency
    [ (3, pure Nothing),
      (1, pure (Just IEEntityNamespaceType)),
      (1, pure (Just IEEntityNamespaceData))
    ]

genBundledNamespace :: Gen (Maybe IEEntityNamespace)
genBundledNamespace =
  frequency
    [ (5, pure Nothing),
      (1, pure (Just IEEntityNamespaceData))
    ]

genMemberNamespace :: Gen (Maybe IEBundledNamespace)
genMemberNamespace =
  frequency
    [ (4, pure Nothing),
      (1, pure (Just IEBundledNamespaceData)),
      (1, pure (Just IEBundledNamespaceType))
    ]

genUnqualifiedVarName :: Gen UnqualifiedName
genUnqualifiedVarName =
  oneof
    [ mkUnqualifiedName NameVarId <$> genVarId,
      mkUnqualifiedName NameVarSym <$> genVarSym
    ]

genImportItems :: Gen [ImportItem]
genImportItems = do
  n <- chooseInt (0, 3)
  vectorOf n arbitrary

genMaybeImportSpec :: Gen (Maybe ImportSpec)
genMaybeImportSpec =
  frequency
    [ (3, pure Nothing),
      (2, Just <$> arbitrary)
    ]

shrinkMaybeImportSpec :: Maybe ImportSpec -> [Maybe ImportSpec]
shrinkMaybeImportSpec mSpec =
  case mSpec of
    Nothing -> []
    Just spec -> Nothing : [Just shrunk | shrunk <- shrink spec]