packages feed

inferno-lsp-0.1.0: src/Inferno/LSP/ParseInfer.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

module Inferno.LSP.ParseInfer where

import Control.Monad (forM_)
import Control.Monad.Except (MonadError)
import Control.Monad.IO.Class (MonadIO (..))
import Data.Either (isLeft)
import Data.List (find, findIndices, nub, sort)
import qualified Data.List.NonEmpty as NEList
import qualified Data.Map as Map
import Data.Maybe (catMaybes, fromMaybe, listToMaybe, mapMaybe)
import qualified Data.Set as Set
import Data.Text (Text, pack)
import qualified Data.Text as Text
import Inferno.Eval.Error (EvalError)
import Inferno.Infer (TypeError (..), closeOverType, findTypeClassWitnesses, inferExpr, inferPossibleTypes, inferTypeReps)
import Inferno.Infer.Env (Namespace (..))
import Inferno.Infer.Pinned (pinExpr)
import Inferno.Module (Module (..))
import Inferno.Module.Builtin (builtinModule)
import Inferno.Module.Prelude (ModuleMap, baseOpsTable, builtinModulesOpsTable, builtinModulesPinMap)
import Inferno.Parse (InfernoParsingError, parseExpr, parseType)
import Inferno.Parse.Commented (insertCommentsIntoExpr)
import Inferno.Parse.Error (prettyError)
import Inferno.Types.Syntax (Comment, Expr (..), ExtIdent (..), Ident (..), ModuleName (..), Scoped (..), getIdentifierPositions, hideInternalIdents)
import Inferno.Types.Type
  ( BaseType (..),
    ImplType (ImplType),
    InfernoType (..),
    TCScheme (..),
    TypeClass (..),
    TypeClassShape (..),
    TypeMetadata (..),
    apply,
    ftv,
    punctuate',
  )
import Inferno.Types.VersionControl (Pinned (..), VCObjectHash)
import Inferno.Utils.Prettyprinter (renderDoc, renderPretty)
import Language.LSP.Types
  ( Diagnostic (..),
    DiagnosticSeverity (DsError),
    MarkupContent (..),
    MarkupKind (MkMarkdown),
    Range,
    mkRange,
  )
import Prettyprinter
  ( Doc,
    Pretty (pretty),
    align,
    hardline,
    indent,
    sep,
    vsep,
    (<+>),
  )
import qualified Safe
import Text.Megaparsec.Error (ParseError, ShowErrorComponent)
import Text.Megaparsec.Pos (SourcePos (..), unPos)

parseExprInBaseModule ::
  forall m c.
  (MonadError EvalError m, Pretty c, Eq c) =>
  ModuleMap m c ->
  Text ->
  Either
    (NEList.NonEmpty (ParseError Text InfernoParsingError, SourcePos))
    (Expr () SourcePos, [Comment SourcePos])
parseExprInBaseModule prelude =
  parseExpr (baseOpsTable prelude) (builtinModulesOpsTable prelude)

errorDiagnostic :: Int -> Int -> Int -> Int -> Maybe Text -> Text -> Diagnostic
errorDiagnostic s_line s_col e_line e_col source message =
  Diagnostic
    { _range = mkRange (fromIntegral s_line - 2) (fromIntegral s_col - 1) (fromIntegral e_line - 2) (fromIntegral e_col - 1),
      _severity = Just DsError,
      _code = Nothing,
      _source = source,
      _message = message,
      _tags = Nothing,
      _relatedInformation = Nothing
    }

parseErrorDiagnostic :: ShowErrorComponent e => (ParseError Text e, SourcePos) -> Diagnostic
parseErrorDiagnostic (err, SourcePos _ l c) =
  errorDiagnostic
    0
    0
    (unPos l - 1)
    (unPos c)
    (Just "inferno.parser")
    (pack $ prettyError err)

errorDiagnosticInfer :: Int -> Int -> Int -> Int -> Text -> Diagnostic
errorDiagnosticInfer s_line s_col e_line e_col = errorDiagnostic s_line s_col e_line e_col (Just "inferno.infer")

