purescript-0.15.15: src/Language/PureScript/Ide/Externs.hs
{-# language PackageImports, BlockArguments #-}
module Language.PureScript.Ide.Externs
( readExternFile
, convertExterns
) where
import Protolude hiding (to, from, (&))
import Codec.CBOR.Term as Term
import Control.Lens (preview, view, (&), (^.))
import "monad-logger" Control.Monad.Logger (MonadLogger, logErrorN)
import Data.Version (showVersion)
import Data.Text qualified as Text
import Language.PureScript qualified as P
import Language.PureScript.Make.Monad qualified as Make
import Language.PureScript.Ide.Error (IdeError (..))
import Language.PureScript.Ide.Types (IdeDataConstructor(..), IdeDeclaration(..), IdeDeclarationAnn(..), IdeType(..), IdeTypeClass(..), IdeTypeOperator(..), IdeTypeSynonym(..), IdeValue(..), IdeValueOperator(..), _IdeDeclType, anyOf, emptyAnn, ideTypeKind, ideTypeName)
import Language.PureScript.Ide.Util (properNameT)
readExternFile
:: (MonadIO m, MonadError IdeError m, MonadLogger m)
=> FilePath
-> m P.ExternsFile
readExternFile fp = do
externsFile <- liftIO (Make.readCborFileIO fp)
case externsFile of
Just externs | version == P.efVersion externs ->
pure externs
_ ->
liftIO (Make.readCborFileIO fp) >>= \case
Just (Term.TList (_tag : Term.TString efVersion : _rest)) -> do
let errMsg =
"Version mismatch for the externs at: "
<> toS fp
<> " Expected: " <> version
<> " Found: " <> efVersion
logErrorN errMsg
throwError (GeneralError errMsg)
_ ->
throwError (GeneralError ("Parsing the extern at: " <> toS fp <> " failed"))
where
version = toS (showVersion P.version)
convertExterns :: P.ExternsFile -> ([IdeDeclarationAnn], [(P.ModuleName, P.DeclarationRef)])
convertExterns ef =
(decls, exportDecls)
where
decls = moduleDecl : map
(IdeDeclarationAnn emptyAnn)
(resolvedDeclarations <> operatorDecls <> tyOperatorDecls)
exportDecls = mapMaybe convertExport (P.efExports ef)
operatorDecls = convertOperator <$> P.efFixities ef
tyOperatorDecls = convertTypeOperator <$> P.efTypeFixities ef
moduleDecl = IdeDeclarationAnn emptyAnn (IdeDeclModule (P.efModuleName ef))
(toResolve, declarations) =
second catMaybes (partitionEithers (map convertDecl (P.efDeclarations ef)))
resolvedDeclarations = resolveSynonymsAndClasses toResolve declarations
resolveSynonymsAndClasses
:: [ToResolve]
-> [IdeDeclaration]
-> [IdeDeclaration]
resolveSynonymsAndClasses trs decls = foldr go decls trs
where
go tr acc = case tr of
TypeClassToResolve tcn ->
case findType (P.coerceProperName tcn) acc of
Nothing ->
acc
Just tyDecl -> IdeDeclTypeClass
(IdeTypeClass tcn (tyDecl ^. ideTypeKind) [])
: filter (not . anyOf (_IdeDeclType . ideTypeName) (== P.coerceProperName tcn)) acc
SynonymToResolve tn ty ->
case findType tn acc of
Nothing ->
acc
Just tyDecl ->
IdeDeclTypeSynonym (IdeTypeSynonym tn ty (tyDecl ^. ideTypeKind))
: filter (not . anyOf (_IdeDeclType . ideTypeName) (== tn)) acc
findType :: P.ProperName 'P.TypeName -> [IdeDeclaration] -> Maybe IdeType
findType tn decls =
decls
& mapMaybe (preview _IdeDeclType)
& find ((==) tn . view ideTypeName)
-- The Externs format splits information about synonyms across EDType
-- and EDTypeSynonym declarations. For type classes it split them
-- across an EDType and an EDClass . We collect these and resolve them
-- at the end of the conversion process.
data ToResolve
= TypeClassToResolve (P.ProperName 'P.ClassName)
| SynonymToResolve (P.ProperName 'P.TypeName) P.SourceType
convertExport :: P.DeclarationRef -> Maybe (P.ModuleName, P.DeclarationRef)
convertExport (P.ReExportRef _ src r) = Just (P.exportSourceDefinedIn src, r)
convertExport _ = Nothing
convertDecl :: P.ExternsDeclaration -> Either ToResolve (Maybe IdeDeclaration)
convertDecl ed = case ed of
-- We need to filter all types and synonyms that contain a '$'
-- because those are typechecker internal definitions that shouldn't
-- be user facing
P.EDType{..} -> Right do
guard (isNothing (Text.find (== '$') (edTypeName ^. properNameT)))
Just (IdeDeclType (IdeType edTypeName edTypeKind []))
P.EDTypeSynonym{..} ->
if isNothing (Text.find (== '$') (edTypeSynonymName ^. properNameT))
then Left (SynonymToResolve edTypeSynonymName edTypeSynonymType)
else Right Nothing
P.EDDataConstructor{..} -> Right do
guard (isNothing (Text.find (== '$') (edDataCtorName ^. properNameT)))
Just
(IdeDeclDataConstructor
(IdeDataConstructor edDataCtorName edDataCtorTypeCtor edDataCtorType))
P.EDValue{..} ->
Right (Just (IdeDeclValue (IdeValue edValueName edValueType)))
P.EDClass{..} ->
Left (TypeClassToResolve edClassName)
P.EDInstance{} ->
Right Nothing
convertOperator :: P.ExternsFixity -> IdeDeclaration
convertOperator P.ExternsFixity{..} =
IdeDeclValueOperator
(IdeValueOperator
efOperator
efAlias
efPrecedence
efAssociativity
Nothing)
convertTypeOperator :: P.ExternsTypeFixity -> IdeDeclaration
convertTypeOperator P.ExternsTypeFixity{..} =
IdeDeclTypeOperator
(IdeTypeOperator
efTypeOperator
efTypeAlias
efTypePrecedence
efTypeAssociativity
Nothing)