elmental-0.1.0.0: src/Elmental/Generate.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeApplications #-}
module Elmental.Generate (
generateTypeDef,
generateEncoder,
generateDecoder,
generateAll,
computeAll,
mkSourceMap,
include,
outputModule,
SomeStructure (..),
ModuleDefinition (..),
) where
import Elmental
import Data.Foldable (toList, traverse_)
import Data.Function ((&))
import Data.List (foldl')
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Data.Maybe (catMaybes, fromJust, fromMaybe)
import Data.Set (Set)
import Data.Set qualified as Set
import Data.Text (Text)
import Data.Text qualified as Text
import Data.Text.IO qualified as Text
import NeatInterpolation (trimming, untrimming)
import System.Directory (createDirectoryIfMissing)
import System.FilePath ((</>))
-- | Generate a type definition
generateTypeDef :: forall {k} (x :: k). (HasElmStructure k x) => Text
generateTypeDef = generateTypeDef' $ getElmStructure @x
generateTypeDef' :: DatatypeStructure x -> Text
generateTypeDef' DatatypeStructure{..}
| (mapping.isTypeAlias && length constructors == 1 && null mapping.args && nParams == 0) =
[trimming|
type alias $tName =
$aliasDef
|]
| (mapping.isTypeAlias && (length constructors /= 1 || not (null mapping.args) || nParams /= 0)) =
error $ "Cannot generate an Elm type alias for: " <> show mapping.typeName
| (not (null constructors) && null mapping.args) =
[trimming|
type $tName $tVars
= $constructorDefs
|]
where
tName = mapping.typeName
currentModule = case mapping.moduleName of
Nothing -> error $ "Cannot generate mapping for: " <> show mapping.typeName <> " (unknown module)"
Just mName -> mName
tVars = Text.intercalate " " (nToVarName <$> take (fromIntegral nParams) [0 ..])
aliasDef = Text.intercalate "\n| " (renderAlias <$> constructors)
constructorDefs = Text.intercalate "\n| " (renderConstructor <$> constructors)
renderAlias Constructor{..} =
case constructorFields of
[] -> [trimming|{}|]
_ ->
let
renderedRecordFields = renderRecordFields constructorFields
in
[trimming|$renderedRecordFields|]
renderConstructor Constructor{..} =
case constructorFields of
[] -> constructorName
((Nothing, _) : _) ->
[trimming|
$constructorName $renderedAnonymousFields
|]
((Just _, _) : _) ->
[trimming|
$constructorName
$renderedRecordFields
|]
where
renderedAnonymousFields = renderAnonymousFields constructorFields
renderedRecordFields = renderRecordFields constructorFields
renderAnonymousFields = Text.intercalate " " . map (renderTyRef currentModule . snd)
renderRecordFields fields =
[trimming|
{ $renderedRecordFields
}
|]
where
renderedRecordFields = Text.intercalate "\n, " (renderField <$> fields)
renderField (Just fieldName, tyRef) = mconcat [fieldName, " : ", renderTyRef currentModule tyRef]
renderField a = error ("renderField - unmatched pattern:" ++ show a)
nToVarName = Text.pack . ('a' :) . show @Integer
generateTypeDef' _ = error "Datatype has no constructor. Impossible to generate Elm definition."
-- | Generate a decoder
generateDecoder :: forall {k} (x :: k). (HasElmStructure k x) => Text
generateDecoder = generateDecoder' $ getElmStructure @x
generateDecoder' :: DatatypeStructure x -> Text
generateDecoder' DatatypeStructure{..} =
[trimming|
$decoderName : $decoderType
$decoderName $decoderArgs =
$decoderBody
|]
where
decoderName = case mapping.decoderLocation of
Just location -> location.symbolName
Nothing -> error $ "No decoder location for: " <> show mapping.typeName
decoderType =
mconcat
[ mconcat $ (\n -> "Decoder a" <> n <> " -> ") <$> params
, "Decoder " <> fullTypeName
]
decoderArgs =
Text.intercalate " " $ ("d" <>) <$> params
decoderBody = case constructors of
[] ->
error $ "Uninhabited datatype: " <> show mapping.typeName
[singleConstructor] ->
if mapping.isTypeAlias
then decodeRecordAlias (mapping.typeName, mapping.moduleName) (singleConstructor.constructorFields)
else decodeConstructor (mapping.typeName, mapping.moduleName) singleConstructor
multiple ->
(if all isNullary constructors then decodeStringTag fullTypeName ((.constructorName) <$> constructors) else decodeTaggedSum (mapping.typeName, mapping.moduleName) fullTypeName multiple)
fullTypeName =
case nParams of
0 -> mapping.typeName
_ ->
let tVars = Text.intercalate " " $ ("a" <>) <$> params
in mconcat ["(", mapping.typeName, " ", tVars, ")"]
params = (Text.pack . show @Integer) <$> take (fromIntegral nParams) [0 ..]
isNullary :: Constructor -> Bool
isNullary Constructor{..} = null constructorFields
decodeStringTag :: Text -> [ConstructorName] -> Text
decodeStringTag fullTypeName cNames =
let tagBranches =
Text.intercalate "\n" $
(\name -> mconcat ["\"", name, "\" -> Json.Decode.succeed ", name])
<$> cNames
in [trimming|
let
decide : String -> Decoder $fullTypeName
decide tag =
case tag of
$tagBranches
other ->
Json.Decode.fail <| "$fullTypeName doesn't have constructor: " ++ other
in
Json.Decode.string
|> Json.Decode.andThen decide
|]
decodeTaggedSum :: (TypeName, Maybe ModuleName) -> Text -> [Constructor] -> Text
decodeTaggedSum recursionStop fullTypeName constructors =
let tagBranches = Text.intercalate "\n" $ formatTagBranch recursionStop <$> constructors
in [trimming|
let
decide : String -> Decoder $fullTypeName
decide tag =
case tag of
$tagBranches
other ->
Json.Decode.fail <| "$fullTypeName doesn't have constructor: " ++ other
in
Json.Decode.field "tag" Json.Decode.string
|> Json.Decode.andThen decide
|]
formatTagBranch :: (TypeName, Maybe ModuleName) -> Constructor -> Text
formatTagBranch recursionStop constructor@Constructor{..} =
let objectDecoder = decodeConstructor recursionStop constructor
in [trimming|
"$constructorName" ->
$objectDecoder
|]
decodeConstructor :: (TypeName, Maybe ModuleName) -> Constructor -> Text
decodeConstructor recursionStop Constructor{..} =
case constructorFields of
[] -> "Json.Decode.succeed " <> constructorName
[(Nothing, tyRef)] ->
mconcat
[ [trimming|Json.Decode.field "contents" <||]
, " Json.Decode.map "
, constructorName
, " "
, mkTypeDecoder recursionStop tyRef
]
fields@((Nothing, _) : _) -> decodeAnonymousConstructor recursionStop constructorName fields
fields@((Just _, _) : _) -> decodeRecordConstructor recursionStop constructorName fields
decodeRecordConstructor :: (TypeName, Maybe ModuleName) -> ConstructorName -> [ElmField] -> Text
decodeRecordConstructor recursionStop cName fields =
case fields of
[] -> error "decodeRecordConstructor called on nullary constructor"
fields' ->
let mkFunction = mkFieldsFunction fields'
fieldDecoders =
Text.intercalate "\n" $
mkFieldDecoder recursionStop <$> fields
in [trimming|
let mkFunction =
$mkFunction
contentDecoder =
Json.Decode.succeed mkFunction
$fieldDecoders
in Json.Decode.field "contents" (Json.Decode.map $cName contentDecoder)
|]
decodeRecordAlias :: (TypeName, Maybe ModuleName) -> [ElmField] -> Text
decodeRecordAlias recursionStop fields =
case fields of
[] -> [trimming| Json.Decode.succeed {} |]
fields' ->
let mkFunction = mkFieldsFunction fields'
fieldDecoders =
Text.intercalate "\n" $
mkFieldDecoder recursionStop <$> fields
in [trimming|
let mkFunction =
$mkFunction
contentDecoder =
Json.Decode.succeed mkFunction
$fieldDecoders
in contentDecoder
|]
mkFieldDecoder :: (TypeName, Maybe ModuleName) -> ElmField -> Text
mkFieldDecoder recursionStop (Just fieldName, tyRef) =
let typDecoder = mkTypeDecoder recursionStop tyRef
in [trimming|
|> andMap (Json.Decode.field "$fieldName" $typDecoder)
|]
mkFieldDecoder _ a =
error $ "mkFieldDecoder - unmatched pattern: " ++ show a
-- TODO: This recursion stop gimmmick only works if the type is directly recursive.
-- NOT if there is a cycle among decoders.
-- We should:
-- - Switch all decoder invocations to lazy as a MVP.
-- - Keep track of all types referend by a type and only insert lazy invocations where needed.
-- The performance hit may not be worth it.
mkTypeDecoder :: (TypeName, Maybe ModuleName) -> TyRef -> Text
mkTypeDecoder recursionStop@(tName, mName) TyRef{..} =
case tyCon of
TyVar varName -> "d" <> Text.tail varName -- TODO: Identify vars as ints, not strings.
TyMapping mapping -> renderMapping tyArgs mapping
where
renderMapping :: [TyRef] -> ElmMapping -> Text
renderMapping args mapping =
let renderedArgs =
Text.intercalate " " $
(renderMapping [] <$> mapping.args)
<> (mkTypeDecoder recursionStop <$> args)
in if mapping.moduleName == mName && mapping.typeName == tName
then -- Same module, same type: lazy decoding without module prefix.
"(Json.Decode.lazy (\\_ -> decode" <> tName <> " " <> renderedArgs <> "))"
else
let decoderLocation = case mapping.decoderLocation of
Nothing -> error "No decoder"
Just location -> location
decodeFunction = (if mName == Just decoderLocation.symbolModuleName then (decoderLocation.symbolName) else decoderLocation.symbolModuleName <> "." <> decoderLocation.symbolName)
in case renderedArgs of
"" -> decodeFunction
_ -> "(" <> decodeFunction <> " " <> renderedArgs <> ")"
mkFieldsFunction :: [ElmField] -> Text
mkFieldsFunction fields =
let fieldNames = (fromJust . fst) <$> fields
args = Text.intercalate " " fieldNames
fieldSetters = Text.intercalate "\n, " $ (\n -> n <> " = " <> n) <$> fieldNames
in [trimming|
\$args ->
{ $fieldSetters
}
|]
decodeAnonymousConstructor :: (TypeName, Maybe ModuleName) -> ConstructorName -> [ElmField] -> Text
decodeAnonymousConstructor recursionStop cName fields =
let contentDecoder = case length fields of
0 -> error "decodeAnonymous constructor should not be used for nullary constructors"
1 -> "Json.Decode.map " <> cName <> mkTypeDecoder recursionStop (snd $ head fields)
_ ->
let numberedFields = zip @Integer [0 ..] (snd <$> fields)
mkFieldDecoder' (n, tyRef) =
"|> andMap (Json.Decode.index "
<> Text.pack (show n)
<> " "
<> mkTypeDecoder recursionStop tyRef
<> ")"
fieldDecoders = Text.intercalate "\n" $ mkFieldDecoder' <$> numberedFields
in [trimming|
Json.Decode.succeed $cName
$fieldDecoders
|]
in [trimming|
let contentDecoder =
$contentDecoder
in
Json.Decode.field "contents" contentDecoder
|]
-- | Generate an encoder
generateEncoder :: forall {k} (x :: k). (HasElmStructure k x) => Text
generateEncoder = generateEncoder' $ getElmStructure @x
getEncoderLocation :: DatatypeStructure x -> SymbolLocation
getEncoderLocation DatatypeStructure{..} =
case mapping.encoderLocation of
Nothing -> error $ "No encoder location for: " <> show mapping.typeName
Just location -> location
getEncoderName :: DatatypeStructure x -> Text
getEncoderName = (.symbolName) . getEncoderLocation
getEncoderModule :: DatatypeStructure x -> Text
getEncoderModule = (.symbolModuleName) . getEncoderLocation
generateEncoder' :: DatatypeStructure x -> Text
generateEncoder' structure@(DatatypeStructure{..})
| mapping.isTypeAlias =
[trimming|
$encoderName : $encoderType
$defLineAlias
$bodyAlias
|]
| otherwise =
[trimming|
$encoderName : $encoderType
$defLine
$body
|]
where
encoderName = structure & getEncoderName
encoderType = mkEncoderType structure
defLine = mkEncoderDefLine structure
defLineAlias = mkEncoderDefLineAlias structure
body = mkEncoderBody structure
bodyAlias = mkEncoderBodyAlias structure
mkEncoderType :: DatatypeStructure x -> Text
mkEncoderType structure =
Text.intercalate " " $
mconcat
[ paramEncoderTypes
, [qualifiedTypeNameAt structure (structure & getEncoderLocation), "->", "Value"]
]
where
paramEncoderTypes =
((\n -> "(e" <> n <> " -> Value) ->") . (Text.pack . show @Integer))
<$> take (fromIntegral structure.nParams) [0 ..]
mkEncoderDefLine :: DatatypeStructure x -> Text
mkEncoderDefLine structure =
Text.intercalate " " $
mconcat
[ [getEncoderName structure]
, (("e" <>) . Text.pack . show @Integer) <$> take (fromIntegral structure.nParams) [0 ..]
, ["v", "="]
]
mkEncoderDefLineAlias :: DatatypeStructure x -> Text
mkEncoderDefLineAlias structure =
Text.intercalate " " $
mconcat
[ [getEncoderName structure]
, (("e" <>) . Text.pack . show @Integer) <$> take (fromIntegral structure.nParams) [0 ..]
, ["r", "="]
]
qualifiedTypeNameAt :: DatatypeStructure x -> SymbolLocation -> Text
qualifiedTypeNameAt s@(DatatypeStructure{..}) loc =
let tName = case mapping.moduleName of
Nothing -> mapping.typeName
Just _ -> prefixFor s loc <> mapping.typeName
tArgs = (("e" <>) . Text.pack . show @Integer) <$> take (fromIntegral s.nParams) [0 ..]
allComponents = tName : tArgs
in case allComponents of
[single] -> single
_multiple -> "(" <> Text.intercalate " " allComponents <> ")"
prefixFor :: DatatypeStructure x -> SymbolLocation -> Text
prefixFor DatatypeStructure{..} loc =
case (mapping.moduleName, loc.symbolModuleName) of
(Nothing, _) -> ""
(Just m1, m2) -> if m1 == m2 then "" else m1 <> "."
mkEncoderBody :: DatatypeStructure x -> Text
mkEncoderBody structure@(DatatypeStructure{..}) =
[trimming|
case v of
$constructorBranches
|]
where
constructorBranches = case constructors of
[] ->
error $
"Cannot generate encoder body (no constructors): "
<> show structure.mapping.typeName
[singleConstructor] ->
unwrapConstructorBranch structure singleConstructor
multiple ->
multipleConstructorBranches structure multiple
mkEncoderBodyAlias :: DatatypeStructure x -> Text
mkEncoderBodyAlias structure@(DatatypeStructure{..}) =
[trimming|
$constructorBranches
|]
where
constructorBranches = case constructors of
[] ->
error $
"Cannot generate encoder body (no constructors): "
<> show structure.mapping.typeName
[singleConstructor] ->
let (_, _, contentEncoder) = constructorBranchHelper structure singleConstructor
in -- Single constructor with no field is encoded as an empty list
fromMaybe "Json.Encode.list identity []" contentEncoder
_ ->
error $
"Cannot generate encoder body as type alias (too many constructors): "
<> show structure.mapping.typeName
unwrapConstructorBranch :: DatatypeStructure x -> Constructor -> Text
unwrapConstructorBranch s@(DatatypeStructure{}) c =
let (cName, cArgs, contentEncoder) = constructorBranchHelper s c
matchLine = Text.intercalate " " (cName : cArgs)
in foldMap
( \e ->
[trimming|
$matchLine ->
$e
|]
)
contentEncoder
constructorBranchHelper :: DatatypeStructure x -> Constructor -> (Text, [Text], Maybe Text)
constructorBranchHelper s@(DatatypeStructure{}) c =
(cName, cArgs, contentEncoder)
where
cName = prefixFor s (s & getEncoderLocation) <> c.constructorName
nFields = length c.constructorFields
contentEncoder = case c.constructorFields of
[] ->
-- Single constructor with no field has no content field
Nothing
[(Nothing, tyRef)] ->
-- Newtype-like: we directly encode the value
Just $ encoderForType s tyRef <> " " <> "p0"
((Nothing, _tyRef) : _) ->
-- Multiple anonymous fields: encode positionally, as a list.
let encodedValues =
Text.intercalate "\n, " $
(\(n, (_, tyRef)) -> encoderForType s tyRef <> " p" <> Text.pack (show n))
<$> (zip @Integer [0 ..] c.constructorFields)
in Just
[trimming|
Json.Encode.list identity
[ $encodedValues
]
|]
recordFields ->
let encodedPairs =
Text.intercalate "\n, " $
( \case
(Just fieldName, tyRef) -> encodePair fieldName tyRef
a -> error $ "unmatched pattern in constructorBranchHelper: " ++ show a
)
<$> recordFields
encodePair fieldName tyRef =
"(\""
<> fieldName
<> "\", "
<> encoderForType s tyRef
<> " r."
<> fieldName
<> ")"
in Just
[trimming|
Json.Encode.object
[ $encodedPairs
]
|]
cArgs = case c.constructorFields of
[] -> []
((Just _, _) : _) -> ["r"]
_ ->
-- Anonymous fields
(("p" <>) . Text.pack . show @Integer) <$> take nFields [0 ..]
multipleConstructorBranches :: DatatypeStructure x -> [Constructor] -> Text
multipleConstructorBranches structure constructors =
let prefix = (prefixFor structure (structure & getEncoderLocation))
in (if all isNullary constructors then encodeStringTags prefix ((.constructorName) <$> constructors) else encodeTaggedBranches structure constructors)
encodeStringTags :: Text -> [ConstructorName] -> Text
encodeStringTags prefix cnames =
Text.intercalate "\n" $
(\cname -> prefix <> cname <> " -> Json.Encode.string \"" <> cname <> "\"") <$> cnames
encodeTaggedBranches :: DatatypeStructure x -> [Constructor] -> Text
encodeTaggedBranches ds cs = Text.intercalate "\n" $ mkTagBranch <$> cs
where
mkTagBranch c =
let (cName, cArgs, encodedContent) =
constructorBranchHelper ds c
match = Text.intercalate " " (cName : cArgs)
tag = c.constructorName
contentsField =
foldMap
( \val ->
[untrimming|
, ( "contents"
, $val
)
|]
)
encodedContent
in [trimming|
$match -> Json.Encode.object
[ ( "tag", Json.Encode.string "$tag" )$contentsField]
|]
encoderForType :: DatatypeStructure x -> TyRef -> Text
encoderForType ds TyRef{..} =
case tyCon of
TyVar varName -> "e" <> Text.tail varName -- Won't have args (No HKTs)
TyMapping mapping -> renderMapping mapping
where
renderMapping mapping =
let encoderName = case mapping.encoderLocation of
Nothing -> error $ "Could not find encoder for type: " <> show mapping.typeName
Just location ->
(if location.symbolModuleName == getEncoderModule ds then (location.symbolName) else location.symbolModuleName <> "." <> location.symbolName)
paramEncoders =
(renderMapping <$> mapping.args)
<> (encoderForType ds <$> tyArgs)
in "(" <> Text.intercalate " " (encoderName : paramEncoders) <> ")"
--
data SomeStructure = forall x. SomeStructure (DatatypeStructure x)
include :: forall {k} x. (HasElmStructure k x) => SomeStructure
include = SomeStructure $ getElmStructure @x
data ModuleDefinition = ModuleDefinition
{ imports :: Set ModuleName
, typeDefs :: [Text]
, encoders :: [Text]
, decoders :: [Text]
}
deriving (Eq, Show)
instance Semigroup ModuleDefinition where
m1 <> m2 =
ModuleDefinition
{ imports = Set.union m1.imports m2.imports
, typeDefs = m1.typeDefs <> m2.typeDefs
, encoders = m1.encoders <> m2.encoders
, decoders = m1.decoders <> m2.decoders
}
instance Monoid ModuleDefinition where
mempty = ModuleDefinition mempty mempty mempty mempty
generateAll :: FilePath -> [SomeStructure] -> IO ()
generateAll baseDir ds = do
let srcMap = mkSourceMap ds
traverse_ (outputModule baseDir) $ Map.toAscList srcMap
mkSourceMap :: [SomeStructure] -> Map ModuleName Text
mkSourceMap ds = Map.mapWithKey renderModule $ computeAll ds
outputModule :: FilePath -> (ModuleName, Text) -> IO ()
outputModule baseDir (mName, source) = do
let fileDir = baseDir </> relDir
filePath = fileDir </> fileName
fileName = fileComponent <> ".elm"
relDir = foldl' (</>) "" dirComponents
fileComponent = head reversed
dirComponents = reverse $ drop 1 reversed
reversed = reverse moduleComponents
moduleComponents = Text.unpack <$> Text.split (== '.') mName
createDirectoryIfMissing True fileDir
Text.writeFile filePath source
renderModule :: ModuleName -> ModuleDefinition -> Text
renderModule mName ModuleDefinition{..} =
[trimming|
module $mName exposing (..)
$importsSrc
import Json.Encode
import Json.Encode exposing (Value)
import Json.Decode
import Json.Decode exposing (Decoder)
import Json.Decode.Extra exposing (andMap)
$typeDefsSrc
$encodersSrc
$decodersSrc
|]
where
typeDefsSrc = Text.intercalate "\n\n" typeDefs
encodersSrc = Text.intercalate "\n\n" encoders
decodersSrc = Text.intercalate "\n\n" decoders
importsSrc = Text.unlines $ ("import " <>) <$> (Set.toAscList imports)
computeAll :: [SomeStructure] -> Map ModuleName ModuleDefinition
computeAll = foldl' addToModules Map.empty . mconcat . fmap mkModuleDefs
where
addToModules modules (mName, def) =
Map.insertWith (<>) mName def modules
mkModuleDefs (SomeStructure ds) =
let constructorImports = Set.fromList $ mconcat $ getImports <$> ds.constructors
typeImport = Set.fromList $ toList ds.mapping.moduleName
in catMaybes
[ case ds.mapping.moduleName of
Nothing -> Nothing
Just mName -> Just (mName, mDef)
where
mDef =
mempty
{ imports = Set.delete mName constructorImports
, typeDefs = pure $ generateTypeDef' ds
}
, case ds.mapping.encoderLocation of
Nothing -> Nothing
Just encoder -> Just (encoder.symbolModuleName, mDef)
where
mDef =
mempty
{ imports =
Set.delete
encoder.symbolModuleName
(constructorImports `Set.union` typeImport)
, encoders = pure $ generateEncoder' ds
}
, case ds.mapping.decoderLocation of
Nothing -> Nothing
Just decoder -> Just (decoder.symbolModuleName, mDef)
where
mDef =
mempty
{ imports =
Set.delete
decoder.symbolModuleName
(constructorImports `Set.union` typeImport)
, decoders = pure $ generateDecoder' ds
}
]
getImports :: Constructor -> [Text]
getImports Constructor{..} = mconcat $ getFieldImports <$> constructorFields
where
getFieldImports (_, tyRef) = getTypeImports tyRef
getTypeImports :: TyRef -> [Text]
getTypeImports tyRef =
mconcat
[ (mconcat $ getTypeImports <$> tyRef.tyArgs)
, mappingArgs tyRef.tyCon
]
mappingArgs tyCon = case tyCon of
TyMapping mapping ->
toList mapping.moduleName
<> toList ((.symbolModuleName) <$> mapping.encoderLocation)
<> toList ((.symbolModuleName) <$> mapping.decoderLocation)
<> mconcat (mappingArgs . TyMapping <$> mapping.args)
_ -> mempty
-- Utility functions
renderTyRef :: ModuleName -> TyRef -> Text
renderTyRef currentModule tyRef =
wrapIfNeeded allRendered
where
wrapIfNeeded :: [Text] -> Text
wrapIfNeeded xs = case xs of
[single] -> single
_multiple -> "(" <> Text.intercalate " " xs <> ")"
allRendered :: [Text]
allRendered =
renderTyCon tyRef.tyCon <> (renderTyRef currentModule <$> tyRef.tyArgs)
renderTyCon :: TyCon -> [Text]
renderTyCon tyCon = case tyCon of
TyVar v -> [v]
TyMapping mapping -> renderMapping mapping
mkTyCon :: ElmMapping -> Text
mkTyCon mapping = case mapping.moduleName of
Nothing -> mapping.typeName
Just mName -> (if mName == currentModule then (mapping.typeName) else mName <> "." <> mapping.typeName)
renderMapping :: ElmMapping -> [Text]
renderMapping mapping =
(mkTyCon mapping)
: ((wrapIfNeeded . renderMapping) <$> mapping.args)