inferErrorDiagnostic :: TypeError SourcePos -> [Diagnostic]
inferErrorDiagnostic = \case
  UnificationFail _ t1 t2 (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ renderDoc
        $ vsep
          [ "Could not match the type",
            indent 2 (pretty $ closeOverType t1),
            "with",
            indent 2 (pretty $ closeOverType t2)
          ]
    ]
  ExpectedFunction _ t1 t2 (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ renderDoc
        $ vsep
          [ "Expected a function here of type",
            indent 2 (pretty $ closeOverType t1),
            "but instead found",
            indent 2 (pretty $ closeOverType t2)
          ]
    ]
  InfiniteType tv t (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ renderDoc
        $ "Could not unify" <+> pretty tv <+> "~" <+> (pretty $ closeOverType t)
    ]
  UnboundExtIdent modNm v (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ renderDoc
        $ "Unbound variable '"
          <> ( case modNm of
                 LocalScope -> ""
                 Scope (ModuleName nm) -> pretty nm <> "."
             )
          <> pretty v
          <> "'"
    ]
  UnboundNameInNamespace modNm (Right n) (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ case n of
          FunNamespace (Ident v) ->
            "Unbound variable '"
              <> ( case modNm of
                     LocalScope -> ""
                     Scope (ModuleName nm) -> nm <> "."
                 )
              <> v
              <> "'"
          OpNamespace (Ident v) ->
            "Unbound operator '"
              <> ( case modNm of
                     LocalScope -> ""
                     Scope (ModuleName nm) -> nm <> "."
                 )
              <> v
              <> "'"
          ModuleNamespace (ModuleName v) ->
            "Module '"
              <> ( case modNm of
                     LocalScope -> ""
                     Scope (ModuleName nm) -> nm <> "."
                 )
              <> v
              <> "' could not be found."
          TypeNamespace (Ident v) ->
            "Type '"
              <> ( case modNm of
                     LocalScope -> ""
                     Scope (ModuleName nm) -> nm <> "."
                 )
              <> v
              <> "' could not be found."
          EnumNamespace (Ident c) ->
            renderDoc $
              vsep
                [ "Could not find the enum constructor '#"
                    <> ( case modNm of
                           LocalScope -> mempty
                           Scope (ModuleName nm) -> pretty nm <> "."
                       )
                    <> pretty c
                    <> "'.",
                  "Make sure the enum you are trying to use has been imported"
                ]
    ]
  UnboundNameInNamespace _modNm (Left h) (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ "Object with has '"
          <> Text.pack (show h)
          <> "' could not be found."
    ]
  ImplicitVarTypeOverlap _ (ExtIdent ident) t1 t2 (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ renderDoc
        $ vsep
          [ "The implicit variable '" <> case ident of { Left i -> "var$" <> pretty i; Right i -> pretty i } <> "' has multiple types:",
            indent 2 (pretty $ closeOverType $ t1),
            "and",
            indent 2 (pretty $ closeOverType $ t2)
          ]
    ]
  VarMultipleOccurrence (Ident x) (s2, e2) (s1, e1) ->
    let message =
          renderDoc $
            vsep
              [ "Duplicate declarations of '" <> pretty x <> "' in the pattern match",
                "at line" <+> pretty (unPos $ sourceLine s1) <> "," <+> "column" <+> pretty (unPos $ sourceColumn s1),
                "and line" <+> pretty (unPos $ sourceLine s2) <> "," <+> "column" <+> pretty (unPos $ sourceColumn s2)
              ]
     in [ errorDiagnosticInfer
            (unPos $ sourceLine s1)
            (unPos $ sourceColumn s1)
            (unPos $ sourceLine e1)
            (unPos $ sourceColumn e1)
            $ message,
          errorDiagnosticInfer
            (unPos $ sourceLine s2)
            (unPos $ sourceColumn s2)
            (unPos $ sourceLine e2)
            (unPos $ sourceColumn e2)
            $ message
        ]
  IfConditionMustBeBool _ t (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ renderDoc
        $ vsep
          [ "The type of the if condition was expected to be a bool, instead I found",
            indent 2 (pretty $ closeOverType t)
          ]
    ]
  AssertConditionMustBeBool _ t (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ renderDoc
        $ vsep
          [ "The type of the assert condition was expected to be a bool, instead I found",
            indent 2 (pretty $ closeOverType t)
          ]
    ]
  IfBranchesMustBeEqType _ t1 t2 (s1, e1) (s2, e2) ->
    let message =
          renderDoc $
            vsep
              [ "The type of both branches of the if statement must be the same, however I found two different types:",
                indent 2 (pretty $ closeOverType t1),
                "and",
                indent 2 (pretty $ closeOverType t2)
              ]
     in [ errorDiagnosticInfer
            (unPos $ sourceLine s1)
            (unPos $ sourceColumn s1)
            (unPos $ sourceLine e1)
            (unPos $ sourceColumn e1)
            $ message,
          errorDiagnosticInfer
            (unPos $ sourceLine s2)
            (unPos $ sourceColumn s2)
            (unPos $ sourceLine e2)
            (unPos $ sourceColumn e2)
            $ message
        ]
  CaseBranchesMustBeEqType _ t1 t2 (s1, e1) (s2, e2) ->
    let message =
          renderDoc $
            vsep
              [ "The type of all case branches must be the same, but I found two different types:",
                indent 2 (pretty $ closeOverType t1),
                "and",
                indent 2 (pretty $ closeOverType t2)
              ]
     in [ errorDiagnosticInfer
            (unPos $ sourceLine s1)
            (unPos $ sourceColumn s1)
            (unPos $ sourceLine e1)
            (unPos $ sourceColumn e1)
            $ message,
          errorDiagnosticInfer
            (unPos $ sourceLine s2)
            (unPos $ sourceColumn s2)
            (unPos $ sourceLine e2)
            (unPos $ sourceColumn e2)
            $ message
        ]
  PatternUnificationFail tPat tE p (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ renderDoc
        $ vsep
          [ "The type of the pattern does not match the case expression",
            "expected",
            indent 2 (pretty $ closeOverType tE),
            "but instead found",
            indent 2 (pretty p <+> ":" <+> align (pretty $ closeOverType tPat))
          ]
    ]
  PatternsMustBeEqType _ t1 t2 p1 p2 (s1, e1) (s2, e2) ->
    let message =
          renderDoc $
            vsep
              [ "The type of all case patterns must be the same, but I found two different types:",
                indent 2 (pretty p1 <+> ":" <+> align (pretty $ closeOverType t1)),
                "and",
                indent 2 (pretty p2 <+> ":" <+> align (pretty $ closeOverType t2))
              ]
     in [ errorDiagnosticInfer
            (unPos $ sourceLine s1)
            (unPos $ sourceColumn s1)
            (unPos $ sourceLine e1)
            (unPos $ sourceColumn e1)
            $ message,
          errorDiagnosticInfer
            (unPos $ sourceLine s2)
            (unPos $ sourceColumn s2)
            (unPos $ sourceLine e2)
            (unPos $ sourceColumn e2)
            $ message
        ]
  NonExhaustivePatternMatch pat (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ renderDoc
        $ vsep
          [ "The patterns in this case expression are non-exhaustive.",
            "For example, the following pattern is missing:",
            indent 2 (pretty $ pat)
          ]
    ]
  UselessPattern (Just pat) (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ renderDoc
        $ vsep
          [ "This case is unreachable, since it is subsumed by the previous pattern",
            indent 2 (pretty $ pat)
          ]
    ]
  UselessPattern Nothing (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ renderDoc
        $ "This case is unreachable, since it is subsumed by the previous patterns"
    ]
  -- TypeClassUnificationError t1 t2 tcs (s, e) ->
  --   [ errorDiagnosticInfer
  --       (unPos $ sourceLine s)
  --       (unPos $ sourceColumn s)
  --       (unPos $ sourceLine e)
  --       (unPos $ sourceColumn e)
  --       $ renderDoc $
  --         vsep
  --           [ "Could not match the type",
  --             indent 2 (pretty $ closeOverType t1),
  --             "with",
  --             indent 2 (pretty $ closeOverType t2),
  --             "arising from this definition constraint:",
  --             indent 2 (pretty $ tcs)
  --           ]
  --   ]
  TypeClassNotFoundError _ tcls (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ renderDoc
        $ vsep
          [ "Could not find any definitions for",
            indent 2 (pretty $ TypeClassShape tcls)
          ]
    ]
  TypeClassNoPartialMatch tcls (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ renderDoc
        $ vsep
          [ "Could not find any matching definitions for",
            indent 2 (pretty $ TypeClassShape tcls)
          ]
    ]
  ModuleNameTaken (ModuleName m) (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ renderDoc
        $ vsep
          [ "The chosen module name already exists:",
            indent 2 (pretty m)
          ]
    ]
  ModuleDoesNotExist (ModuleName m) (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ renderDoc
        $ vsep
          [ "The following module does not exist:",
            indent 2 (pretty m),
            "make sure you have imported the module"
          ]
    ]
  NameInModuleDoesNotExist (ModuleName m) (Ident i) (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ renderDoc
        $ vsep
          [ "The module",
            indent 2 (pretty m),
            "does not contain:",
            indent 2 (pretty i)
          ]
    ]
  AmbiguousName (ModuleName m) i (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ renderDoc
        $ vsep
          [ "The name",
            indent 2 (pretty i),
            "you are trying to import from",
            indent 2 (pretty m),
            "already exists in local scope and would be overshadowed. Consider using:",
            indent 2 $ (pretty m) <> "." <> (pretty i)
          ]
    ]
  CouldNotFindTypeclassWitness tyCls (s, e) ->
    [ errorDiagnosticInfer
        (unPos $ sourceLine s)
        (unPos $ sourceColumn s)
        (unPos $ sourceLine e)
        (unPos $ sourceColumn e)
        $ renderDoc
        $ vsep
        $ "Could not find any matching assignment of types which satisfies the type class constraints:"
          : [ indent 2 (pretty c) | c <- Set.toList tyCls
            ]
    ]

allClasses :: forall m c. (MonadError EvalError m, Pretty c, Eq c) => ModuleMap m c -> Set.Set TypeClass
allClasses prelude = Set.unions $ moduleTypeClasses builtinModule : [cls | Module {moduleTypeClasses = cls} <- Map.elems prelude]

parseAndInfer :: forall m1 m2 c. (MonadError EvalError m1, Pretty c, Eq c, MonadIO m2) => ModuleMap m1 c -> [Maybe Ident] -> Text -> (InfernoType -> Either Text ()) -> m2 (Either [Diagnostic] (Expr (Pinned VCObjectHash) (), TCScheme, [(Range, MarkupContent)]))
parseAndInfer prelude idents txt validateInput = do
  let input = case idents of
        [] -> "\n" <> txt
        ids -> "fun " <> (Text.intercalate " " $ map (maybe "_" (\(Ident i) -> i)) ids) <> " -> \n" <> txt
  -- AppConfig _ _ tracer <- ask
  -- let trace = const $ pure () --traceWith tracer
  case (parseExprInBaseModule prelude) input of
    Left err -> do
      -- liftIO $ debugM "reactor.handle" $ "Parsing error: " ++ show err
      return $ Left $ fmap parseErrorDiagnostic $ NEList.toList err
    Right (ast, comments) -> do
      -- liftIO $ debugM "reactor.handle" $ "Finished parsing"
      case pinExpr (builtinModulesPinMap prelude) ast of
        Left err -> do
          -- liftIO $ debugM "reactor.handle" $ "Pinning error: " ++ show err
          return $ Left $ concatMap inferErrorDiagnostic $ Set.toList $ Set.fromList err
        Right pinnedAST -> do
          -- liftIO $ debugM "reactor.handle" $ "Finished pinning"
          case inferExpr prelude pinnedAST of
            Left err -> do
              -- liftIO $ debugM "reactor.handle" $ "Infer error: " ++ show err
              return $ Left $ concatMap inferErrorDiagnostic $ Set.toList $ Set.fromList err
            Right (pinnedAST', tcSch@(ForallTC _ currentClasses (ImplType _ typSig)), tyMap) -> do
              liftIO $ print typSig
              let possibleInputErrors = validateInputs typSig
              case find isLeft possibleInputErrors of
                Just (Left err) -> do
                  -- Get the idents that correspond to the indices
                  let indices = findIndices isLeft possibleInputErrors
                  let badIdent = listToMaybe $ catMaybes (mapMaybe (Safe.atMay idents) indices)
                  case badIdent of
                    Nothing -> return $ Left [errorDiagnosticInfer 0 0 0 0 $ renderPretty ["Could not find the input that caused the error. " `Text.append` err]]
                    Just ident@(Ident _i) -> do
                      let mIdentPos = listToMaybe $ getIdentifierPositions ident pinnedAST'
                      case mIdentPos of
                        Nothing -> return $ Left [errorDiagnosticInfer 0 0 0 2 $ renderDoc $ vsep ["Could not find the input that caused the error"]]
                        Just (s, e) -> return $ Left [errorDiagnosticInfer (unPos $ sourceLine s) (unPos $ sourceColumn s) (unPos $ sourceLine e) (unPos $ sourceColumn e) err]
                _ -> do
                  -- liftIO $ debugM "reactor.handle" $ "Finished inferring"
                  let final = insertCommentsIntoExpr comments pinnedAST'
                  return $
                    Right
                      ( fmap (const ()) final,
                        tcSch,
                        Map.foldrWithKey (\k v xs -> (mkHover prelude currentClasses k v) : xs) mempty tyMap
                      )
  where
    -- Example: The type signature has the format for 3 inputs:
    -- TArr
    --   Input1
    --   TArr
    --     Input2
    --     TArr
    --       Input3
    --       Output
    --
    -- Therefore we need to check the type of the first value in each TArr to see if it is a valid input type
    -- The final output value will be ignored
    validateInputs :: InfernoType -> [Either Text ()]
    validateInputs = \case
      -- Ignore validation if the final output is an array
      typSig | isOutputAnArray typSig -> [Right ()]
      -- Head is input, tail *could* be other inputs
      TArr h t | isTArr t -> validateInput h : validateInputs t
      -- Tail of this would be the output
      TArr h _t | not (isTArr h) -> [validateInput h]
      -- Top level is not an arr, assume it is correct to prevent errors
      _ -> [Right ()]

    isTArr :: InfernoType -> Bool
    isTArr = \case
      TArr _ _ -> True
      _ -> False

    isOutputAnArray :: InfernoType -> Bool
    isOutputAnArray = \case
      TArr _h t -> isOutputAnArray t
      TArray _ -> True
      _ -> False

parseAndInferPretty :: forall m c. (MonadError EvalError m, Pretty c, Eq c) => ModuleMap m c -> Text -> IO ()
parseAndInferPretty prelude txt =
  (parseAndInfer @m @_ @c prelude) [] txt (const $ Right ()) >>= \case
    Left err -> print err
    Right (expr, typ, _hovers) -> do
      putStrLn $ Text.unpack $ "internal: " <> renderPretty expr

      putStrLn $ Text.unpack $ "\nhidden: " <> renderPretty (hideInternalIdents expr)

      putStrLn $ Text.unpack $ "\ntype: " <> renderPretty typ

      putStrLn $ "\ntype (pretty)" <> (Text.unpack $ renderDoc $ mkPrettyTy prelude mempty typ)

parseAndInferTypeReps :: forall m c. (MonadError EvalError m, Pretty c, Eq c) => ModuleMap m c -> Text -> [Text] -> Text -> IO ()
parseAndInferTypeReps prelude expr inTys outTy =
  (parseAndInfer @m @_ @c prelude) [] expr (const $ Right ()) >>= \case
    Left err -> print err
    Right (_expr, typ, _hovers) -> do
      putStrLn $ Text.unpack $ "\ntype: " <> renderPretty typ
      putStrLn $ "\ntype (pretty)" <> (Text.unpack $ renderDoc $ mkPrettyTy prelude mempty typ)

      case traverse parseType inTys of
        Left errs -> print errs
        Right inTysParsed -> case parseType outTy of
          Left errTy -> print errTy
          Right outTyParsed ->
            case inferTypeReps (allClasses prelude) typ inTysParsed outTyParsed of
              Left errTy' -> do
                print errTy'
                forM_ errTy' $ \e -> print $ inferErrorDiagnostic e
              Right res -> do
                putStrLn ("type reps:" :: String)
                print res

parseAndInferPossibleTypes :: forall m c. (MonadError EvalError m, Pretty c, Eq c) => ModuleMap m c -> Text -> [Maybe Text] -> Maybe Text -> IO ()
parseAndInferPossibleTypes prelude expr inTys outTy =
  (parseAndInfer @m @_ @c prelude) [] expr (const $ Right ()) >>= \case
    Left err -> print err
    Right (_expr, typ, _hovers) -> do
      putStrLn $ Text.unpack $ "\ntype: " <> renderPretty typ
      putStrLn $ "\ntype (pretty)" <> (Text.unpack $ renderDoc $ mkPrettyTy prelude mempty typ)

      case traverse (maybe (pure Nothing) ((Just <$>) . parseType)) inTys of
        Left errs -> print errs
        Right inTysParsed -> case (maybe (pure Nothing) ((Just <$>) . parseType)) outTy of
          Left errTy -> print errTy
          Right outTyParsed ->
            case inferPossibleTypes (allClasses prelude) typ inTysParsed outTyParsed of
              Left errTy' -> do
                print errTy'
                forM_ errTy' $ \e -> print $ inferErrorDiagnostic e
              Right res -> do
                putStrLn ("possible types:" :: String)
                print res

-- putStrLn $ show hovers

mkHover :: forall m c. (MonadError EvalError m, Pretty c, Eq c) => ModuleMap m c -> Set.Set TypeClass -> (SourcePos, SourcePos) -> TypeMetadata TCScheme -> (Range, MarkupContent)
mkHover prelude currentClasses (s, e) meta@TypeMetadata {identExpr = expr, ty = tcSchTy} =
  let prettyTy = mkPrettyTy prelude currentClasses tcSchTy
   in ( mkRange ((fromIntegral $ unPos $ sourceLine s) - 2) ((fromIntegral $ unPos $ sourceColumn s) - 1) ((fromIntegral $ unPos $ sourceLine e) - 2) ((fromIntegral $ unPos $ sourceColumn e) - 1),
        MarkupContent MkMarkdown $
          "**Type**\n"
            <> "~~~inferno\n"
            <> (renderDoc $ pretty expr <+> align prettyTy)
            <> "\n~~~"
            <> (maybe "" ("\n" <>) (getTypeMetadataText meta))
      )

mkPrettyTy :: forall m c ann. (MonadError EvalError m, Pretty c, Eq c) => ModuleMap m c -> Set.Set TypeClass -> TCScheme -> Doc ann
mkPrettyTy prelude currentClasses (ForallTC _tvs cls typ) =
  let ftvTy = ftv typ
   in if Set.null ftvTy
        then -- if the body of the type contains no type variables, simply pretty print it
          ":" <+> align (pretty typ)
        else -- otherwise get a union type by running findTypeClassWitnesses.
        -- things to note:
        --   * we need to filter out the "rep" typeclass, since it is always defined and thererfore pointless to pass to the solver
        --   * we only want to pass in the fvs of typ to the solver, as these are the only type variables we care about displaying
        case findTypeClassWitnesses (allClasses prelude) (Just 11) (Set.filter (\case TypeClass "rep" _ -> False; _ -> True) $ Set.union cls currentClasses) ftvTy of
          [] -> error "we must always have at least one witness!"
          subs ->
            let prettyList = map pretty $ nub $ sort $ map (flip apply $ filterOutImplicitTypeReps typ) subs
                prettyListMax10 = take 10 prettyList
             in if length prettyListMax10 == length prettyList
                  then (sep $ unionTySig prettyList)
                  else (sep $ unionTySig $ prettyList <> ["..."])
  where
    unionTySig [] = []
    unionTySig (t : ts) = (":" <+> t) : go ts
    go [] = []
    go (t : ts) = ("|" <+> t) : go ts

    filterOutImplicitTypeReps (ImplType impls ty) =
      ImplType (Map.filter (\case TRep _ -> False; _ -> True) impls) ty

getTypeMetadataText :: TypeMetadata TCScheme -> Maybe Text
getTypeMetadataText TypeMetadata {docs = tcsDocs, ty = ForallTC _ _ (ImplType _ tcsTy)} =
  renderDoc
    <$> case (tcsDocs, tcsTy) of
      (Nothing, _) -> Nothing
      (_, TBase (TEnum nm cs)) ->
        Just $
          pretty (fromMaybe "" tcsDocs)
            <> hardline
            <> "~~~inferno"
            <> hardline
            <> "enum"
            <+> pretty nm
            <+> align (sep $ "=" : (punctuate' "|" $ map (("#" <>) . pretty . unIdent) $ Set.toList cs))
              <> hardline
              <> "~~~"
      _ -> Just $ pretty (fromMaybe "" tcsDocs)