shwifty 0.0.1.0 → 0.0.2.0
raw patch · 7 files changed
+2575/−2180 lines, 7 filesdep ~primitivePVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: primitive
API changes (from Hackage documentation)
- Shwifty: ($sel:constructorModifier:Options) :: Options -> String -> String
- Shwifty: ($sel:dataProtocols:Options) :: Options -> [Protocol]
- Shwifty: ($sel:dataRawValue:Options) :: Options -> Maybe Ty
- Shwifty: ($sel:fieldLabelModifier:Options) :: Options -> String -> String
- Shwifty: ($sel:generateToSwift:Options) :: Options -> Bool
- Shwifty: ($sel:generateToSwiftData:Options) :: Options -> Bool
- Shwifty: ($sel:indent:Options) :: Options -> Int
- Shwifty: ($sel:lowerFirstCase:Options) :: Options -> Bool
- Shwifty: ($sel:lowerFirstField:Options) :: Options -> Bool
- Shwifty: ($sel:newtypeTag:Options) :: Options -> Bool
- Shwifty: ($sel:optionalExpand:Options) :: Options -> Bool
- Shwifty: ($sel:typeAlias:Options) :: Options -> Bool
- Shwifty: TypeAlias :: String -> [String] -> Ty -> SwiftData
- Shwifty: [$sel:cases:SwiftStruct] :: SwiftData -> [(String, [(Maybe String, Ty)])]
- Shwifty: [$sel:disambiguate:Unit] :: Ty -> Bool
- Shwifty: [$sel:fields:SwiftStruct] :: SwiftData -> [(String, Ty)]
- Shwifty: [$sel:name:SwiftStruct] :: SwiftData -> String
- Shwifty: [$sel:name:Unit] :: Ty -> String
- Shwifty: [$sel:parent:Unit] :: Ty -> String
- Shwifty: [$sel:protocols:SwiftStruct] :: SwiftData -> [Protocol]
- Shwifty: [$sel:rawValue:SwiftStruct] :: SwiftData -> Maybe Ty
- Shwifty: [$sel:tags:SwiftStruct] :: SwiftData -> [Ty]
- Shwifty: [$sel:tyVars:SwiftStruct] :: SwiftData -> [String]
- Shwifty: [$sel:tyVars:Unit] :: Ty -> [Ty]
- Shwifty: [$sel:typ:SwiftStruct] :: SwiftData -> Ty
- Shwifty: [$sel:typ:Unit] :: Ty -> Ty
- Shwifty: instance (Shwifty.ToSwift a, Shwifty.ToSwift b) => Shwifty.ToSwift (Data.Either.Either a b)
- Shwifty: instance (Shwifty.ToSwift a, Shwifty.ToSwift b) => Shwifty.ToSwift (a -> b)
- Shwifty: instance (Shwifty.ToSwift a, Shwifty.ToSwift b) => Shwifty.ToSwift (a, b)
- Shwifty: instance (Shwifty.ToSwift a, Shwifty.ToSwift b, Shwifty.ToSwift c) => Shwifty.ToSwift (a, b, c)
- Shwifty: instance (Shwifty.ToSwift k, Shwifty.ToSwift v) => Shwifty.ToSwift (Data.HashMap.Base.HashMap k v)
- Shwifty: instance (Shwifty.ToSwift k, Shwifty.ToSwift v) => Shwifty.ToSwift (Data.Map.Internal.Map k v)
- Shwifty: instance GHC.Classes.Eq Shwifty.Protocol
- Shwifty: instance GHC.Classes.Eq Shwifty.SwiftData
- Shwifty: instance GHC.Classes.Eq Shwifty.Ty
- Shwifty: instance GHC.Generics.Generic Shwifty.Protocol
- Shwifty: instance GHC.Generics.Generic Shwifty.SwiftData
- Shwifty: instance GHC.Generics.Generic Shwifty.Ty
- Shwifty: instance GHC.Read.Read Shwifty.Protocol
- Shwifty: instance GHC.Read.Read Shwifty.SwiftData
- Shwifty: instance GHC.Read.Read Shwifty.Ty
- Shwifty: instance GHC.Show.Show Shwifty.Protocol
- Shwifty: instance GHC.Show.Show Shwifty.SwiftData
- Shwifty: instance GHC.Show.Show Shwifty.Ty
- Shwifty: instance GHC.TypeLits.KnownSymbol x => Shwifty.ToSwift (Shwifty.SingSymbol x)
- Shwifty: instance Language.Haskell.TH.Syntax.Lift Shwifty.Protocol
- Shwifty: instance Language.Haskell.TH.Syntax.Lift Shwifty.Ty
- Shwifty: instance Shwifty.ToSwift ()
- Shwifty: instance Shwifty.ToSwift (Data.CaseInsensitive.Internal.CI s)
- Shwifty: instance Shwifty.ToSwift Data.ByteString.Internal.ByteString
- Shwifty: instance Shwifty.ToSwift Data.ByteString.Lazy.Internal.ByteString
- Shwifty: instance Shwifty.ToSwift Data.Primitive.ByteArray.ByteArray
- Shwifty: instance Shwifty.ToSwift Data.Text.Internal.Lazy.Text
- Shwifty: instance Shwifty.ToSwift Data.Text.Internal.Text
- Shwifty: instance Shwifty.ToSwift Data.Time.Clock.Internal.UTCTime.UTCTime
- Shwifty: instance Shwifty.ToSwift Data.UUID.Types.Internal.UUID
- Shwifty: instance Shwifty.ToSwift Data.Void.Void
- Shwifty: instance Shwifty.ToSwift GHC.Int.Int16
- Shwifty: instance Shwifty.ToSwift GHC.Int.Int32
- Shwifty: instance Shwifty.ToSwift GHC.Int.Int64
- Shwifty: instance Shwifty.ToSwift GHC.Int.Int8
- Shwifty: instance Shwifty.ToSwift GHC.Integer.Type.Integer
- Shwifty: instance Shwifty.ToSwift GHC.Types.Bool
- Shwifty: instance Shwifty.ToSwift GHC.Types.Char
- Shwifty: instance Shwifty.ToSwift GHC.Types.Double
- Shwifty: instance Shwifty.ToSwift GHC.Types.Float
- Shwifty: instance Shwifty.ToSwift GHC.Types.Int
- Shwifty: instance Shwifty.ToSwift GHC.Types.Word
- Shwifty: instance Shwifty.ToSwift GHC.Word.Word16
- Shwifty: instance Shwifty.ToSwift GHC.Word.Word32
- Shwifty: instance Shwifty.ToSwift GHC.Word.Word64
- Shwifty: instance Shwifty.ToSwift GHC.Word.Word8
- Shwifty: instance Shwifty.ToSwift [GHC.Types.Char]
- Shwifty: instance Shwifty.ToSwift a => Shwifty.ToSwift (Data.Primitive.Array.Array a)
- Shwifty: instance Shwifty.ToSwift a => Shwifty.ToSwift (Data.Primitive.PrimArray.PrimArray a)
- Shwifty: instance Shwifty.ToSwift a => Shwifty.ToSwift (Data.Primitive.SmallArray.SmallArray a)
- Shwifty: instance Shwifty.ToSwift a => Shwifty.ToSwift (Data.Vector.Vector a)
- Shwifty: instance Shwifty.ToSwift a => Shwifty.ToSwift (GHC.Maybe.Maybe a)
- Shwifty: instance Shwifty.ToSwift a => Shwifty.ToSwift [a]
+ Shwifty: Codec :: Codec tag
+ Shwifty: DataCon :: Label
+ Shwifty: Field :: Label
+ Shwifty: SwiftAlias :: String -> [String] -> Ty -> SwiftData
+ Shwifty: TyCon :: Label
+ Shwifty: [aliasName] :: SwiftData -> String
+ Shwifty: [aliasTyVars] :: SwiftData -> [String]
+ Shwifty: [aliasTyp] :: SwiftData -> Ty
+ Shwifty: [concreteName] :: Ty -> String
+ Shwifty: [concreteTyVars] :: Ty -> [Ty]
+ Shwifty: [enumCases] :: SwiftData -> [(String, [(Maybe String, Ty)])]
+ Shwifty: [enumName] :: SwiftData -> String
+ Shwifty: [enumPrivateTypes] :: SwiftData -> [SwiftData]
+ Shwifty: [enumProtocols] :: SwiftData -> [Protocol]
+ Shwifty: [enumRawValue] :: SwiftData -> Maybe Ty
+ Shwifty: [enumTags] :: SwiftData -> [Ty]
+ Shwifty: [enumTyVars] :: SwiftData -> [String]
+ Shwifty: [structFields] :: SwiftData -> [(String, Ty)]
+ Shwifty: [structName] :: SwiftData -> String
+ Shwifty: [structPrivateTypes] :: SwiftData -> [SwiftData]
+ Shwifty: [structProtocols] :: SwiftData -> [Protocol]
+ Shwifty: [structTags] :: SwiftData -> [Ty]
+ Shwifty: [structTyVars] :: SwiftData -> [String]
+ Shwifty: [tagDisambiguate] :: Ty -> Bool
+ Shwifty: [tagName] :: Ty -> String
+ Shwifty: [tagParent] :: Ty -> String
+ Shwifty: [tagTyp] :: Ty -> Ty
+ Shwifty: class CanBeRawValue (ty :: Ty)
+ Shwifty: class ModifyOptions tag
+ Shwifty: constructorModifier :: Options -> String -> String
+ Shwifty: data Codec tag
+ Shwifty: data DontGenerate (cls :: * -> Constraint)
+ Shwifty: data DontLowercase (someKind :: Label)
+ Shwifty: data Drop (label :: Label) (string :: Symbol)
+ Shwifty: data Implement (protocol :: Protocol)
+ Shwifty: data Label
+ Shwifty: data MakeBase (rawValue :: Maybe Ty) (protocols :: [Protocol])
+ Shwifty: data NewtypeTag
+ Shwifty: data OmitCase (cas :: Symbol)
+ Shwifty: data OmitField (field :: Symbol)
+ Shwifty: data RawValue (ty :: Ty)
+ Shwifty: data TypeAlias
+ Shwifty: data a & b
+ Shwifty: dataProtocols :: Options -> [Protocol]
+ Shwifty: dataRawValue :: Options -> Maybe Ty
+ Shwifty: fieldLabelModifier :: Options -> String -> String
+ Shwifty: generateToSwift :: Options -> Bool
+ Shwifty: generateToSwiftData :: Options -> Bool
+ Shwifty: getShwiftyCodec :: forall tag. ModifyOptions tag => Codec tag -> Name -> Q [Dec]
+ Shwifty: getShwiftyCodecTags :: forall tag. ModifyOptions tag => Codec tag -> [Name] -> Name -> Q [Dec]
+ Shwifty: infixr 6 &
+ Shwifty: instance GHC.TypeLits.KnownSymbol x => Shwifty.Class.ToSwift (Shwifty.SingSymbol x)
+ Shwifty: lowerFirstCase :: Options -> Bool
+ Shwifty: lowerFirstField :: Options -> Bool
+ Shwifty: makeBase :: Options -> (Bool, Maybe Ty, [Protocol])
+ Shwifty: modifyOptions :: ModifyOptions tag => Options -> Options
+ Shwifty: newtypeTag :: Options -> Bool
+ Shwifty: omitCases :: Options -> [String]
+ Shwifty: omitFields :: Options -> [String]
+ Shwifty: optionalExpand :: Options -> Bool
+ Shwifty: type AsIs = ()
+ Shwifty: typeAlias :: Options -> Bool
- Shwifty: SwiftEnum :: String -> [String] -> [Protocol] -> [(String, [(Maybe String, Ty)])] -> Maybe Ty -> [Ty] -> SwiftData
+ Shwifty: SwiftEnum :: String -> [String] -> [Protocol] -> [(String, [(Maybe String, Ty)])] -> Maybe Ty -> [SwiftData] -> [Ty] -> SwiftData
- Shwifty: SwiftStruct :: String -> [String] -> [Protocol] -> [(String, Ty)] -> [Ty] -> SwiftData
+ Shwifty: SwiftStruct :: String -> [String] -> [Protocol] -> [(String, Ty)] -> [SwiftData] -> [Ty] -> SwiftData
- Shwifty: prettySwiftData :: ToSwiftData a => Proxy a -> String
+ Shwifty: prettySwiftData :: SwiftData -> String
Files
- CHANGELOG.md +18/−0
- shwifty.cabal +7/−2
- src/Shwifty.hs +1668/−2178
- src/Shwifty/Class.hs +154/−0
- src/Shwifty/Codec.hs +210/−0
- src/Shwifty/Pretty.hs +204/−0
- src/Shwifty/Types.hs +314/−0
CHANGELOG.md view
@@ -1,5 +1,23 @@ # Revision history for shwifty +## 0.0.2.0 - 2020-02-26++* Primitive lower bound: 0.7 -> 0.6.4+* Fix issue with type defaulting in disambiguate+* Add docs to 'toSwift'+* Add 'Codec' API for cleaner deriving+* Remove usage of -XDuplicateRecordFields+* Add getShwiftyCodecTags +* Cleanup internal code (organise but maintain external API)+* Add support for private enums/structs +* Add support for 'base' types+* Add support for omitting fields and cases +* Remove indent option +* Fix pretty-printing for functions in swift+* Remove instance for Void+* Make 'prettySwiftData' a top-level function+* Remove 'Comparable' protocol+ ## 0.0.1.0 -- 2020-02-03 * First version. Released on an unsuspecting world.
shwifty.cabal view
@@ -2,7 +2,7 @@ name: shwifty version:- 0.0.1.0+ 0.0.2.0 synopsis: Generate swift types from haskell types. description:@@ -35,13 +35,18 @@ exposed-modules: Shwifty --Test+ other-modules:+ Shwifty.Class+ Shwifty.Codec+ Shwifty.Pretty+ Shwifty.Types build-depends: , base >= 4.11 && < 4.14 , bytestring >= 0.10 && < 0.11 , case-insensitive >= 1.2 && < 1.3 , containers >= 0.5.9 && < 0.7 , mtl >= 2.2 && < 2.3- , primitive >= 0.7 && < 0.8+ , primitive >= 0.6.4 && < 0.8 , template-haskell >= 2.11 && < 2.17 , text >= 1.2 && < 1.3 , th-abstraction >= 0.3 && < 0.4
src/Shwifty.hs view
@@ -1,2181 +1,1671 @@ {-# language AllowAmbiguousTypes , BangPatterns- , CPP- , DataKinds- , DefaultSignatures- , DeriveFoldable- , DeriveFunctor- , DeriveGeneric- , DeriveLift- , DeriveTraversable- , DerivingStrategies- , DuplicateRecordFields- , FlexibleInstances- , GADTs- , KindSignatures- , LambdaCase- , MultiWayIf- , NamedFieldPuns- , OverloadedStrings- , PolyKinds- , RecordWildCards- , ScopedTypeVariables- , TemplateHaskell- , TypeApplications- , ViewPatterns-#-}--{-# options_ghc- -Wall- -fno-warn-duplicate-exports-#-}---- | The Shwifty library allows generation of--- Swift types (structs and enums) from Haskell--- ADTs, using Template Haskell. The main--- entry point to the library should be the--- documentation and examples of 'getShwifty'.--- See also 'getShwiftyWith'--- and 'getShwiftyWithTags'.------ This library is in alpha and there are a number--- of known bugs which shouldn't affect most users.--- See the issue tracker to see what those are.------ There are probably many bugs/some weird behaviour--- when it comes to data families. Please report--- any issues on the issue tracker.-module Shwifty- ( -- * Classes for conversion- ToSwift(..)- , ToSwiftData(..)-- -- * Generating instances- , getShwifty- , getShwiftyWith- , getShwiftyWithTags-- -- * Types- , Ty(..)- , SwiftData(..)- , Protocol(..)-- -- * Options for encoding types- -- ** Option type- , Options- -- ** Actual Options- , fieldLabelModifier- , constructorModifier- , optionalExpand- , indent- , generateToSwift- , generateToSwiftData- , dataProtocols- , dataRawValue- , typeAlias- , newtypeTag- , lowerFirstCase- , lowerFirstField- -- ** Default 'Options'- , defaultOptions-- -- * Pretty-printing- -- ** Functions- , prettyTy- , prettySwiftData- -- ** Re-exports- , X- ) where--#include "MachDeps.h"--import Control.Monad.Except-import Data.CaseInsensitive (CI)-import Data.Foldable (foldlM,foldr',foldl')-import Data.Functor ((<&>))-import Data.Int (Int8,Int16,Int32,Int64)-import Data.List (intercalate)-import Data.List.NonEmpty ((<|), NonEmpty(..))-import Data.Maybe (mapMaybe, catMaybes)-import Data.Proxy (Proxy(..))-import Data.Time (UTCTime)-import Data.UUID.Types (UUID)-import Data.Vector (Vector)-import Data.Void (Void)-import Data.Word (Word8,Word16,Word32,Word64)-import GHC.Generics (Generic)-import GHC.TypeLits (Symbol, KnownSymbol, symbolVal)-import Language.Haskell.TH hiding (stringE)-import Language.Haskell.TH.Datatype-import Language.Haskell.TH.Syntax (Lift)-import Prelude hiding (Enum(..))-import qualified Data.ByteString as BS-import qualified Data.ByteString.Lazy as BL-import qualified Data.Char as Char-import qualified Data.HashMap.Strict as HM-import qualified Data.List as L-import qualified Data.List.NonEmpty as NE-import qualified Data.Map as M-import qualified Data.Text as TS-import qualified Data.Text.Lazy as TL-import qualified Data.Primitive as Prim---- | An AST representing a Swift type.-data Ty- = Unit- -- ^ Unit (called "Unit/Void" in swift). Empty struct type.- | Bool- -- ^ Bool- | Character- -- ^ Character- | Str- -- ^ String. Named 'Str' to avoid conflicts with- -- 'Data.Aeson.String'.- | I- -- ^ signed machine integer- | I8- -- ^ signed 8-bit integer- | I16- -- ^ signed 16-bit integer- | I32- -- ^ signed 32-bit integer- | I64- -- ^ signed 64-bit integer- | U- -- ^ unsigned machine integer- | U8- -- ^ unsigned 8-bit integer- | U16- -- ^ unsigned 16-bit integer- | U32- -- ^ unsigned 32-bit integer- | U64- -- ^ unsigned 64-bit integer- | F32- -- ^ 32-bit floating point- | F64- -- ^ 64-bit floating point- | Decimal- -- ^ Increased-precision floating point- | BigSInt32- -- ^ 32-bit big integer- | BigSInt64- -- ^ 64-bit big integer- | Tuple2 Ty Ty- -- ^ 2-tuple- | Tuple3 Ty Ty Ty- -- ^ 3-tuple- | Optional Ty- -- ^ Maybe type- | Result Ty Ty- -- ^ Either type- --- -- /Note/: The error type in Swift must- -- implement the @Error@ protocol. This library- -- currently does not enforce this.- | Set Ty- -- ^ Set type- | Dictionary Ty Ty- -- ^ Dictionary type- | Array Ty- -- ^ array type- | App Ty Ty- -- ^ function type- | Poly String- -- ^ polymorphic type variable- | Concrete- { name :: String- -- ^ the name of the type- , tyVars :: [Ty]- -- ^ the type's type variables- }- -- ^ a concrete type variable, and its- -- type variables. Will typically be generated- -- by 'getShwifty'.- | Tag- { name :: String- -- ^ the name of the type- , parent :: String- -- ^ the type constructor of the type- -- to which this alias belongs- , typ :: Ty- -- ^ the type that this represents- , disambiguate :: Bool- -- ^ does the type need disambiguation?- --- -- This will happen if there are multiple- -- tags with the same type. This is needed- -- to maintain safety.- }- -- ^ A @Tagged@ typealias, for newtyping- -- in a way that doesn't break Codable.- --- -- See 'getShwiftyWithTags' for examples.- deriving stock (Eq, Show, Read)- deriving stock (Generic)- deriving stock (Lift)---- | A Swift datatype, either a struct (product type)--- or enum (sum type). Haskll types are--- sums-of-products, so the way we differentiate--- when doing codegen,--- is that types with a single constructor--- will be converted to a struct, and those with--- two or more constructors will be converted to an--- enum. Types with 0 constructors will be converted--- to an empty enum.------ /Note/: There seems to be a haddock bug related--- to `-XDuplicateRecordFields` which--- causes the haddocks for the fields of 'SwiftEnum'--- to refer to struct.-data SwiftData- = SwiftStruct- { name :: String- -- ^ The name of the struct- , tyVars :: [String]- -- ^ The struct's type variables- , protocols :: [Protocol]- -- ^ The protocols which the struct- -- implements- , fields :: [(String, Ty)]- -- ^ The fields of the struct. the pair- -- is interpreted as (name, type).- , tags :: [Ty]- -- ^ The tags of the struct. See 'Tag'.- }- -- ^ A struct (product type)- | SwiftEnum- { name :: String- -- ^ The name of the enum- , tyVars :: [String]- -- ^ The enum's type variables- , protocols :: [Protocol]- -- ^ The protocols which the enum- -- implements- , cases :: [(String, [(Maybe String, Ty)])]- -- ^ The cases of the enum. the type- -- can be interpreted as- -- (name, [(label, type)]).- , rawValue :: Maybe Ty- -- ^ The rawValue of an enum. See- -- https://developer.apple.com/documentation/swift/rawrepresentable/1540698-rawvalue- --- -- Typically the 'Ty' will be- -- 'I' or 'String'.- --- -- /Note/: Currently, nothing will prevent- -- you from putting something- -- nonsensical here.- , tags :: [Ty]- -- ^ The tags of the struct. See 'Tag'.- }- -- ^ An enum (sum type)- | TypeAlias- { name :: String- -- ^ the name of the type alias- , tyVars :: [String]- -- ^ the type variables of the type alias- , typ :: Ty- -- ^ the type this represents (RHS)- }- -- ^ A /top-level/ type alias- deriving stock (Eq, Read, Show, Generic)---- | The class for things which can be converted to--- 'SwiftData'.------ Typically the instance will be generated by--- 'getShwifty'.-class ToSwiftData a where- -- | Convert a type to 'SwiftData'- toSwiftData :: Proxy a -> SwiftData- -- | Pretty-print a type as its 'SwiftData'.- prettySwiftData :: Proxy a -> String- default prettySwiftData :: Proxy a -> String- prettySwiftData = prettySwiftDataWith (indent defaultOptions) . toSwiftData---- | Swift protocols.--- Only a few are supported right now.-data Protocol- = Hashable- -- ^ The 'Hashable' protocol.- -- See https://developer.apple.com/documentation/swift/hashable- | Codable- -- ^ The 'Codable' protocol.- -- See https://developer.apple.com/documentation/swift/hashable- | Equatable- -- ^ The 'Equatable' protocol.- -- See https://developer.apple.com/documentation/swift/hashable- deriving stock (Eq, Read, Show, Generic)- deriving stock (Lift)---- | Options that specify how to--- encode your 'SwiftData' to a swift type.------ Options can be set using record syntax on--- 'defaultOptions' with the fields below.-data Options = Options- { typeConstructorModifier :: String -> String- -- ^ Function applied to type constructor names.- -- The default ('id') makes no changes.- , fieldLabelModifier :: String -> String- -- ^ Function applied to field labels.- -- Handy for removing common record prefixes,- -- for example. The default ('id') makes no- -- changes.- , constructorModifier :: String -> String- -- ^ Function applied to value constructor names.- -- The default ('id') makes no changes.- , optionalExpand :: Bool- -- ^ Whether or not to truncate Optional types.- -- Normally, an Optional ('Maybe') is encoded- -- as "A?", which is syntactic sugar for- -- "Optional\<A\>". The default value ('False')- -- will keep it as sugar. A value of 'True'- -- will expand it to be desugared.- , indent :: Int- -- ^ Number of spaces to indent field names- -- and cases. The default is 4.- , generateToSwift :: Bool- -- ^ Whether or not to generate a 'ToSwift'- -- instance. Sometime this can be desirable- -- if you want to define the instance by hand,- -- or the instance exists elsewhere.- -- The default is 'True', i.e., to generate- -- the instance.- , generateToSwiftData :: Bool- -- ^ Whether or not to generate a 'ToSwiftData'- -- instance. Sometime this can be desirable- -- if you want to define the instance by hand,- -- or the instance exists elsewhere.- -- The default is 'True', i.e., to generate- -- the instance.- , dataProtocols :: [Protocol]- -- ^ Protocols to add to a type.- -- The default (@[]@) will add none.- , dataRawValue :: Maybe Ty- -- ^ The rawValue of an enum. See- -- https://developer.apple.com/documentation/swift/rawrepresentable/1540698-rawvalue- --- -- The default ('Nothing') will not- -- include any rawValue.- --- -- Typically, if the type does have- -- a 'rawValue', the 'Ty' will be- -- 'I' or 'Str'.- --- -- /Note/: Currently, nothing will prevent- -- you from putting something- -- nonsensical here.- , typeAlias :: Bool- -- ^ Whether or not to generate a newtype as- -- a type alias. Consider if you want this- -- or to use 'getShwiftyWithTags' instead.- --- -- The default ('False') will generate newtypes- -- as their own structs.- , newtypeTag :: Bool- -- ^ Whether or not to generate a newtype as an- -- empty enum with a tag. This is for type- -- safety reasons, but with retaining the- -- ability to have Codable conformance.- --- -- The default ('False') will not do this.- --- -- /Note/: This takes priority over 'typeAlias'.- --- -- /Note/: This option is not currently- -- supported for newtype instances.- --- -- === __Examples__- --- -- > newtype NonEmptyText = MkNonEmptyText String- -- > $(getShwiftyWith (defaultOptions { newtypeTag = True }) ''NonEmpyText)- --- -- @- -- enum NonEmptyTextTag {- -- typealias NonEmptyText = Tagged\<NonEmptyTextTag, String\>- -- }- -- @- , lowerFirstField :: Bool- -- ^ Whether or not to lower-case the first- -- character of a field after applying all- -- modifiers to it.- --- -- The default ('True') will do so.- , lowerFirstCase :: Bool- -- ^ Whether or not to lower-case the first- -- character of a case after applying all- -- modifiers to it.- --- -- The default ('True') will do so.- }---- | The default 'Options'.------ @--- defaultOptions :: Options--- defaultOptions = Options--- { typeConstructorModifier = id--- , fieldLabelModifier = id--- , constructorModifier = id--- , optionalExpand= False--- , indent = 4--- , generateToSwift = True--- , generateToSwiftData = True--- , dataProtocols = []--- , dataRawValue = Nothing--- , typeAlias = False--- , newtypeTag = False--- , lowerFirstField = True--- , lowerFirstCase = True--- }--- @----defaultOptions :: Options-defaultOptions = Options- { typeConstructorModifier = id- , fieldLabelModifier = id- , constructorModifier = id- , optionalExpand = False- , indent = 4- , generateToSwift = True- , generateToSwiftData = True- , dataProtocols = []- , dataRawValue = Nothing- , typeAlias = False- , newtypeTag = False- , lowerFirstField = True- , lowerFirstCase = True- }---- | The class for things which can be converted to--- a Swift type ('Ty').------ Typically the instance will be generated by--- 'getShwifty'.-class ToSwift a where- toSwift :: Proxy a -> Ty---- Used internally to reflect polymorphic type--- variables into TH, then reify them into 'Poly'.------ See the Rose tree section below-data SingSymbol (x :: Symbol)-instance KnownSymbol x => ToSwift (SingSymbol x) where- toSwift _ = Poly (symbolVal (Proxy @x))---- | A filler type to be used when pretty-printing.--- The codegen used by shwifty doesn't look at--- at what a type's type variables are instantiated--- to, but rather at the type's top-level--- definition. However,--- to make GHC happy, you will have to fill in type--- variables with unused types. To get around this,--- you could also use something like--- `-XQuantifiedConstraints`, or existential types,--- but we leave that to the user to handle.-type X = Void--instance ToSwift Void where- toSwift = const (Concrete "Void" [])--instance ToSwift () where- toSwift = const Unit--instance ToSwift Bool where- toSwift = const Bool--instance ToSwift UUID where- toSwift = const (Concrete "UUID" [])--instance ToSwift UTCTime where- toSwift = const (Concrete "Date" [])--instance forall a b. (ToSwift a, ToSwift b) => ToSwift (a -> b) where- toSwift = const (App (toSwift (Proxy @a)) (toSwift (Proxy @b)))--instance forall a. ToSwift a => ToSwift (Maybe a) where- toSwift = const (Optional (toSwift (Proxy @a)))---- | /Note/: In Swift, the ordering of the type--- variables is flipped - Shwifty has made the--- design choice to flip them for you. If you--- take issue with this, please open an issue--- for discussion on GitHub.-instance forall a b. (ToSwift a, ToSwift b) => ToSwift (Either a b) where- toSwift = const (Result (toSwift (Proxy @b)) (toSwift (Proxy @a)))--instance ToSwift Integer where- toSwift = const-#if WORD_SIZE_IN_BITS == 32- BigSInt32-#else- BigSInt64-#endif--instance ToSwift Int where toSwift = const I-instance ToSwift Int8 where toSwift = const I8-instance ToSwift Int16 where toSwift = const I16-instance ToSwift Int32 where toSwift = const I32-instance ToSwift Int64 where toSwift = const I64--instance ToSwift Word where toSwift = const U-instance ToSwift Word8 where toSwift = const U8-instance ToSwift Word16 where toSwift = const U16-instance ToSwift Word32 where toSwift = const U32-instance ToSwift Word64 where toSwift = const U64--instance ToSwift Float where toSwift = const F32-instance ToSwift Double where toSwift = const F64--instance ToSwift Char where toSwift = const Character--instance forall a. (ToSwift a) => ToSwift (Prim.Array a) where- toSwift = const (Array (toSwift (Proxy @a)))--instance forall a. (ToSwift a) => ToSwift (Prim.SmallArray a) where- toSwift = const (Array (toSwift (Proxy @a)))--instance ToSwift Prim.ByteArray where- toSwift = const (Array U8)--instance forall a. (ToSwift a) => ToSwift (Prim.PrimArray a) where- toSwift = const (Array (toSwift (Proxy @a)))--instance forall a. ToSwift a => ToSwift (Vector a) where- toSwift = const (Array (toSwift (Proxy @a)))--instance {-# overlappable #-} forall a. ToSwift a => ToSwift [a] where- toSwift = const (Array (toSwift (Proxy @a)))--instance {-# overlapping #-} ToSwift [Char] where toSwift = const Str--instance ToSwift TL.Text where toSwift = const Str-instance ToSwift TS.Text where toSwift = const Str--instance ToSwift BL.ByteString where toSwift = const (Array U8)-instance ToSwift BS.ByteString where toSwift = const (Array U8)--instance ToSwift (CI s) where toSwift = const Str--instance forall k v. (ToSwift k, ToSwift v) => ToSwift (M.Map k v) where toSwift = const (Dictionary (toSwift (Proxy @k)) (toSwift (Proxy @v)))--instance forall k v. (ToSwift k, ToSwift v) => ToSwift (HM.HashMap k v) where toSwift = const (Dictionary (toSwift (Proxy @k)) (toSwift (Proxy @v)))--instance forall a b. (ToSwift a, ToSwift b) => ToSwift ((,) a b) where- toSwift = const (Tuple2 (toSwift (Proxy @a)) (toSwift (Proxy @b)))--instance forall a b c. (ToSwift a, ToSwift b, ToSwift c) => ToSwift ((,,) a b c) where- toSwift = const (Tuple3 (toSwift (Proxy @a)) (toSwift (Proxy @b)) (toSwift (Proxy @c)))--labelCase :: Maybe String -> Ty -> String-labelCase Nothing ty = prettyTy ty-labelCase (Just label) ty = "_ " ++ label ++ ": " ++ prettyTy ty--prettyTypeHeader :: String -> [String] -> String-prettyTypeHeader name [] = name-prettyTypeHeader name tyVars = name ++ "<" ++ intercalate ", " tyVars ++ ">"---- | Pretty-print a 'Ty'.-prettyTy :: Ty -> String-prettyTy = \case- Str -> "String"- Unit -> "()"- Bool -> "Bool"- Character -> "Character"- Tuple2 e1 e2 -> "(" ++ prettyTy e1 ++ ", " ++ prettyTy e2 ++ ")"- Tuple3 e1 e2 e3 -> "(" ++ prettyTy e1 ++ ", " ++ prettyTy e2 ++ ", " ++ prettyTy e3 ++ ")"- Optional e -> prettyTy e ++ "?"- Result e1 e2 -> "Result<" ++ prettyTy e1 ++ ", " ++ prettyTy e2 ++ ">"- Set e -> "Set<" ++ prettyTy e ++ ">"- Dictionary e1 e2 -> "Dictionary<" ++ prettyTy e1 ++ ", " ++ prettyTy e2 ++ ">"- Array e -> "[" ++ prettyTy e ++ "]"- -- App is special, we recurse until we no longer- -- any applications.- App e1 e2 -> prettyApp e1 e2- I -> "Int"- I8 -> "Int8"- I16 -> "Int16"- I32 -> "Int32"- I64 -> "Int64"- U -> "UInt"- U8 -> "UInt8"- U16 -> "UInt16"- U32 -> "UInt32"- U64 -> "UInt64"- F32 -> "Float"- F64 -> "Double"- Decimal -> "Decimal"- BigSInt32 -> "BigSInt32"- BigSInt64 -> "BigSInt64"- Poly ty -> ty- Concrete ty [] -> ty- Concrete ty tys -> ty- ++ "<"- ++ intercalate ", " (map prettyTy tys)- ++ ">"- Tag {name,parent} -> parent ++ "." ++ name--prettyApp :: Ty -> Ty -> String-prettyApp t1 t2 = "(("- ++ intercalate ", " (map prettyTy as)- ++ ") -> "- ++ prettyTy r- ++ ")"- where- (as, r) = go t1 t2- go e1 (App e2 e3) = case go e2 e3 of- (args, ret) -> (e1 : args, ret)- go e1 e2 = ([e1], e2)--prettyRawValueAndProtocols :: Maybe Ty -> [Protocol] -> String-prettyRawValueAndProtocols Nothing ps = prettyProtocols ps-prettyRawValueAndProtocols (Just ty) [] = ": " ++ prettyTy ty-prettyRawValueAndProtocols (Just ty) ps = ": " ++ prettyTy ty ++ ", " ++ intercalate ", " (map show ps)--prettyProtocols :: [Protocol] -> String-prettyProtocols = \case- [] -> ""- ps -> ": " ++ intercalate ", " (map show ps)--prettyTags :: String -> [Ty] -> String-prettyTags indents = go where- go [] = ""- go (Tag{name,parent,typ,disambiguate}:ts) = []- ++ "\n"- ++ prettyTagDisambiguator disambiguate indents name- ++ indents- ++ "typealias "- ++ name- ++ " = Tagged<"- ++ (if disambiguate then name ++ "Tag" else parent)- ++ ", "- ++ prettyTy typ- ++ ">"- ++ go ts- go _ = error "non-tag supplied to prettyTags"--prettyTagDisambiguator :: ()- => Bool- -- ^ disambiguate?- -> String- -- ^ indents- -> String- -- ^ parent type name- -> String-prettyTagDisambiguator disambiguate indents parent- = if disambiguate- then []- ++ indents- ++ "enum "- ++ parent- ++ "Tag { }\n"- else ""---- | Pretty-print a 'SwiftData'.--- This function cares about indent.-prettySwiftDataWith :: ()- => Int -- ^ indent- -> SwiftData- -> String-prettySwiftDataWith indent = \case-- SwiftEnum {name,tyVars,protocols,cases,rawValue,tags} -> []- ++ "enum "- ++ prettyTypeHeader name tyVars- ++ prettyRawValueAndProtocols rawValue protocols- ++ " {"- ++ newlineNonEmpty cases- ++ go cases- ++ prettyTags indents tags- ++ newlineNonEmpty tags- ++ "}"- where- go [] = ""- go ((caseNm, []):xs) = []- ++ indents- ++ "case "- ++ caseNm- ++ "\n"- ++ go xs- go ((caseNm, cs):xs) = []- ++ indents- ++ "case "- ++ caseNm- ++ "("- ++ (intercalate ", " (map (uncurry labelCase) cs))- ++ ")\n"- ++ go xs- SwiftStruct {name,tyVars,protocols,fields,tags} -> []- ++ "struct "- ++ prettyTypeHeader name tyVars- ++ prettyProtocols protocols- ++ " {"- ++ newlineNonEmpty fields- ++ go fields- ++ prettyTags indents tags- ++ newlineNonEmpty tags- ++ "}"- where- go [] = ""- go ((fieldName,ty):fs) = indents ++ "let " ++ fieldName ++ ": " ++ prettyTy ty ++ "\n" ++ go fs- TypeAlias {name, tyVars, typ} -> []- ++ "typealias "- ++ prettyTypeHeader name tyVars- ++ " = "- ++ prettyTy typ- where- indents = replicate indent ' '-- newlineNonEmpty [] = ""- newlineNonEmpty _ = "\n"--ensureEnabled :: Extension -> ShwiftyM ()-ensureEnabled ext = do- enabled <- lift $ isExtEnabled ext- unless enabled $ do- throwError $ ExtensionNotEnabled ext---- | Generate 'ToSwiftData' and 'ToSwift' instances--- for your type. 'ToSwift' instances are typically--- used to build cases or fields, whereas--- 'ToSwiftData' instances are for building structs--- and enums. Click the @Examples@ button to see--- examples of what Swift gets generated in--- different scenarios. To get access to the--- generated code, you will have to use one of--- the pretty-printing functions provided.------ === __Examples__------ > -- A simple sum type--- > data SumType = Sum1 | Sum2 | Sum3--- > getShwifty ''SumType------ @--- enum SumType {--- case sum1--- case sum2--- case sum3--- }--- @------ > -- A simple product type--- > data ProductType = ProductType { x :: Int, y :: Int }--- > getShwifty ''ProductType------ @--- struct ProductType {--- let x: Int--- let y: Int--- }--- @------ > -- A sum type with type variables--- > data SumType a b = SumL a | SumR b--- > getShwifty ''SumType------ @--- enum SumType\<A, B\> {--- case sumL(A)--- case sumR(B)--- }--- @------ > -- A product type with type variables--- > data ProductType a b = ProductType { aField :: a, bField :: b }--- > getShwifty ''ProductType------ @--- struct ProductType\<A, B\> {--- let aField: A--- let bField: B--- }--- @------ > -- A newtype--- > newtype Newtype a = Newtype { getNewtype :: a }--- > getShwifty ''Newtype------ @--- struct Newtype\<A\> {--- let getNewtype: A--- }--- @------ > -- A type with a function field--- > newtype Endo a = Endo { appEndo :: a -> a }--- > getShwifty ''Endo------ @--- struct Endo\<A\> {--- let appEndo: ((A) -> A)--- }--- @------ > -- A type with a kookier function field--- > newtype Fun a = Fun { fun :: Int -> Char -> Bool -> String -> Maybe a }--- > getShwifty ''Fun------ @--- struct Fun<A> {--- let fun: ((Int, Char, Bool, String) -> A?)--- }--- @------ > -- A weird type with nested fields. Also note the Result's types being flipped from that of the Either.--- > data YouveGotProblems a b = YouveGotProblems { field1 :: Maybe (Maybe (Maybe a)), field2 :: Either (Maybe a) (Maybe b) }--- > getShwifty ''YouveGotProblems------ @--- struct YouveGotProblems\<A, B\> {--- let field1: Option\<Option\<Option\<A\>\>\>--- let field2: Result\<Option\<B\>,Option\<A\>\>--- }--- @------ > -- A type with polykinded type variables--- > -- Also note that there is no newline because--- > -- of the absence of fields--- > data PolyKinded (a :: k) = PolyKinded--- > getShwifty ''PolyKinded------ @--- struct PolyKinded\<A\> { }--- @------ > -- A sum type where constructors might be records--- > data SumType a b (c :: k) = Sum1 Int a (Maybe b) | Sum2 b | Sum3 { x :: Int, y :: Int }--- > getShwifty ''SumType------ @--- enum SumType\<A, B, C\> {--- case field1(Int, A, Optional\<B\>)--- case field2(B)--- case field3(_ x: Int, _ y: Int)--- }--- @------ > -- A type containing another type with instance generated by 'getShwifty'--- > newtype MyFirstType a = MyFirstType { getMyFirstType :: a }--- > getShwifty ''MyFirstType--- >--- > data Contains a = Contains { x :: MyFirstType Int, y :: MyFirstType a }--- > getShwifty ''Contains------ @--- struct MyFirstType\<A\> {--- let getMyFirstType: A--- }------ struct Contains\<A\> {--- let x: MyFirstType\<Int\>--- let y: MyFirstType\<A\>--- }--- @-getShwifty :: Name -> Q [Dec]-getShwifty = getShwiftyWith defaultOptions---- | Like 'getShwifty', but lets you supply--- your own 'Options'. Click the examples--- for some clarification of what you can do.------ === __Examples__------ > data PrefixedFields = MkPrefixedFields { prefixedFieldsX :: Int, prefixedFieldsY :: Int }--- > $(getShwiftyWith (defaultOptions { fieldLabelModifier = drop (length "PrefixedFields") }) ''PrefixedFields)------ @--- struct PrefixedFields {--- let x: Int--- let y: Int--- }--- @------ > data PrefixedCons = MkPrefixedConsLeft | MkPrefixedConsRight--- > $(getShwiftyWith (defaultOptions { constructorModifier = drop (length "MkPrefixedCons"), dataProtocols = [Codable] }) ''PrefixedCons)------ @--- enum PrefixedCons: Codable {--- case left--- case right--- }--- @-getShwiftyWith :: Options -> Name -> Q [Dec]-getShwiftyWith o n = getShwiftyWithTags o [] n--data NewtypeInfo = NewtypeInfo- { newtypeName :: Name- -- ^ Type constructor- , newtypeVars :: [TyVarBndr]- -- ^ Type parameters- , newtypeInstTypes :: [Type]- -- ^ Argument types- , newtypeVariant :: DatatypeVariant- -- ^ Whether or not the type is a- -- newtype or newtype instance- , newtypeCon :: ConstructorInfo- }--reifyNewtype :: Name -> ShwiftyM NewtypeInfo-reifyNewtype n = do- DatatypeInfo{..} <- lift $ reifyDatatype n- case datatypeCons of- [c] -> do- pure NewtypeInfo {- newtypeName = datatypeName- , newtypeVars = datatypeVars- , newtypeInstTypes = datatypeInstTypes- , newtypeVariant = datatypeVariant- , newtypeCon = c- }- _ -> do- throwError $ NotANewtype n---- Generate the tags for a type.--- Also generate the ToSwift instance for each tag--- type. We can't just expect people to do this--- with a separate 'getShwifty' call, because--- they will generate the wrong code, since other--- types with a tag that isn't theirs won't generate--- well-scoped fields.-getTags :: ()- => Name- -- ^ name of parent type- -> [Name]- -- ^ tags- -> ShwiftyM ([Exp], [Dec])-getTags parentName ts = do- let len = length ts- disambiguate <- lift $ [||len > 1||]- tags <- foldlM- (\(es,ds) n -> do-- NewtypeInfo{..} <- reifyNewtype n- let ConstructorInfo{..} = newtypeCon-- -- generate the tag- let tyconName = case newtypeVariant of- NewtypeInstance -> constructorName- _ -> newtypeName- typ <- case constructorFields of- [ty] -> pure ty- _ -> throwError $ NotANewtype newtypeName- let tag = RecConE 'Tag- [ (mkName "name", unqualName tyconName)- , (mkName "parent", unqualName parentName)- , (mkName "typ", toSwiftEPoly typ)- , (mkName "disambiguate", unType disambiguate)- ]-- -- generate the instance- !instHeadTy- <- buildTypeInstance newtypeName ClassSwift newtypeInstTypes newtypeVars newtypeVariant- -- we do not want to strip here- clauseTy <- tagToSwift tyconName typ parentName- swiftTyInst <- lift $ instanceD- (pure [])- (pure instHeadTy)- [ funD 'toSwift- [ clause [] (normalB (pure clauseTy)) []- ]- ]-- pure $ (es ++ [tag], ds ++ [swiftTyInst])- ) ([], []) ts- pure tags--getToSwift :: ()- => Options- -- ^ options- -> Name- -- ^ type name- -> [Type]- -- ^ type variables- -> [TyVarBndr]- -- ^ type binders- -> DatatypeVariant- -- ^ type variant- -> [ConstructorInfo]- -- ^ constructors- -> ShwiftyM [Dec]-getToSwift Options{..} parentName instTys tyVarBndrs variant cons = if generateToSwift- then do- instHead <- buildTypeInstance parentName ClassSwift instTys tyVarBndrs variant- clauseTy <- case variant of- NewtypeInstance -> case cons of- [ConstructorInfo{..}] -> do- newtypToSwift constructorName instTys- _ -> do- throwError ExpectedNewtypeInstance- _ -> do- typToSwift newtypeTag parentName instTys- inst <- lift $ instanceD- (pure [])- (pure instHead)- [ funD 'toSwift- [ clause [] (normalB (pure clauseTy)) []- ]- ]- pure [inst]- else do- pure []--getToSwiftData :: ()- => Options- -- ^ options- -> Name- -- ^ type name- -> [Type]- -- ^ type variables- -> [TyVarBndr]- -- ^ type binders- -> DatatypeVariant- -- ^ type variant- -> [Exp]- -- ^ tags- -> [ConstructorInfo]- -- ^ constructors- -> ShwiftyM [Dec]-getToSwiftData o@Options{..} parentName instTys tyVarBndrs variant tags cons = if generateToSwiftData- then do- instHead <- buildTypeInstance parentName ClassSwiftData instTys tyVarBndrs variant- clauseData <- consToSwift o parentName instTys variant tags cons- clausePretty <- mkClausePretty o- inst <- lift $ instanceD- (pure [])- (pure instHead)- [ funD 'toSwiftData- [ clause [] (normalB (pure clauseData)) []- ]- , funD 'prettySwiftData- [ clause [] (normalB (pure clausePretty)) []- ]- ]- pure [inst]- else do- pure []---- | Like 'getShwiftyWith', but lets you supply--- tags. Tags are type-safe typealiases that--- are akin to newtypes in Haskell. The--- introduction of a struct around something--- which is, say, a UUID in Swift means that--- the default Codable instance will not work--- correctly. So we introduce a tag(s). See the--- examples to see how this looks. Also, see--- https://github.com/pointfreeco/swift-tagged,--- the library which these tags use. The library--- is not included in any generated code.------ === __Examples__------ > -- Example of using the swift-tagged library:--- > -- A type containing a database key--- > data User = User { id :: UserId, name :: Text }--- > -- the user key--- > newtype UserId = UserId UUID--- > $(getShwiftyWithTags defaultOptions [ ''UserId ] ''User)--- > -- A type that also contains the UserId--- > data UserDetails = UserDetails { id :: UserId, lastName :: Text }--- > getShwifty ''UserDetails------ @--- struct User {--- let id: UserId--- let name: String------ typealias UserId = Tagged\<User,UUID\>--- }------ struct UserDetails {--- let id: User.UserId--- let lastName: String--- }--- @------ > -- Example type with multiple tags--- > newtype Name = MkName String--- > newtype Email = MkEmail String--- > data Person = Person { name :: Name, email :: Email }--- > $(getShwiftyWithTags defaultOptions [ ''Name, ''Email ] ''Person)------ @--- struct Person {--- let name: Name--- let email: Email------ enum NameTag {}--- typealias Name = Tagged\<NameTag, String\>------ enum EmailTag {}--- typealias Email = Tagged\<EmailTag, String\>--- }--- @-getShwiftyWithTags :: ()- => Options- -> [Name]- -> Name- -> Q [Dec]-getShwiftyWithTags o@Options{..} ts name = do- r <- runExceptT $ do- ensureEnabled ScopedTypeVariables- ensureEnabled DataKinds- ensureEnabled DuplicateRecordFields- DatatypeInfo- { datatypeName = parentName- , datatypeVars = tyVarBndrs- , datatypeInstTypes = instTys- , datatypeVariant = variant- , datatypeCons = cons- } <- lift $ reifyDatatype name- noExistentials cons-- -- get tags/ToSwift instances for tags- (tags, extraDecs) <- getTags parentName ts-- swiftDataInst <- getToSwiftData o parentName instTys tyVarBndrs variant tags cons-- swiftTyInst <- getToSwift o parentName instTys tyVarBndrs variant cons- pure $ swiftDataInst ++ swiftTyInst ++ extraDecs- case r of- Left e -> fail $ prettyShwiftyError e- Right d -> pure d--noExistentials :: [ConstructorInfo] -> ShwiftyM ()-noExistentials cs = forM_ cs $ \ConstructorInfo{..} ->- case (constructorName, constructorVars) of- (_, []) -> do- pure ()- (cn, cvs) -> do- throwError $ ExistentialTypes cn cvs--data ShwiftyError- = SingleConNonRecord- { _conName :: Name- }- | EncounteredInfixConstructor- { _conName :: Name- }- | KindVariableCannotBeRealised- { _typName :: Name- , _kind :: Kind- }- | ExtensionNotEnabled- { _ext :: Extension- }- | ExistentialTypes- { _conName :: Name- , _types :: [TyVarBndr]- }- | ExpectedNewtypeInstance- | NotANewtype- { _typName :: Name- }--prettyShwiftyError :: ShwiftyError -> String-prettyShwiftyError = \case- SingleConNonRecord (nameStr -> n) -> mempty- ++ n- ++ ": Cannot get shwifty with single-constructor "- ++ "non-record types. This is due to a "- ++ "restriction of Swift that prohibits structs "- ++ "from not having named fields. Try turning "- ++ n ++ " into a record!"- EncounteredInfixConstructor (nameStr -> n) -> mempty- ++ n- ++ ": Cannot get shwifty with infix constructors. "- ++ "Swift doesn't support them. Try changing "- ++ n ++ " into a prefix constructor!"- KindVariableCannotBeRealised (nameStr -> n) typ ->- let (typStr, kindStr) = prettyKindVar typ- in mempty- ++ n- ++ ": Encountered a type variable ("- ++ typStr- ++ ") with a kind ("- ++ kindStr- ++ ") that can't "- ++ "get shwifty! Shwifty needs to be able "- ++ "to realise your kind variables to `*`, "- ++ "since that's all that makes sense in "- ++ "Swift. The only kinds that can happen with "- ++ "are `*` and the free-est kind, `k`."- ExtensionNotEnabled ext -> mempty- ++ show ext- ++ " is not enabled. Shwifty needs it to work!"- -- TODO: make this not print out implicit kinds.- -- e.g. for `data Ex = forall x. Ex x`, there are- -- no implicit `TyVarBndr`s, but for- -- `data Ex = forall x y z. Ex x`, there are two:- -- the kinds inferred by `y` and `z` are both `k`.- -- We print these out - this could be confusing to- -- the end user. I'm not immediately certain how to- -- be rid of them.- ExistentialTypes (nameStr -> n) tys -> mempty- ++ n- ++ " has existential type variables ("- ++ L.intercalate ", " (map prettyTyVarBndrStr tys)- ++ ")! Shwifty doesn't support these."- ExpectedNewtypeInstance -> mempty- ++ "Expected a newtype instance. This is an "- ++ "internal logic error. Please report it as a "- ++ "bug."- NotANewtype (nameStr -> n) -> mempty- ++ n- ++ " is not a newtype. This is an internal logic "- ++ "error. Please report it as a bug."--prettyTyVarBndrStr :: TyVarBndr -> String-prettyTyVarBndrStr = \case- PlainTV n -> go n- KindedTV n _ -> go n- where- go = TS.unpack . head . TS.splitOn "_" . last . TS.splitOn "." . TS.pack . show---- prettify the type and kind.-prettyKindVar :: Type -> (String, String)-prettyKindVar = \case- SigT typ k -> (go typ, go k)- VarT n -> (nameStr n, "*")- typ -> error $ "Shwifty.prettyKindVar: used on a type without a kind signature. Type was: " ++ show typ- where- go = TS.unpack . head . TS.splitOn "_" . last . TS.splitOn "." . TS.pack . show . ppr--type ShwiftyM = ExceptT ShwiftyError Q--tagToSwift :: ()- => Name- -- ^ name of the type constructor- -> Type- -- ^ type variables- -> Name- -- ^ parent name- -> ShwiftyM Exp-tagToSwift tyconName typ parentName = do- -- TODO: use '_' instead of matching- value <- lift $ newName "value"- matches <- lift $ fmap ((:[]) . pure) $ do- match- (conP 'Proxy [])- (normalB- $ pure- $ RecConE 'Tag- $ [ (mkName "name", unqualName tyconName)- , (mkName "parent", unqualName parentName)- , (mkName "typ", toSwiftECxt typ)- , (mkName "disambiguate", (ConE 'False))- ]- )- []- lift $ lamE [varP value] (caseE (varE value) matches)-newtypToSwift :: ()- => Name- -- ^ name of the constructor- -> [Type]- -- ^ type variables- -> ShwiftyM Exp-newtypToSwift conName (stripConT -> instTys) = do- typToSwift False conName instTys--typToSwift :: ()- => Bool- -- ^ is this a newtype tag?- -> Name- -- ^ name of the type- -> [Type]- -- ^ type variables- -> ShwiftyM Exp-typToSwift newtypeTag parentName instTys = do- -- TODO: use '_' instead of matching- value <- lift $ newName "value"- let tyVars = map toSwiftECxt instTys- let name =- let parentStr = nameStr parentName- accessedName = if newtypeTag- then parentStr ++ "Tag." ++ parentStr- else parentStr- in stringE accessedName- matches <- lift $ fmap ((:[]) . pure) $ do- match- (conP 'Proxy [])- (normalB- $ pure- $ RecConE 'Concrete- $ [ (mkName "name", name)- , (mkName "tyVars", ListE tyVars)- ]- )- []- lift $ lamE [varP value] (caseE (varE value) matches)--rawValueE :: Maybe Ty -> Exp-rawValueE = \case- Nothing -> ConE 'Nothing- Just ty -> AppE (ConE 'Just) (ParensE (tyE ty))---- god this is annoying. write a cleaner--- version of this-tyE :: Ty -> Exp-tyE = \case- Unit -> ConE 'Unit- Bool -> ConE 'Bool- Character -> ConE 'Character- Str -> ConE 'Str- I -> ConE 'I- I8 -> ConE 'I8- I16 -> ConE 'I16- I32 -> ConE 'I32- I64 -> ConE 'I64- U -> ConE 'U- U8 -> ConE 'U8- U16 -> ConE 'U16- U32 -> ConE 'U32- U64 -> ConE 'U64- F32 -> ConE 'F32- F64 -> ConE 'F64- Decimal -> ConE 'Decimal- BigSInt32 -> ConE 'BigSInt32- BigSInt64 -> ConE 'BigSInt64- Poly s -> AppE (ConE 'Poly) (stringE s)- Concrete tyCon tyVars -> AppE (AppE (ConE 'Concrete) (stringE tyCon)) (ListE (map tyE tyVars))- Tuple2 e1 e2 -> AppE (AppE (ConE 'Tuple2) (tyE e1)) (tyE e2)- Tuple3 e1 e2 e3 -> AppE (AppE (AppE (ConE 'Tuple3) (tyE e1)) (tyE e2)) (tyE e3)- Optional e -> AppE (ConE 'Optional) (tyE e)- Result e1 e2 -> AppE (AppE (ConE 'Result) (tyE e1)) (tyE e2)- Set e -> AppE (ConE 'Set) (tyE e)- Dictionary e1 e2 -> AppE (AppE (ConE 'Dictionary) (tyE e1)) (tyE e2)- App e1 e2 -> AppE (AppE (ConE 'App) (tyE e1)) (tyE e2)- Array e -> AppE (ConE 'Array) (tyE e)- Tag{name,parent,typ,disambiguate} -> AppE (AppE (AppE (AppE (ConE 'Tag) (stringE name)) (stringE parent)) (tyE typ)) (if disambiguate then ConE 'True else ConE 'False)--mkClausePretty :: ()- => Options- -> ShwiftyM Exp-mkClausePretty Options{..} = do- value <- lift $ newName "value"- matches <- lift $ do- x <- match- (conP 'Proxy [])- (normalB- $ pure- $ AppE- (AppE- (VarE 'prettySwiftDataWith)- (LitE (IntegerL (fromIntegral indent)))- )- $ ParensE- (AppE- (VarE 'toSwiftData)- (VarE value)- )- )- []- pure [pure x]- lift $ lamE [varP value] (caseE (varE value) matches)--consToSwift :: ()- => Options- -- ^ options about how to encode things- -> Name- -- ^ name of type- -> [Type]- -- ^ type variables- -> DatatypeVariant- -- ^ data type variant- -> [Exp]- -- ^ tags- -> [ConstructorInfo]- -- ^ constructors- -> ShwiftyM Exp-consToSwift o@Options{..} parentName instTys variant ts = \case- [] -> do- value <- lift $ newName "value"- matches <- liftCons (mkVoid parentName instTys ts)- lift $ lamE [varP value] (caseE (varE value) matches)- cons -> do- -- TODO: use '_' instead of matching- value <- lift $ newName "value"- matches <- matchesWorker- lift $ lamE [varP value] (caseE (varE value) matches)- where- -- bad name- matchesWorker :: ShwiftyM [Q Match]- matchesWorker = case cons of- [con] -> liftCons $ do- case variant of- NewtypeInstance -> do- if | typeAlias -> do- mkNewtypeInstanceAlias instTys con- | otherwise -> do- mkNewtypeInstance o instTys ts con- Newtype -> do- if | newtypeTag -> do- mkTypeTag o parentName instTys con- | typeAlias -> do- mkTypeAlias parentName instTys con- | otherwise -> do- mkProd o parentName instTys ts con- _ -> do- mkProd o parentName instTys ts con- _ -> do- let tyVars = prettyTyVars instTys- let protos = map (ConE . mkName . show) dataProtocols- let raw = rawValueE dataRawValue- let tags = ListE ts- cases <- forM cons (liftEither . mkCase o)- pure $ (:[]) $ match- (conP 'Proxy [])- (normalB- $ pure- $ RecConE 'SwiftEnum- $ [ (mkName "name", unqualName parentName)- , (mkName "tyVars", tyVars)- , (mkName "protocols", ListE protos)- , (mkName "cases", ListE cases)- , (mkName "rawValue", raw)- , (mkName "tags", tags)- ]- )- []--liftCons :: (Functor f, Applicative g) => f a -> f ([g a])-liftCons x = ((:[]) . pure) <$> x---- Create the case (String, [(Maybe String, Ty)])-mkCaseHelper :: Options -> Name -> [Exp] -> Exp-mkCaseHelper o name es = TupE [ caseName o name, ListE es ]--mkCase :: ()- => Options- -> ConstructorInfo- -> Either ShwiftyError Exp-mkCase o = \case- -- non-record- ConstructorInfo- { constructorVariant = NormalConstructor- , constructorName = name- , constructorFields = fields- , ..- } -> Right $ mkCaseHelper o name $ fields <&>- (\typ -> TupE- [ ConE 'Nothing- , toSwiftEPoly typ- ]- )- ConstructorInfo- { constructorVariant = InfixConstructor- , constructorName = name- , ..- } -> Left $ EncounteredInfixConstructor name- -- records- -- we turn names into labels- ConstructorInfo- { constructorVariant = RecordConstructor fieldNames- , constructorName = name- , constructorFields = fields- , ..- } ->- let cases = zipWith (caseField o) fieldNames fields- in Right $ mkCaseHelper o name cases--caseField :: Options -> Name -> Type -> Exp-caseField o n typ = TupE- [ mkLabel o n- , toSwiftEPoly typ- ]--onHeadWith :: Bool -> String -> String-onHeadWith toLower = if toLower- then onHead Char.toLower- else id---- apply a function only to the head of a string-onHead :: (Char -> Char) -> String -> String-onHead f = \case { [] -> []; (x:xs) -> f x : xs }--mkLabel :: Options -> Name -> Exp-mkLabel Options{..} = AppE (ConE 'Just)- . stringE- . fieldLabelModifier- . onHeadWith lowerFirstField- . TS.unpack- . last- . TS.splitOn "."- . TS.pack- . show--mkNewtypeInstanceAlias :: ()- => [Type]- -- ^ type variables- -> ConstructorInfo- -- ^ constructor info- -> ShwiftyM Match-mkNewtypeInstanceAlias (stripConT -> instTys) = \case- ConstructorInfo- { constructorFields = [field]- , ..- } -> do- let tyVars = prettyTyVars instTys- lift $ match- (conP 'Proxy [])- (normalB- $ pure- $ RecConE 'TypeAlias- $ [ (mkName "name", unqualName constructorName)- , (mkName "tyVars", tyVars)- , (mkName "typ", toSwiftECxt field)- ]- )- []- _ -> throwError $ ExpectedNewtypeInstance--mkNewtypeInstance :: ()- => Options- -- ^ encoding options- -> [Type]- -- ^ type variables- -> [Exp]- -- ^ tags- -> ConstructorInfo- -- ^ constructor info- -> ShwiftyM Match-mkNewtypeInstance o@Options{dataProtocols} (stripConT -> instTys) ts = \case- ConstructorInfo- { constructorVariant = RecordConstructor [fieldName]- , constructorFields = [field]- , ..- } -> do- let tyVars = prettyTyVars instTys- let protos = ListE $ map (ConE . mkName . show) dataProtocols- let fields = ListE $ [prettyField o fieldName field]- let tags = ListE ts- lift $ match- (conP 'Proxy [])- (normalB- $ pure- $ RecConE 'SwiftStruct- $ [ (mkName "name", unqualName constructorName)- , (mkName "tyVars", tyVars)- , (mkName "protocols", protos)- , (mkName "fields", fields)- , (mkName "tags", tags)- ]- )- []- _ -> throwError ExpectedNewtypeInstance---- make a newtype into an empty enum--- with a tag-mkTypeTag :: ()- => Options- -- ^ options- -> Name- -- ^ type name- -> [Type]- -- ^ type variables- -> ConstructorInfo- -- ^ constructor info- -> ShwiftyM Match-mkTypeTag Options{..} typName instTys = \case- ConstructorInfo- { constructorFields = [field]- , ..- } -> do- let tyVars = prettyTyVars instTys- let protos = map (ConE . mkName . show) dataProtocols- let raw = rawValueE dataRawValue- let parentName = nameStr typName ++ "Tag"- let tag = RecConE 'Tag- [ (mkName "name", unqualName typName)- , (mkName "parent", stringE parentName)- , (mkName "typ", toSwiftEPoly field)- , (mkName "disambiguate", ConE 'False)- ]- lift $ match- (conP 'Proxy [])- (normalB- $ pure- $ RecConE 'SwiftEnum- $ [ (mkName "name", stringE parentName)- , (mkName "tyVars", tyVars)- , (mkName "protocols", ListE protos)- , (mkName "cases", ListE [])- , (mkName "rawValue", raw)- , (mkName "tags", ListE [tag])- ]- )- []- _ -> throwError $ NotANewtype typName---- make a newtype into a type alias-mkTypeAlias :: ()- => Name- -- ^ type name- -> [Type]- -- ^ type variables- -> ConstructorInfo- -- ^ constructor info- -> ShwiftyM Match-mkTypeAlias typName instTys = \case- ConstructorInfo- { constructorFields = [field]- , ..- } -> do- let tyVars = prettyTyVars instTys- lift $ match- (conP 'Proxy [])- (normalB- $ pure- $ RecConE 'TypeAlias- $ [ (mkName "name", unqualName typName)- , (mkName "tyVars", tyVars)- , (mkName "typ", toSwiftECxt field)- ]- )- []- _ -> throwError $ NotANewtype typName---- | Make a void type (empty enum)-mkVoid :: ()- => Name- -- ^ type name- -> [Type]- -- ^ type variables- -> [Exp]- -- ^ tags- -> ShwiftyM Match-mkVoid typName instTys ts = do- let tyVars = prettyTyVars instTys- lift $ match- (conP 'Proxy [])- (normalB- $ pure- $ RecConE 'SwiftEnum- $ [ (mkName "name", unqualName typName)- , (mkName "tyVars", tyVars)- , (mkName "protocols", ListE [])- , (mkName "cases", ListE [])- , (mkName "rawValue", ConE 'Nothing)- , (mkName "tags", ListE ts)- ]- )- []---- | Make a single-constructor product (struct)-mkProd :: ()- => Options- -- ^ encoding options- -> Name- -- ^ type name- -> [Type]- -- ^ type variables- -> [Exp]- -- ^ tags- -> ConstructorInfo- -- ^ constructor info- -> ShwiftyM Match-mkProd o@Options{dataProtocols} typName instTys ts = \case- -- single constructor, no fields- ConstructorInfo- { constructorVariant = NormalConstructor- , constructorFields = []- , ..- } -> do- let tyVars = prettyTyVars instTys- let protos = map (ConE . mkName . show) dataProtocols- let tags = ListE ts- lift $ match- (conP 'Proxy [])- (normalB- $ pure- $ RecConE 'SwiftStruct- $ [ (mkName "name", unqualName typName)- , (mkName "tyVars", tyVars)- , (mkName "protocols", ListE protos)- , (mkName "fields", ListE [])- , (mkName "tags", tags)- ]- )- []- -- single constructor, non-record (Normal)- ConstructorInfo- { constructorVariant = NormalConstructor- , constructorName = name- } -> do- throwError $ SingleConNonRecord name- -- single constructor, non-record (Infix)- ConstructorInfo- { constructorVariant = InfixConstructor- , constructorName = name- } -> do- throwError $ EncounteredInfixConstructor name- -- single constructor, record- ConstructorInfo- { constructorVariant = RecordConstructor fieldNames- , ..- } -> do- let tyVars = prettyTyVars instTys- let protos = map (ConE . mkName . show) dataProtocols- let fields = ListE $ zipWith (prettyField o) fieldNames constructorFields- let tags = ListE ts- lift $ match- (conP 'Proxy [])- (normalB- $ pure- $ RecConE 'SwiftStruct- $ [ (mkName "name", unqualName typName)- , (mkName "tyVars", tyVars)- , (mkName "protocols", ListE protos)- , (mkName "fields", fields)- , (mkName "tags", tags)- ]- )- []---- turn a field name into a swift case name.--- examples:------ data Foo = A | B | C--- =>--- enum Foo {--- case a--- case b--- case c--- }------ data Bar a = MkBar1 a | MkBar2--- =>--- enum Bar<A> {--- case mkBar1(A)--- case mkBar2--- }-caseName :: Options -> Name -> Exp-caseName Options{..} = id- . stringE- . onHeadWith lowerFirstCase- . constructorModifier- . TS.unpack- . last- . TS.splitOn "."- . TS.pack- . show---- remove qualifiers from a name, turn into String-nameStr :: Name -> String-nameStr = TS.unpack . last . TS.splitOn "." . TS.pack . show---- remove qualifiers from a name, turn into Exp-unqualName :: Name -> Exp-unqualName = stringE . nameStr---- prettify a type variable as an Exp-prettyTyVar :: Name -> Exp-prettyTyVar = stringE . map Char.toUpper . TS.unpack . head . TS.splitOn "_" . last . TS.splitOn "." . TS.pack . show---- prettify a bunch of type variables as an Exp-prettyTyVars :: [Type] -> Exp-prettyTyVars = ListE . map prettyTyVar . getTyVars---- get the free type variables from many types-getTyVars :: [Type] -> [Name]-getTyVars = mapMaybe getFreeTyVar---- get the free type variables in a type-getFreeTyVar :: Type -> Maybe Name-getFreeTyVar = \case- VarT name -> Just name- SigT (VarT name) _kind -> Just name- _ -> Nothing---- make a struct field pretty-prettyField :: Options -> Name -> Type -> Exp-prettyField Options{..} name ty = TupE- [ (stringE (onHeadWith lowerFirstField (fieldLabelModifier (nameStr name))))- , toSwiftEPoly ty- ]---- build the instance head for a type-buildTypeInstance :: ()- => Name- -- ^ name of the type- -> ShwiftyClass- -- ^ which class instance head we are building- -> [Type]- -- ^ type variables- -> [TyVarBndr]- -- ^ the binders for our tyvars- -> DatatypeVariant- -- ^ variant (datatype, newtype, data family, newtype family)- -> ShwiftyM Type-buildTypeInstance tyConName cls varTysOrig tyVarBndrs variant = do- -- Make sure to expand through type/kind synonyms!- -- Otherwise, the eta-reduction check might get- -- tripped up over type variables in a synonym- -- that are actually dropped.- -- (See GHC Trac #11416 for a scenario where this- -- actually happened)- varTysExp <- lift $ mapM resolveTypeSynonyms varTysOrig-- -- get the kind status of all of our types.- -- we must realise them all to *.- starKindStats :: [KindStatus] <- foldlM- (\stats k -> case canRealiseKindStar k of- NotKindStar -> do- throwError $ KindVariableCannotBeRealised tyConName k- s -> pure (stats ++ [s])- ) [] varTysExp-- let -- get the names of our kind vars- kindVarNames :: [Name]- kindVarNames = flip mapMaybe starKindStats- (\case- IsKindVar n -> Just n- _ -> Nothing- )-- let- -- instantiate polykinded things to star.- varTysExpSubst :: [Type]- varTysExpSubst = map (substNamesWithKindStar kindVarNames) varTysExp-- -- the constraints needed on type variables- preds :: [Maybe Pred]- preds = map (deriveConstraint cls) varTysExpSubst-- -- We now sub all of the specialised-to-* kind- -- variable names with *, but in the original types,- -- not the synonym-expanded types. The reason we- -- do this is superficial: we want the derived- -- instance to resemble the datatype written in- -- source code as closely as possible. For example,- --- -- data family Fam a- -- newtype instance Fam String = Fam String- --- -- We'd want to generate the instance:- --- -- instance C (Fam String)- --- -- Not:- --- -- instance C (Fam [Char])- varTysOrigSubst :: [Type]- varTysOrigSubst =- map (substNamesWithKindStar kindVarNames) $ varTysOrig-- -- if we are working on a data family- -- or newtype family, we need to peel off- -- the kinds. See Note [Kind signatures in- -- derived instances]- varTysOrigSubst' :: [Type]- varTysOrigSubst' = if isDataFamily variant- then varTysOrigSubst- else map unSigT varTysOrigSubst-- -- the constraints needed on type variables- -- makes up the constraint part of the- -- instance head.- instanceCxt :: Cxt- instanceCxt = catMaybes preds-- -- the class and type in the instance head.- instanceType :: Type- instanceType = AppT (ConT (shwiftyClassName cls))- $ applyTyCon tyConName varTysOrigSubst'-- -- forall <tys>. ctx tys => Cls ty- lift $ forallT- (map tyVarBndrNoSig tyVarBndrs)- (pure instanceCxt)- (pure instanceType)---- the class we're generating an instance of-data ShwiftyClass- = ClassSwift -- ToSwift- | ClassSwiftData -- ToSwiftData---- turn a 'ShwiftyClass' into a 'Name'-shwiftyClassName :: ShwiftyClass -> Name-shwiftyClassName = \case- ClassSwift -> ''ToSwift- ClassSwiftData -> ''ToSwiftData---- derive the constraint needed on a type variable--- in order to build the instance head for a class.-deriveConstraint :: ()- => ShwiftyClass- -- ^ class name- -> Type- -- ^ type- -> Maybe Pred- -- ^ constraint on type-deriveConstraint c@ClassSwift typ- | not (isTyVar typ) = Nothing- | hasKindStar typ = Just (applyCon (shwiftyClassName c) tName)- | otherwise = Nothing- where- tName :: Name- tName = varTToName typ- varTToName = \case- VarT n -> n- SigT t _ -> varTToName t- _ -> error "Shwifty.varTToName: encountered non-type variable"-deriveConstraint ClassSwiftData _ = Nothing---- apply a type constructor to a type variable.--- this can be useful for letting the kind--- inference engine doing work for you. see--- 'toSwiftECxt' for an example of this.-applyCon :: Name -> Name -> Pred-applyCon con t = AppT (ConT con) (VarT t)---- peel off a kind signature from a Type-unSigT :: Type -> Type-unSigT = \case- SigT t _ -> t- t -> t---- is the type a type variable?-isTyVar :: Type -> Bool-isTyVar = \case- VarT _ -> True- SigT t _ -> isTyVar t- _ -> False---- does the type have kind *?-hasKindStar :: Type -> Bool-hasKindStar = \case- VarT _ -> True- SigT _ StarT -> True- _ -> False---- perform the substitution of type variables--- who have kinds which can be realised to *,--- with the same type variable where its kind--- has been turned into *-substNamesWithKindStar :: [Name] -> Type -> Type-substNamesWithKindStar ns t = foldr' (`substNameWithKind` starK) t ns- where- substNameWithKind :: Name -> Kind -> Type -> Type- substNameWithKind n k = applySubstitution (M.singleton n k)---- | The status of a kind variable w.r.t. its--- ability to be realised into *.-data KindStatus- = KindStar- -- ^ kind * (or some k which can be realised to *)- | NotKindStar- -- ^ any other kind- | IsKindVar Name- -- ^ is actually a kind variable- | IsCon Name- -- ^ is a constructor - this will typically- -- happen in a data family instance, because- -- we often have to construct a- -- FlexibleInstance. our old check for- -- canRealiseKindStar didn't check for- -- `ConT` - where this would happen.- --- -- TODO: Now i think this might need to be- -- removed in favour of something smarter.---- can we realise the type's kind to *?-canRealiseKindStar :: Type -> KindStatus-canRealiseKindStar = \case- VarT{} -> KindStar- SigT _ StarT -> KindStar- SigT _ (VarT n) -> IsKindVar n- ConT n -> IsCon n- _ -> NotKindStar---- discard the kind signature from a TyVarBndr.-tyVarBndrNoSig :: TyVarBndr -> TyVarBndr-tyVarBndrNoSig = \case- PlainTV n -> PlainTV n- KindedTV n _k -> PlainTV n---- fully applies a type constructor to its--- type variables-applyTyCon :: Name -> [Type] -> Type-applyTyCon = foldl' AppT . ConT---- Turn a String into an Exp string literal-stringE :: String -> Exp-stringE = LitE . StringL---- convert a type into a 'Ty'.--- we respect constraints here - e.g. in--- `(Swift a, Swift b) => Swift (Foo a b)`,--- we don't just fill in holes like in--- `toSwiftEPoly`, we actually turn `a`--- and `b` into `Ty`s directly. Consequently,--- the implementation is much simpler - just--- an application.------ Note the use of unSigT - see Note--- [Kind signatures in derived instances].-toSwiftECxt :: Type -> Exp-toSwiftECxt (unSigT -> typ) = AppE- (VarE 'toSwift)- (SigE (ConE 'Proxy) (AppT (ConT ''Proxy) typ))---- convert a type into a 'Ty'.--- polymorphic types do not require a 'ToSwift'--- instance, since we fill them in with 'SingSymbol'.------ We do this by stretching out a type along its--- spine, completely. we then fill in any polymorphic--- variables with 'SingSymbol', reflecting the type--- Name to a Symbol. then we compress the spine to--- get the original type. the 'ToSwift' instance for--- 'SingSymbol' gets us where we need to go.------ Note that @compress . decompress@ is not--- actually equivalent to the identity function on--- Type because of ForallT, where we discard some--- context. However, for any types we care about,--- there shouldn't be a ForallT, so this *should*--- be fine.-toSwiftEPoly :: Type -> Exp-toSwiftEPoly = \case- -- we don't need to special case VarT and SigT- VarT n- -> AppE (ConE 'Poly) (prettyTyVar n)- SigT (VarT n) _- -> AppE (ConE 'Poly) (prettyTyVar n)- typ ->- let decompressed = decompress typ- prettyName = map Char.toUpper . TS.unpack . head . TS.splitOn "_" . last . TS.splitOn "." . TS.pack . show- filledInHoles = decompressed <&>- (\case- VarT name -> AppT- (ConT ''Shwifty.SingSymbol)- (LitT (StrTyLit (prettyName name)))- SigT (VarT name) _ -> AppT- (ConT ''Shwifty.SingSymbol)- (LitT (StrTyLit (prettyName name)))- t -> t- )- typ' = compress filledInHoles- in AppE- (VarE 'toSwift)- (SigE (ConE 'Proxy) (AppT (ConT ''Proxy) typ'))--decompress :: Type -> Rose Type-decompress typ = case unapplyTy typ of- tyCon :| tyArgs -> Rose tyCon (decompress <$> tyArgs)--compress :: Rose Type -> Type-compress (Rose typ []) = typ-compress (Rose t ts) = foldl' AppT t (compress <$> ts)--unapplyTy :: Type -> NonEmpty Type-unapplyTy = NE.reverse . go- where- go = \case- AppT t1 t2 -> t2 <| go t1- SigT t _ -> go t- ForallT _ _ t -> go t- t -> t :| []---- | Types can be stretched out into a Rose tree.--- decompress will stretch a type out completely,--- in such a way that it cannot be stretched out--- further. compress will reconstruct a type from--- its stretched form.------ Also note that this is equivalent to--- Cofree NonEmpty Type.------ Examples:------ Maybe a--- =>--- AppT (ConT Maybe) (VarT a)--------- Either a b--- =>--- AppT (AppT (ConT Either) (VarT a)) (VarT b)--- =>--- Rose (ConT Either)--- [ Rose (VarT a)--- [--- ]--- , Rose (VarT b)--- [--- ]--- ]--------- Either (Maybe a) (Maybe b)--- =>--- AppT (AppT (ConT Either) (AppT (ConT Maybe) (VarT a))) (AppT (ConT Maybe) (VarT b))--- =>--- Rose (ConT Either)--- [ Rose (ConT Maybe)--- [ Rose (VarT a)--- [--- ]--- ]--- , Rose (ConT Maybe)--- [ Rose (VarT b)--- [--- ]--- ]--- ]-data Rose a = Rose a [Rose a]- deriving stock (Eq, Show)- deriving stock (Functor,Foldable,Traversable)--{--Note [Kind signatures in derived instances]-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~--It is possible to put explicit kind signatures into the derived instances, e.g.,-- instance C a => C (Data (f :: * -> *)) where ...--But it is preferable to avoid this if possible. If we come up with an incorrect-kind signature (which is entirely possible, since Template Haskell doesn't always-have the best track record with reifying kind signatures), then GHC will flat-out-reject the instance, which is quite unfortunate.--Plain old datatypes have the advantage that you can avoid using any kind signatures-at all in their instances. This is because a datatype declaration uses all type-variables, so the types that we use in a derived instance uniquely determine their-kinds. As long as we plug in the right types, the kind inferencer can do the rest-of the work. For this reason, we use unSigT to remove all kind signatures before-splicing in the instance context and head.--Data family instances are trickier, since a data family can have two instances that-are distinguished by kind alone, e.g.,-- data family Fam (a :: k)- data instance Fam (a :: * -> *)- data instance Fam (a :: *)--If we dropped the kind signatures for C (Fam a), then GHC will have no way of-knowing which instance we are talking about. To avoid this scenario, we always-include explicit kind signatures in data family instances. There is a chance that-the inferred kind signatures will be incorrect, in which case we have to write the instance manually.--}---- are we working on a data family--- or newtype family?-isDataFamily :: DatatypeVariant -> Bool-isDataFamily = \case- NewtypeInstance -> True- DataInstance -> True- _ -> False--stripConT :: [Type] -> [Type]-stripConT = mapMaybe noConT- where- noConT = \case- ConT {} -> Nothing- t -> Just t+ , DataKinds+ , DeriveFoldable+ , DeriveFunctor+ , DeriveGeneric+ , DeriveTraversable+ , DerivingStrategies+ , FlexibleInstances+ , LambdaCase+ , MultiWayIf+ , NamedFieldPuns+ , OverloadedStrings+ , RecordWildCards+ , ScopedTypeVariables+ , TemplateHaskell+ , TypeApplications+ , TypeFamilies+ , TypeOperators+ , UndecidableInstances+ , ViewPatterns+#-}++{-# options_ghc+ -Wall+ -fno-warn-duplicate-exports+#-}++-- | The Shwifty library allows generation of+-- Swift types (structs and enums) from Haskell+-- ADTs, using Template Haskell. The main+-- entry point to the library should be the+-- documentation and examples of 'getShwifty'.+-- See also 'getShwiftyWith'+-- and 'getShwiftyWithTags'.+--+-- This library is in alpha and there are a number+-- of known bugs which shouldn't affect most users.+-- See the issue tracker to see what those are.+--+-- There are probably many bugs/some weird behaviour+-- when it comes to data families. Please report+-- any issues on the issue tracker.+module Shwifty+ ( -- * Classes for conversion+ ToSwift(..)+ , ToSwiftData(..)++ -- * Generating instances+ , getShwifty+ , getShwiftyWith+ , getShwiftyWithTags++ , getShwiftyCodec+ , getShwiftyCodecTags++ -- * Types+ , Ty(..)+ , SwiftData(..)+ , Protocol(..)++ -- * Options for encoding types+ -- ** Option type+ , Options+ -- ** Actual Options+ , fieldLabelModifier+ , constructorModifier+ , optionalExpand+ , generateToSwift+ , generateToSwiftData+ , dataProtocols+ , dataRawValue+ , typeAlias+ , newtypeTag+ , lowerFirstCase+ , lowerFirstField+ , omitFields+ , omitCases+ , makeBase+ -- ** Default 'Options'+ , defaultOptions++ -- ** Codec options+ , Codec(..)+ , ModifyOptions(..)+ , AsIs+ , type (&)+ , Label(..)+ , Drop+ , DontGenerate+ , Implement+ , RawValue+ , CanBeRawValue+ , TypeAlias+ , NewtypeTag+ , DontLowercase+ , OmitField+ , OmitCase+ , MakeBase++ -- * Pretty-printing+ -- ** Functions+ , prettyTy+ , prettySwiftData+ -- ** Re-exports+ , X+ ) where++import Control.Monad.Except+import Data.Foldable (foldlM,foldr',foldl')+import Data.Functor ((<&>))+import Data.List.NonEmpty ((<|), NonEmpty(..))+import Data.Maybe (mapMaybe, catMaybes)+import Data.Proxy (Proxy(..))+import Data.Void (Void)+import GHC.TypeLits (Symbol, KnownSymbol, symbolVal)+import Language.Haskell.TH hiding (stringE)+import Language.Haskell.TH.Datatype+import Prelude hiding (Enum(..))+import qualified Data.Char as Char+import qualified Data.List as L+import qualified Data.List.NonEmpty as NE+import qualified Data.Map as M+import qualified Data.Text as TS++import Shwifty.Class+import Shwifty.Codec+import Shwifty.Pretty+import Shwifty.Types++-- | The default 'Options'.+--+-- @+-- defaultOptions :: Options+-- defaultOptions = Options+-- { typeConstructorModifier = id+-- , fieldLabelModifier = id+-- , constructorModifier = id+-- , optionalExpand= False+-- , generateToSwift = True+-- , generateToSwiftData = True+-- , dataProtocols = []+-- , dataRawValue = Nothing+-- , typeAlias = False+-- , newtypeTag = False+-- , lowerFirstField = True+-- , lowerFirstCase = True+-- , omitFields = []+-- , omitCases = []+-- , makeBase = (False, Nothing, [])+-- }+-- @+--+defaultOptions :: Options+defaultOptions = Options+ { typeConstructorModifier = id+ , fieldLabelModifier = id+ , constructorModifier = id+ , optionalExpand = False+ , generateToSwift = True+ , generateToSwiftData = True+ , dataProtocols = []+ , dataRawValue = Nothing+ , typeAlias = False+ , newtypeTag = False+ , lowerFirstField = True+ , lowerFirstCase = True+ , omitFields = []+ , omitCases = []+ , makeBase = (False, Nothing, [])+ }++-- Used internally to reflect polymorphic type+-- variables into TH, then reify them into 'Poly'.+--+-- See the Rose tree section below+data SingSymbol (x :: Symbol)+instance KnownSymbol x => ToSwift (SingSymbol x) where+ toSwift _ = Poly (symbolVal (Proxy @x))++-- | A filler type to be used when pretty-printing.+-- The codegen used by shwifty doesn't look at+-- at what a type's type variables are instantiated+-- to, but rather at the type's top-level+-- definition. However,+-- to make GHC happy, you will have to fill in type+-- variables with unused types. To get around this,+-- you could also use something like+-- `-XQuantifiedConstraints`, or existential types,+-- but we leave that to the user to handle.+type X = Void++ensureEnabled :: Extension -> ShwiftyM ()+ensureEnabled ext = do+ enabled <- lift $ isExtEnabled ext+ unless enabled $ do+ throwError $ ExtensionNotEnabled ext++-- | Generate 'ToSwiftData' and 'ToSwift' instances+-- for your type. 'ToSwift' instances are typically+-- used to build cases or fields, whereas+-- 'ToSwiftData' instances are for building structs+-- and enums. Click the @Examples@ button to see+-- examples of what Swift gets generated in+-- different scenarios. To get access to the+-- generated code, you will have to use one of+-- the pretty-printing functions provided.+--+-- === __Examples__+--+-- > -- A simple sum type+-- > data SumType = Sum1 | Sum2 | Sum3+-- > getShwifty ''SumType+--+-- @+-- enum SumType {+-- case sum1+-- case sum2+-- case sum3+-- }+-- @+--+-- > -- A simple product type+-- > data ProductType = ProductType { x :: Int, y :: Int }+-- > getShwifty ''ProductType+--+-- @+-- struct ProductType {+-- let x: Int+-- let y: Int+-- }+-- @+--+-- > -- A sum type with type variables+-- > data SumType a b = SumL a | SumR b+-- > getShwifty ''SumType+--+-- @+-- enum SumType\<A, B\> {+-- case sumL(A)+-- case sumR(B)+-- }+-- @+--+-- > -- A product type with type variables+-- > data ProductType a b = ProductType { aField :: a, bField :: b }+-- > getShwifty ''ProductType+--+-- @+-- struct ProductType\<A, B\> {+-- let aField: A+-- let bField: B+-- }+-- @+--+-- > -- A newtype+-- > newtype Newtype a = Newtype { getNewtype :: a }+-- > getShwifty ''Newtype+--+-- @+-- struct Newtype\<A\> {+-- let getNewtype: A+-- }+-- @+--+-- > -- A type with a function field+-- > newtype Endo a = Endo { appEndo :: a -> a }+-- > getShwifty ''Endo+--+-- @+-- struct Endo\<A\> {+-- let appEndo: ((A) -> A)+-- }+-- @+--+-- > -- A type with a kookier function field+-- > newtype Fun a = Fun { fun :: Int -> Char -> Bool -> String -> Maybe a }+-- > getShwifty ''Fun+--+-- @+-- struct Fun<A> {+-- let fun: ((Int, Char, Bool, String) -> A?)+-- }+-- @+--+-- > -- A weird type with nested fields. Also note the Result's types being flipped from that of the Either.+-- > data YouveGotProblems a b = YouveGotProblems { field1 :: Maybe (Maybe (Maybe a)), field2 :: Either (Maybe a) (Maybe b) }+-- > getShwifty ''YouveGotProblems+--+-- @+-- struct YouveGotProblems\<A, B\> {+-- let field1: Option\<Option\<Option\<A\>\>\>+-- let field2: Result\<Option\<B\>,Option\<A\>\>+-- }+-- @+--+-- > -- A type with polykinded type variables+-- > -- Also note that there is no newline because+-- > -- of the absence of fields+-- > data PolyKinded (a :: k) = PolyKinded+-- > getShwifty ''PolyKinded+--+-- @+-- struct PolyKinded\<A\> { }+-- @+--+-- > -- A sum type where constructors might be records+-- > data SumType a b (c :: k) = Sum1 Int a (Maybe b) | Sum2 b | Sum3 { x :: Int, y :: Int }+-- > getShwifty ''SumType+--+-- @+-- enum SumType\<A, B, C\> {+-- case field1(Int, A, Optional\<B\>)+-- case field2(B)+-- case field3(_ x: Int, _ y: Int)+-- }+-- @+--+-- > -- A type containing another type with instance generated by 'getShwifty'+-- > newtype MyFirstType a = MyFirstType { getMyFirstType :: a }+-- > getShwifty ''MyFirstType+-- >+-- > data Contains a = Contains { x :: MyFirstType Int, y :: MyFirstType a }+-- > getShwifty ''Contains+--+-- @+-- struct MyFirstType\<A\> {+-- let getMyFirstType: A+-- }+--+-- struct Contains\<A\> {+-- let x: MyFirstType\<Int\>+-- let y: MyFirstType\<A\>+-- }+-- @+getShwifty :: Name -> Q [Dec]+getShwifty = getShwiftyWith defaultOptions++-- | Like 'getShwifty', but lets you supply+-- your own 'Options'. Click the examples+-- for some clarification of what you can do.+--+-- === __Examples__+--+-- > data PrefixedFields = MkPrefixedFields { prefixedFieldsX :: Int, prefixedFieldsY :: Int }+-- > $(getShwiftyWith (defaultOptions { fieldLabelModifier = drop (length "PrefixedFields") }) ''PrefixedFields)+--+-- @+-- struct PrefixedFields {+-- let x: Int+-- let y: Int+-- }+-- @+--+-- > data PrefixedCons = MkPrefixedConsLeft | MkPrefixedConsRight+-- > $(getShwiftyWith (defaultOptions { constructorModifier = drop (length "MkPrefixedCons"), dataProtocols = [Codable] }) ''PrefixedCons)+--+-- @+-- enum PrefixedCons: Codable {+-- case left+-- case right+-- }+-- @+getShwiftyWith :: Options -> Name -> Q [Dec]+getShwiftyWith o n = getShwiftyWithTags o [] n++data NewtypeInfo = NewtypeInfo+ { newtypeName :: Name+ -- ^ Type constructor+ , newtypeVars :: [TyVarBndr]+ -- ^ Type parameters+ , newtypeInstTypes :: [Type]+ -- ^ Argument types+ , newtypeVariant :: DatatypeVariant+ -- ^ Whether or not the type is a+ -- newtype or newtype instance+ , newtypeCon :: ConstructorInfo+ }++-- | Reify a newtype.+reifyNewtype :: Name -> ShwiftyM NewtypeInfo+reifyNewtype n = do+ DatatypeInfo{..} <- lift $ reifyDatatype n+ case (datatypeCons, datatypeVariant) of+ ([c], Newtype) -> do+ pure NewtypeInfo {+ newtypeName = datatypeName+ , newtypeVars = datatypeVars+ , newtypeInstTypes = datatypeInstTypes+ , newtypeVariant = datatypeVariant+ , newtypeCon = c+ }+ ([c], NewtypeInstance) -> do+ pure NewtypeInfo {+ newtypeName = datatypeName+ , newtypeVars = datatypeVars+ , newtypeInstTypes = datatypeInstTypes+ , newtypeVariant = datatypeVariant+ , newtypeCon = c+ }+ _ -> do+ throwError $ NotANewtype n++-- Generate the tags for a type.+-- Also generate the ToSwift instance for each tag+-- type. We can't just expect people to do this+-- with a separate 'getShwifty' call, because+-- they will generate the wrong code, since other+-- types with a tag that isn't theirs won't generate+-- well-scoped fields.+getTags :: ()+ => Name+ -- ^ name of parent type+ -> [Name]+ -- ^ tags+ -> ShwiftyM ([Exp], [Dec])+getTags parentName ts = do+ let b = length ts > 1+ disambiguate <- lift $ [||b||]+ tags <- foldlM+ (\(es,ds) n -> do++ NewtypeInfo{..} <- reifyNewtype n+ let ConstructorInfo{..} = newtypeCon++ -- generate the tag+ let tyconName = case newtypeVariant of+ NewtypeInstance -> constructorName+ _ -> newtypeName+ typ <- case constructorFields of+ [ty] -> pure ty+ _ -> throwError $ NotANewtype newtypeName+ let tag = RecConE 'Tag+ [ (mkName "tagName", unqualName tyconName)+ , (mkName "tagParent", unqualName parentName)+ , (mkName "tagTyp", toSwiftEPoly typ)+ , (mkName "tagDisambiguate", unType disambiguate)+ ]++ -- generate the instance+ !instHeadTy+ <- buildTypeInstance newtypeName ClassSwift newtypeInstTypes newtypeVars newtypeVariant+ -- we do not want to strip here+ clauseTy <- tagToSwift tyconName typ parentName+ swiftTyInst <- lift $ instanceD+ (pure [])+ (pure instHeadTy)+ [ funD 'toSwift+ [ clause [] (normalB (pure clauseTy)) []+ ]+ ]++ pure $ (es ++ [tag], ds ++ [swiftTyInst])+ ) ([], []) ts+ pure tags++getToSwift :: ()+ => Options+ -- ^ options+ -> Name+ -- ^ type name+ -> [Type]+ -- ^ type variables+ -> [TyVarBndr]+ -- ^ type binders+ -> DatatypeVariant+ -- ^ type variant+ -> [ConstructorInfo]+ -- ^ constructors+ -> ShwiftyM [Dec]+getToSwift Options{..} parentName instTys tyVarBndrs variant cons = if generateToSwift+ then do+ instHead <- buildTypeInstance parentName ClassSwift instTys tyVarBndrs variant+ clauseTy <- case variant of+ NewtypeInstance -> case cons of+ [ConstructorInfo{..}] -> do+ newtypToSwift constructorName instTys+ _ -> do+ throwError ExpectedNewtypeInstance+ _ -> do+ typToSwift newtypeTag parentName instTys+ inst <- lift $ instanceD+ (pure [])+ (pure instHead)+ [ funD 'toSwift+ [ clause [] (normalB (pure clauseTy)) []+ ]+ ]+ pure [inst]+ else do+ pure []++getToSwiftData :: ()+ => Options+ -- ^ options+ -> Name+ -- ^ type name+ -> [Type]+ -- ^ type variables+ -> [TyVarBndr]+ -- ^ type binders+ -> DatatypeVariant+ -- ^ type variant+ -> [Exp]+ -- ^ tags+ -> [ConstructorInfo]+ -- ^ constructors+ -> ShwiftyM [Dec]+getToSwiftData o@Options{..} parentName instTys tyVarBndrs variant tags cons = if generateToSwiftData+ then do+ instHead <- buildTypeInstance parentName ClassSwiftData instTys tyVarBndrs variant+ clauseData <- consToSwift o parentName instTys variant tags makeBase cons+ inst <- lift $ instanceD+ (pure [])+ (pure instHead)+ [ funD 'toSwiftData+ [ clause [] (normalB (pure clauseData)) []+ ]+ ]+ pure [inst]+ else do+ pure []++-- | Like 'getShwiftyWith', but lets you supply+-- tags. Tags are type-safe typealiases that+-- are akin to newtypes in Haskell. The+-- introduction of a struct around something+-- which is, say, a UUID in Swift means that+-- the default Codable instance will not work+-- correctly. So we introduce a tag(s). See the+-- examples to see how this looks. Also, see+-- https://github.com/pointfreeco/swift-tagged,+-- the library which these tags use. The library+-- is not included in any generated code.+--+-- === __Examples__+--+-- > -- Example of using the swift-tagged library:+-- > -- A type containing a database key+-- > data User = User { id :: UserId, name :: Text }+-- > -- the user key+-- > newtype UserId = UserId UUID+-- > $(getShwiftyWithTags defaultOptions [ ''UserId ] ''User)+-- > -- A type that also contains the UserId+-- > data UserDetails = UserDetails { id :: UserId, lastName :: Text }+-- > getShwifty ''UserDetails+--+-- @+-- struct User {+-- let id: UserId+-- let name: String+--+-- typealias UserId = Tagged\<User,UUID\>+-- }+--+-- struct UserDetails {+-- let id: User.UserId+-- let lastName: String+-- }+-- @+--+-- > -- Example type with multiple tags+-- > newtype Name = MkName String+-- > newtype Email = MkEmail String+-- > data Person = Person { name :: Name, email :: Email }+-- > $(getShwiftyWithTags defaultOptions [ ''Name, ''Email ] ''Person)+--+-- @+-- struct Person {+-- let name: Name+-- let email: Email+--+-- enum NameTag {}+-- typealias Name = Tagged\<NameTag, String\>+--+-- enum EmailTag {}+-- typealias Email = Tagged\<EmailTag, String\>+-- }+-- @+getShwiftyWithTags :: ()+ => Options+ -> [Name]+ -> Name+ -> Q [Dec]+getShwiftyWithTags o@Options{..} ts name = do+ r <- runExceptT $ do+ ensureEnabled ScopedTypeVariables+ ensureEnabled DataKinds+ DatatypeInfo+ { datatypeName = parentName+ , datatypeVars = tyVarBndrs+ , datatypeInstTypes = instTys+ , datatypeVariant = variant+ , datatypeCons = cons+ } <- lift $ reifyDatatype name+ noExistentials cons++ -- get tags/ToSwift instances for tags+ (tags, extraDecs) <- getTags parentName ts++ swiftDataInst <- getToSwiftData o parentName instTys tyVarBndrs variant tags cons++ swiftTyInst <- getToSwift o parentName instTys tyVarBndrs variant cons+ pure $ swiftDataInst ++ swiftTyInst ++ extraDecs+ case r of+ Left e -> fail $ prettyShwiftyError e+ Right d -> pure d++noExistentials :: [ConstructorInfo] -> ShwiftyM ()+noExistentials cs = forM_ cs $ \ConstructorInfo{..} ->+ case (constructorName, constructorVars) of+ (_, []) -> do+ pure ()+ (cn, cvs) -> do+ throwError $ ExistentialTypes cn cvs++data ShwiftyError+ = SingleConNonRecord+ { _conName :: Name+ }+ | EncounteredInfixConstructor+ { _conName :: Name+ }+ | KindVariableCannotBeRealised+ { _typName :: Name+ , _kind :: Kind+ }+ | ExtensionNotEnabled+ { _ext :: Extension+ }+ | ExistentialTypes+ { _conName :: Name+ , _types :: [TyVarBndr]+ }+ | ExpectedNewtypeInstance+ | NotANewtype+ { _typName :: Name+ }++prettyShwiftyError :: ShwiftyError -> String+prettyShwiftyError = \case+ SingleConNonRecord (nameStr -> n) -> mempty+ ++ n+ ++ ": Cannot get shwifty with single-constructor "+ ++ "non-record types. This is due to a "+ ++ "restriction of Swift that prohibits structs "+ ++ "from not having named fields. Try turning "+ ++ n ++ " into a record!"+ EncounteredInfixConstructor (nameStr -> n) -> mempty+ ++ n+ ++ ": Cannot get shwifty with infix constructors. "+ ++ "Swift doesn't support them. Try changing "+ ++ n ++ " into a prefix constructor!"+ KindVariableCannotBeRealised (nameStr -> n) typ ->+ let (typStr, kindStr) = prettyKindVar typ+ in mempty+ ++ n+ ++ ": Encountered a type variable ("+ ++ typStr+ ++ ") with a kind ("+ ++ kindStr+ ++ ") that can't "+ ++ "get shwifty! Shwifty needs to be able "+ ++ "to realise your kind variables to `*`, "+ ++ "since that's all that makes sense in "+ ++ "Swift. The only kinds that can happen with "+ ++ "are `*` and the free-est kind, `k`."+ ExtensionNotEnabled ext -> mempty+ ++ show ext+ ++ " is not enabled. Shwifty needs it to work!"+ -- TODO: make this not print out implicit kinds.+ -- e.g. for `data Ex = forall x. Ex x`, there are+ -- no implicit `TyVarBndr`s, but for+ -- `data Ex = forall x y z. Ex x`, there are two:+ -- the kinds inferred by `y` and `z` are both `k`.+ -- We print these out - this could be confusing to+ -- the end user. I'm not immediately certain how to+ -- be rid of them.+ ExistentialTypes (nameStr -> n) tys -> mempty+ ++ n+ ++ " has existential type variables ("+ ++ L.intercalate ", " (map prettyTyVarBndrStr tys)+ ++ ")! Shwifty doesn't support these."+ ExpectedNewtypeInstance -> mempty+ ++ "Expected a newtype instance. This is an "+ ++ "internal logic error. Please report it as a "+ ++ "bug."+ NotANewtype (nameStr -> n) -> mempty+ ++ n+ ++ " is not a newtype. This is an internal logic "+ ++ "error. Please report it as a bug."++prettyTyVarBndrStr :: TyVarBndr -> String+prettyTyVarBndrStr = \case+ PlainTV n -> go n+ KindedTV n _ -> go n+ where+ go = TS.unpack . head . TS.splitOn "_" . last . TS.splitOn "." . TS.pack . show++-- prettify the type and kind.+prettyKindVar :: Type -> (String, String)+prettyKindVar = \case+ SigT typ k -> (go typ, go k)+ VarT n -> (nameStr n, "*")+ typ -> error $ "Shwifty.prettyKindVar: used on a type without a kind signature. Type was: " ++ show typ+ where+ go = TS.unpack . head . TS.splitOn "_" . last . TS.splitOn "." . TS.pack . show . ppr++type ShwiftyM = ExceptT ShwiftyError Q++tagToSwift :: ()+ => Name+ -- ^ name of the type constructor+ -> Type+ -- ^ type variables+ -> Name+ -- ^ parent name+ -> ShwiftyM Exp+tagToSwift tyconName typ parentName = do+ -- TODO: use '_' instead of matching+ value <- lift $ newName "value"+ ourMatch <- matchProxy+ $ tagExp tyconName parentName typ False+ let matches = [pure ourMatch]+ lift $ lamE [varP value] (caseE (varE value) matches)+newtypToSwift :: ()+ => Name+ -- ^ name of the constructor+ -> [Type]+ -- ^ type variables+ -> ShwiftyM Exp+newtypToSwift conName (stripConT -> instTys) = do+ typToSwift False conName instTys++typToSwift :: ()+ => Bool+ -- ^ is this a newtype tag?+ -> Name+ -- ^ name of the type+ -> [Type]+ -- ^ type variables+ -> ShwiftyM Exp+typToSwift newtypeTag parentName instTys = do+ -- TODO: use '_' instead of matching+ value <- lift $ newName "value"+ let tyVars = map toSwiftECxt instTys+ let name =+ let parentStr = nameStr parentName+ accessedName = if newtypeTag+ then parentStr ++ "Tag." ++ parentStr+ else parentStr+ in stringE accessedName+ ourMatch <- matchProxy+ $ RecConE 'Concrete+ $ [ (mkName "concreteName", name)+ , (mkName "concreteTyVars", ListE tyVars)+ ]+ let matches = [pure ourMatch]+ lift $ lamE [varP value] (caseE (varE value) matches)++rawValueE :: Maybe Ty -> Exp+rawValueE = \case+ Nothing -> ConE 'Nothing+ Just ty -> AppE (ConE 'Just) (ParensE (tyE ty))++-- god this is annoying. write a cleaner+-- version of this+tyE :: Ty -> Exp+tyE = \case+ Unit -> ConE 'Unit+ Bool -> ConE 'Bool+ Character -> ConE 'Character+ Str -> ConE 'Str+ I -> ConE 'I+ I8 -> ConE 'I8+ I16 -> ConE 'I16+ I32 -> ConE 'I32+ I64 -> ConE 'I64+ U -> ConE 'U+ U8 -> ConE 'U8+ U16 -> ConE 'U16+ U32 -> ConE 'U32+ U64 -> ConE 'U64+ F32 -> ConE 'F32+ F64 -> ConE 'F64+ Decimal -> ConE 'Decimal+ BigSInt32 -> ConE 'BigSInt32+ BigSInt64 -> ConE 'BigSInt64+ Poly s -> AppE (ConE 'Poly) (stringE s)+ Concrete tyCon tyVars -> AppE (AppE (ConE 'Concrete) (stringE tyCon)) (ListE (map tyE tyVars))+ Tuple2 e1 e2 -> AppE (AppE (ConE 'Tuple2) (tyE e1)) (tyE e2)+ Tuple3 e1 e2 e3 -> AppE (AppE (AppE (ConE 'Tuple3) (tyE e1)) (tyE e2)) (tyE e3)+ Optional e -> AppE (ConE 'Optional) (tyE e)+ Result e1 e2 -> AppE (AppE (ConE 'Result) (tyE e1)) (tyE e2)+ Set e -> AppE (ConE 'Set) (tyE e)+ Dictionary e1 e2 -> AppE (AppE (ConE 'Dictionary) (tyE e1)) (tyE e2)+ App e1 e2 -> AppE (AppE (ConE 'App) (tyE e1)) (tyE e2)+ Array e -> AppE (ConE 'Array) (tyE e)+ Tag{..} -> AppE (AppE (AppE (AppE (ConE 'Tag) (stringE tagName)) (stringE tagParent)) (tyE tagTyp)) (if tagDisambiguate then ConE 'True else ConE 'False)++consToSwift :: ()+ => Options+ -- ^ options about how to encode things+ -> Name+ -- ^ name of type+ -> [Type]+ -- ^ type variables+ -> DatatypeVariant+ -- ^ data type variant+ -> [Exp]+ -- ^ tags+ -> (Bool, Maybe Ty, [Protocol])+ -- ^ Make base?+ -> [ConstructorInfo]+ -- ^ constructors+ -> ShwiftyM Exp+consToSwift o@Options{..} parentName instTys variant ts bs = \case+ [] -> do+ value <- lift $ newName "value"+ matches <- liftCons (mkVoid parentName instTys ts)+ lift $ lamE [varP value] (caseE (varE value) matches)+ cons -> do+ -- TODO: use '_' instead of matching+ value <- lift $ newName "value"+ matches <- matchesWorker+ lift $ lamE [varP value] (caseE (varE value) matches)+ where+ -- bad name+ matchesWorker :: ShwiftyM [Q Match]+ matchesWorker = case cons of+ [con] -> liftCons $ do+ case variant of+ NewtypeInstance -> do+ if | typeAlias -> do+ mkNewtypeInstanceAlias instTys con+ | otherwise -> do+ mkNewtypeInstance o instTys ts con+ Newtype -> do+ if | newtypeTag -> do+ mkTypeTag o parentName instTys con+ | typeAlias -> do+ mkTypeAlias parentName instTys con+ | otherwise -> do+ mkProd o parentName instTys ts con+ _ -> do+ mkProd o parentName instTys ts con+ _ -> do+ -- omit the cases we don't want+ let cons' = flip filter cons $ \ConstructorInfo{..} -> not (nameStr constructorName `elem` omitCases)+ cases <- forM cons' (liftEither . mkCase o)+ ourMatch <- matchProxy+ $ enumExp parentName instTys dataProtocols cases dataRawValue ts bs+ pure [pure ourMatch]++liftCons :: (Functor f, Applicative g) => f a -> f ([g a])+liftCons x = ((:[]) . pure) <$> x++-- Create the case (String, [(Maybe String, Ty)])+mkCaseHelper :: Options -> Name -> [Exp] -> Exp+mkCaseHelper o name es = TupE [ caseName o name, ListE es ]++mkCase :: ()+ => Options+ -> ConstructorInfo+ -> Either ShwiftyError Exp+mkCase o = \case+ -- non-record+ ConstructorInfo+ { constructorVariant = NormalConstructor+ , constructorName = name+ , constructorFields = fields+ , ..+ } -> Right $ mkCaseHelper o name $ fields <&>+ (\typ -> TupE+ [ ConE 'Nothing+ , toSwiftEPoly typ+ ]+ )+ ConstructorInfo+ { constructorVariant = InfixConstructor+ , constructorName = name+ , ..+ } -> Left $ EncounteredInfixConstructor name+ -- records+ -- we turn names into labels+ ConstructorInfo+ { constructorVariant = RecordConstructor fieldNames+ , constructorName = name+ , constructorFields = fields+ , ..+ } ->+ let cases = zipWith (caseField o) fieldNames fields+ in Right $ mkCaseHelper o name cases++caseField :: Options -> Name -> Type -> Exp+caseField o n typ = TupE+ [ mkLabel o n+ , toSwiftEPoly typ+ ]++onHeadWith :: Bool -> String -> String+onHeadWith toLower = if toLower+ then onHead Char.toLower+ else id++-- apply a function only to the head of a string+onHead :: (Char -> Char) -> String -> String+onHead f = \case { [] -> []; (x:xs) -> f x : xs }++mkLabel :: Options -> Name -> Exp+mkLabel Options{..} = AppE (ConE 'Just)+ . stringE+ . fieldLabelModifier+ . onHeadWith lowerFirstField+ . TS.unpack+ . last+ . TS.splitOn "."+ . TS.pack+ . show++mkNewtypeInstanceAlias :: ()+ => [Type]+ -- ^ type variables+ -> ConstructorInfo+ -- ^ constructor info+ -> ShwiftyM Match+mkNewtypeInstanceAlias (stripConT -> instTys) = \case+ ConstructorInfo+ { constructorName = conName+ , constructorFields = [field]+ , ..+ } -> do+ lift $ match+ (conP 'Proxy [])+ (normalB+ (pure+ (aliasExp conName instTys field)))+ []+ _ -> throwError $ ExpectedNewtypeInstance++mkNewtypeInstance :: ()+ => Options+ -- ^ encoding options+ -> [Type]+ -- ^ type variables+ -> [Exp]+ -- ^ tags+ -> ConstructorInfo+ -- ^ constructor info+ -> ShwiftyM Match+mkNewtypeInstance o@Options{..} (stripConT -> instTys) ts = \case+ ConstructorInfo+ { constructorVariant = RecordConstructor [fieldName]+ , constructorFields = [field]+ , ..+ } -> do+ let fields = [prettyField o fieldName field]+ matchProxy $ structExp constructorName instTys dataProtocols fields ts makeBase+ _ -> throwError ExpectedNewtypeInstance++-- make a newtype into an empty enum+-- with a tag+mkTypeTag :: ()+ => Options+ -- ^ options+ -> Name+ -- ^ type name+ -> [Type]+ -- ^ type variables+ -> ConstructorInfo+ -- ^ constructor info+ -> ShwiftyM Match+mkTypeTag Options{..} typName instTys = \case+ ConstructorInfo+ { constructorFields = [field]+ , ..+ } -> do+ let parentName = mkName+ (nameStr typName ++ "Tag")+ let tag = tagExp typName parentName field False+ matchProxy $ enumExp parentName instTys dataProtocols [] dataRawValue [tag] (False, Nothing, [])++ _ -> throwError $ NotANewtype typName++-- make a newtype into a type alias+mkTypeAlias :: ()+ => Name+ -- ^ type name+ -> [Type]+ -- ^ type variables+ -> ConstructorInfo+ -- ^ constructor info+ -> ShwiftyM Match+mkTypeAlias typName instTys = \case+ ConstructorInfo+ { constructorFields = [field]+ , ..+ } -> do+ lift $ match+ (conP 'Proxy [])+ (normalB+ (pure (aliasExp typName instTys field)))+ []+ _ -> throwError $ NotANewtype typName++-- | Make a void type (empty enum)+mkVoid :: ()+ => Name+ -- ^ type name+ -> [Type]+ -- ^ type variables+ -> [Exp]+ -- ^ tags+ -> ShwiftyM Match+mkVoid typName instTys ts = matchProxy+ $ enumExp typName instTys [] [] Nothing ts (False, Nothing, [])++-- | Make a single-constructor product (struct)+mkProd :: ()+ => Options+ -- ^ encoding options+ -> Name+ -- ^ type name+ -> [Type]+ -- ^ type variables+ -> [Exp]+ -- ^ tags+ -> ConstructorInfo+ -- ^ constructor info+ -> ShwiftyM Match+mkProd o@Options{..} typName instTys ts = \case+ -- single constructor, no fields+ ConstructorInfo+ { constructorVariant = NormalConstructor+ , constructorFields = []+ , ..+ } -> do+ matchProxy $ structExp typName instTys dataProtocols [] ts makeBase+ -- single constructor, non-record (Normal)+ ConstructorInfo+ { constructorVariant = NormalConstructor+ , constructorName = name+ } -> do+ throwError $ SingleConNonRecord name+ -- single constructor, non-record (Infix)+ ConstructorInfo+ { constructorVariant = InfixConstructor+ , constructorName = name+ } -> do+ throwError $ EncounteredInfixConstructor name+ -- single constructor, record+ ConstructorInfo+ { constructorVariant = RecordConstructor fieldNames+ , ..+ } -> do+ let fields = zipFields o fieldNames constructorFields+ matchProxy $ structExp typName instTys dataProtocols fields ts makeBase++zipFields :: Options -> [Name] -> [Type] -> [Exp]+zipFields o = zipWithPred p (prettyField o)+ where+ p :: Name -> Type -> Bool+ p n _ = not $ nameStr n `elem` omitFields o++zipWithPred :: (a -> b -> Bool) -> (a -> b -> c) -> [a] -> [b] -> [c]+zipWithPred _ _ [] _ = []+zipWithPred _ _ _ [] = []+zipWithPred p f (x:xs) (y:ys)+ | p x y = f x y : zipWithPred p f xs ys+ | otherwise = zipWithPred p f xs ys++-- turn a field name into a swift case name.+-- examples:+--+-- data Foo = A | B | C+-- =>+-- enum Foo {+-- case a+-- case b+-- case c+-- }+--+-- data Bar a = MkBar1 a | MkBar2+-- =>+-- enum Bar<A> {+-- case mkBar1(A)+-- case mkBar2+-- }+caseName :: Options -> Name -> Exp+caseName Options{..} = id+ . stringE+ . onHeadWith lowerFirstCase+ . constructorModifier+ . TS.unpack+ . last+ . TS.splitOn "."+ . TS.pack+ . show++-- remove qualifiers from a name, turn into String+nameStr :: Name -> String+nameStr = TS.unpack . last . TS.splitOn "." . TS.pack . show++-- remove qualifiers from a name, turn into Exp+unqualName :: Name -> Exp+unqualName = stringE . nameStr++-- prettify a type variable as an Exp+prettyTyVar :: Name -> Exp+prettyTyVar = stringE . map Char.toUpper . TS.unpack . head . TS.splitOn "_" . last . TS.splitOn "." . TS.pack . show++-- prettify a bunch of type variables as an Exp+prettyTyVars :: [Type] -> Exp+prettyTyVars = ListE . map prettyTyVar . getTyVars++-- get the free type variables from many types+getTyVars :: [Type] -> [Name]+getTyVars = mapMaybe getFreeTyVar++-- get the free type variables in a type+getFreeTyVar :: Type -> Maybe Name+getFreeTyVar = \case+ VarT name -> Just name+ SigT (VarT name) _kind -> Just name+ _ -> Nothing++-- make a struct field pretty+prettyField :: Options -> Name -> Type -> Exp+prettyField Options{..} name ty = TupE+ [ (stringE (onHeadWith lowerFirstField (fieldLabelModifier (nameStr name))))+ , toSwiftEPoly ty+ ]++-- build the instance head for a type+buildTypeInstance :: ()+ => Name+ -- ^ name of the type+ -> ShwiftyClass+ -- ^ which class instance head we are building+ -> [Type]+ -- ^ type variables+ -> [TyVarBndr]+ -- ^ the binders for our tyvars+ -> DatatypeVariant+ -- ^ variant (datatype, newtype, data family, newtype family)+ -> ShwiftyM Type+buildTypeInstance tyConName cls varTysOrig tyVarBndrs variant = do+ -- Make sure to expand through type/kind synonyms!+ -- Otherwise, the eta-reduction check might get+ -- tripped up over type variables in a synonym+ -- that are actually dropped.+ -- (See GHC Trac #11416 for a scenario where this+ -- actually happened)+ varTysExp <- lift $ mapM resolveTypeSynonyms varTysOrig++ -- get the kind status of all of our types.+ -- we must realise them all to *.+ starKindStats :: [KindStatus] <- foldlM+ (\stats k -> case canRealiseKindStar k of+ NotKindStar -> do+ throwError $ KindVariableCannotBeRealised tyConName k+ s -> pure (stats ++ [s])+ ) [] varTysExp++ let -- get the names of our kind vars+ kindVarNames :: [Name]+ kindVarNames = flip mapMaybe starKindStats+ (\case+ IsKindVar n -> Just n+ _ -> Nothing+ )++ let+ -- instantiate polykinded things to star.+ varTysExpSubst :: [Type]+ varTysExpSubst = map (substNamesWithKindStar kindVarNames) varTysExp++ -- the constraints needed on type variables+ preds :: [Maybe Pred]+ preds = map (deriveConstraint cls) varTysExpSubst++ -- We now sub all of the specialised-to-* kind+ -- variable names with *, but in the original types,+ -- not the synonym-expanded types. The reason we+ -- do this is superficial: we want the derived+ -- instance to resemble the datatype written in+ -- source code as closely as possible. For example,+ --+ -- data family Fam a+ -- newtype instance Fam String = Fam String+ --+ -- We'd want to generate the instance:+ --+ -- instance C (Fam String)+ --+ -- Not:+ --+ -- instance C (Fam [Char])+ varTysOrigSubst :: [Type]+ varTysOrigSubst =+ map (substNamesWithKindStar kindVarNames) $ varTysOrig++ -- if we are working on a data family+ -- or newtype family, we need to peel off+ -- the kinds. See Note [Kind signatures in+ -- derived instances]+ varTysOrigSubst' :: [Type]+ varTysOrigSubst' = if isDataFamily variant+ then varTysOrigSubst+ else map unSigT varTysOrigSubst++ -- the constraints needed on type variables+ -- makes up the constraint part of the+ -- instance head.+ instanceCxt :: Cxt+ instanceCxt = catMaybes preds++ -- the class and type in the instance head.+ instanceType :: Type+ instanceType = AppT (ConT (shwiftyClassName cls))+ $ applyTyCon tyConName varTysOrigSubst'++ -- forall <tys>. ctx tys => Cls ty+ lift $ forallT+ (map tyVarBndrNoSig tyVarBndrs)+ (pure instanceCxt)+ (pure instanceType)++-- the class we're generating an instance of+data ShwiftyClass+ = ClassSwift -- ToSwift+ | ClassSwiftData -- ToSwiftData++-- turn a 'ShwiftyClass' into a 'Name'+shwiftyClassName :: ShwiftyClass -> Name+shwiftyClassName = \case+ ClassSwift -> ''ToSwift+ ClassSwiftData -> ''ToSwiftData++-- derive the constraint needed on a type variable+-- in order to build the instance head for a class.+deriveConstraint :: ()+ => ShwiftyClass+ -- ^ class name+ -> Type+ -- ^ type+ -> Maybe Pred+ -- ^ constraint on type+deriveConstraint c@ClassSwift typ+ | not (isTyVar typ) = Nothing+ | hasKindStar typ = Just (applyCon (shwiftyClassName c) tName)+ | otherwise = Nothing+ where+ tName :: Name+ tName = varTToName typ+ varTToName = \case+ VarT n -> n+ SigT t _ -> varTToName t+ _ -> error "Shwifty.varTToName: encountered non-type variable"+deriveConstraint ClassSwiftData _ = Nothing++-- apply a type constructor to a type variable.+-- this can be useful for letting the kind+-- inference engine doing work for you. see+-- 'toSwiftECxt' for an example of this.+applyCon :: Name -> Name -> Pred+applyCon con t = AppT (ConT con) (VarT t)++-- peel off a kind signature from a Type+unSigT :: Type -> Type+unSigT = \case+ SigT t _ -> t+ t -> t++-- is the type a type variable?+isTyVar :: Type -> Bool+isTyVar = \case+ VarT _ -> True+ SigT t _ -> isTyVar t+ _ -> False++-- does the type have kind *?+hasKindStar :: Type -> Bool+hasKindStar = \case+ VarT _ -> True+ SigT _ StarT -> True+ _ -> False++-- perform the substitution of type variables+-- who have kinds which can be realised to *,+-- with the same type variable where its kind+-- has been turned into *+substNamesWithKindStar :: [Name] -> Type -> Type+substNamesWithKindStar ns t = foldr' (`substNameWithKind` starK) t ns+ where+ substNameWithKind :: Name -> Kind -> Type -> Type+ substNameWithKind n k = applySubstitution (M.singleton n k)++-- | The status of a kind variable w.r.t. its+-- ability to be realised into *.+data KindStatus+ = KindStar+ -- ^ kind * (or some k which can be realised to *)+ | NotKindStar+ -- ^ any other kind+ | IsKindVar Name+ -- ^ is actually a kind variable+ | IsCon Name+ -- ^ is a constructor - this will typically+ -- happen in a data family instance, because+ -- we often have to construct a+ -- FlexibleInstance. our old check for+ -- canRealiseKindStar didn't check for+ -- `ConT` - where this would happen.+ --+ -- TODO: Now i think this might need to be+ -- removed in favour of something smarter.++-- can we realise the type's kind to *?+canRealiseKindStar :: Type -> KindStatus+canRealiseKindStar = \case+ VarT{} -> KindStar+ SigT _ StarT -> KindStar+ SigT _ (VarT n) -> IsKindVar n+ ConT n -> IsCon n+ _ -> NotKindStar++-- discard the kind signature from a TyVarBndr.+tyVarBndrNoSig :: TyVarBndr -> TyVarBndr+tyVarBndrNoSig = \case+ PlainTV n -> PlainTV n+ KindedTV n _k -> PlainTV n++-- fully applies a type constructor to its+-- type variables+applyTyCon :: Name -> [Type] -> Type+applyTyCon = foldl' AppT . ConT++-- Turn a String into an Exp string literal+stringE :: String -> Exp+stringE = LitE . StringL++-- convert a type into a 'Ty'.+-- we respect constraints here - e.g. in+-- `(Swift a, Swift b) => Swift (Foo a b)`,+-- we don't just fill in holes like in+-- `toSwiftEPoly`, we actually turn `a`+-- and `b` into `Ty`s directly. Consequently,+-- the implementation is much simpler - just+-- an application.+--+-- Note the use of unSigT - see Note+-- [Kind signatures in derived instances].+toSwiftECxt :: Type -> Exp+toSwiftECxt (unSigT -> typ) = AppE+ (VarE 'toSwift)+ (SigE (ConE 'Proxy) (AppT (ConT ''Proxy) typ))++-- convert a type into a 'Ty'.+-- polymorphic types do not require a 'ToSwift'+-- instance, since we fill them in with 'SingSymbol'.+--+-- We do this by stretching out a type along its+-- spine, completely. we then fill in any polymorphic+-- variables with 'SingSymbol', reflecting the type+-- Name to a Symbol. then we compress the spine to+-- get the original type. the 'ToSwift' instance for+-- 'SingSymbol' gets us where we need to go.+--+-- Note that @compress . decompress@ is not+-- actually equivalent to the identity function on+-- Type because of ForallT, where we discard some+-- context. However, for any types we care about,+-- there shouldn't be a ForallT, so this *should*+-- be fine.+toSwiftEPoly :: Type -> Exp+toSwiftEPoly = \case+ -- we don't need to special case VarT and SigT+ VarT n+ -> AppE (ConE 'Poly) (prettyTyVar n)+ SigT (VarT n) _+ -> AppE (ConE 'Poly) (prettyTyVar n)+ typ ->+ let decompressed = decompress typ+ prettyName = map Char.toUpper . TS.unpack . head . TS.splitOn "_" . last . TS.splitOn "." . TS.pack . show+ filledInHoles = decompressed <&>+ (\case+ VarT name -> AppT+ (ConT ''Shwifty.SingSymbol)+ (LitT (StrTyLit (prettyName name)))+ SigT (VarT name) _ -> AppT+ (ConT ''Shwifty.SingSymbol)+ (LitT (StrTyLit (prettyName name)))+ t -> t+ )+ typ' = compress filledInHoles+ in AppE+ (VarE 'toSwift)+ (SigE (ConE 'Proxy) (AppT (ConT ''Proxy) typ'))++decompress :: Type -> Rose Type+decompress typ = case unapplyTy typ of+ tyCon :| tyArgs -> Rose tyCon (decompress <$> tyArgs)++compress :: Rose Type -> Type+compress (Rose typ []) = typ+compress (Rose t ts) = foldl' AppT t (compress <$> ts)++unapplyTy :: Type -> NonEmpty Type+unapplyTy = NE.reverse . go+ where+ go = \case+ AppT t1 t2 -> t2 <| go t1+ SigT t _ -> go t+ ForallT _ _ t -> go t+ t -> t :| []++-- | Types can be stretched out into a Rose tree.+-- decompress will stretch a type out completely,+-- in such a way that it cannot be stretched out+-- further. compress will reconstruct a type from+-- its stretched form.+--+-- Also note that this is equivalent to+-- Cofree NonEmpty Type.+--+-- Examples:+--+-- Maybe a+-- =>+-- AppT (ConT Maybe) (VarT a)+--+--+-- Either a b+-- =>+-- AppT (AppT (ConT Either) (VarT a)) (VarT b)+-- =>+-- Rose (ConT Either)+-- [ Rose (VarT a)+-- [+-- ]+-- , Rose (VarT b)+-- [+-- ]+-- ]+--+--+-- Either (Maybe a) (Maybe b)+-- =>+-- AppT (AppT (ConT Either) (AppT (ConT Maybe) (VarT a))) (AppT (ConT Maybe) (VarT b))+-- =>+-- Rose (ConT Either)+-- [ Rose (ConT Maybe)+-- [ Rose (VarT a)+-- [+-- ]+-- ]+-- , Rose (ConT Maybe)+-- [ Rose (VarT b)+-- [+-- ]+-- ]+-- ]+data Rose a = Rose a [Rose a]+ deriving stock (Eq, Show)+ deriving stock (Functor,Foldable,Traversable)++{-+Note [Kind signatures in derived instances]+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~++It is possible to put explicit kind signatures into the derived instances, e.g.,++ instance C a => C (Data (f :: * -> *)) where ...++But it is preferable to avoid this if possible. If we come up with an incorrect+kind signature (which is entirely possible, since Template Haskell doesn't always+have the best track record with reifying kind signatures), then GHC will flat-out+reject the instance, which is quite unfortunate.++Plain old datatypes have the advantage that you can avoid using any kind signatures+at all in their instances. This is because a datatype declaration uses all type+variables, so the types that we use in a derived instance uniquely determine their+kinds. As long as we plug in the right types, the kind inferencer can do the rest+of the work. For this reason, we use unSigT to remove all kind signatures before+splicing in the instance context and head.++Data family instances are trickier, since a data family can have two instances that+are distinguished by kind alone, e.g.,++ data family Fam (a :: k)+ data instance Fam (a :: * -> *)+ data instance Fam (a :: *)++If we dropped the kind signatures for C (Fam a), then GHC will have no way of+knowing which instance we are talking about. To avoid this scenario, we always+include explicit kind signatures in data family instances. There is a chance that+the inferred kind signatures will be incorrect, in which case we have to write the instance manually.+-}++-- are we working on a data family+-- or newtype family?+isDataFamily :: DatatypeVariant -> Bool+isDataFamily = \case+ NewtypeInstance -> True+ DataInstance -> True+ _ -> False++stripConT :: [Type] -> [Type]+stripConT = mapMaybe noConT+ where+ noConT = \case+ ConT {} -> Nothing+ t -> Just t++-- | Like 'getShwiftyWith', but with a 'Codec'+-- instead of 'Options'.+getShwiftyCodec :: forall tag. ModifyOptions tag => Codec tag -> Name -> Q [Dec]+getShwiftyCodec c = getShwiftyCodecTags c []++-- | Like 'getShwiftyWithTags', but with a 'Codec'+-- instead of 'Options'.+getShwiftyCodecTags :: forall tag. ModifyOptions tag => Codec tag -> [Name] -> Name -> Q [Dec]+getShwiftyCodecTags _ ts n = getShwiftyWithTags (modifyOptions @tag defaultOptions) ts n++--getShwiftyModTags :: forall tag typ. (ModifyOptions tag, KnownSymbol typ) => [Name] -> Q [Dec]+--getShwiftyModTags ts = getShwiftyWithTags (modifyOptions @tag defaultOptions) ts (mkName (symbolVal (Proxy @typ)))++--combine :: Codec a -> Codec b -> Codec (a & b)+--combine _ _ = Codec++-- | Construct a Type Alias.+aliasExp :: ()+ => Name+ -- ^ alias name+ -> [Type]+ -- ^ type variables+ -> Type+ -- ^ type (RHS)+ -> Exp+aliasExp name tyVars field = RecConE 'SwiftAlias+ [ (mkName "aliasName", unqualName name)+ , (mkName "aliasTyVars", prettyTyVars tyVars)+ , (mkName "aliasTyp", toSwiftECxt field)+ ]++-- | Construct a Tag.+tagExp :: ()+ => Name+ -- ^ tycon name+ -> Name+ -- ^ parent name+ -> Type+ -- ^ type of the tag (RHS)+ -> Bool+ -- ^ Whether or not we are disambiguating.+ -> Exp+tagExp tyconName parentName typ dis = RecConE 'Tag+ [ (mkName "tagName", unqualName tyconName)+ , (mkName "tagParent", unqualName parentName)+ , (mkName "tagTyp", toSwiftECxt typ)+ , (mkName "tagDisambiguate", case dis of+ { False -> ConE 'False+ ; True -> ConE 'True+ })+ ]++-- | Construct an Enum.+enumExp :: ()+ => Name+ -- ^ parent name+ -> [Type]+ -- ^ type variables+ -> [Protocol]+ -- ^ protocols+ -> [Exp]+ -- ^ cases+ -> Maybe Ty+ -- ^ Raw Value+ -> [Exp]+ -- ^ Tags+ -> (Bool, Maybe Ty, [Protocol])+ -- ^ Make base?+ -> Exp+enumExp parentName tyVars protos cases raw tags bs+ = applyBase bs $ RecConE 'SwiftEnum+ [ (mkName "enumName", unqualName parentName)+ , (mkName "enumTyVars", prettyTyVars tyVars)+ , (mkName "enumProtocols", protosExp protos)+ , (mkName "enumCases", ListE cases)+ , (mkName "enumRawValue", rawValueE raw)+ , (mkName "enumPrivateTypes", ListE [])+ , (mkName "enumTags", ListE tags)+ ]++-- | Construct a Struct.+structExp :: ()+ => Name+ -- ^ struct name+ -> [Type]+ -- ^ type variables+ -> [Protocol]+ -- ^ protocols+ -> [Exp]+ -- ^ fields+ -> [Exp]+ -- ^ tags+ -> (Bool, Maybe Ty, [Protocol])+ -- ^ Make base?+ -> Exp+structExp name tyVars protos fields tags bs+ = applyBase bs $ RecConE 'SwiftStruct+ [ (mkName "structName", unqualName name)+ , (mkName "structTyVars", prettyTyVars tyVars)+ , (mkName "structProtocols", protosExp protos)+ , (mkName "structFields", ListE fields)+ , (mkName "structPrivateTypes", ListE [])+ , (mkName "structTags", ListE tags)+ ]++matchProxy :: Exp -> ShwiftyM Match+matchProxy e = lift $ match+ (conP 'Proxy [])+ (normalB (pure e))+ []++stripFields :: SwiftData -> SwiftData+stripFields = \case+ s@SwiftStruct{} -> s { structFields = [] }+ s@SwiftEnum{} -> s { enumCases = go (enumCases s) }+ where+ go = map stripOne+ stripOne (x, _) = (x, [])+ s -> s++giveProtos :: [Protocol] -> SwiftData -> SwiftData+giveProtos ps = \case+ s@SwiftStruct{} -> s { structProtocols = ps }+ s@SwiftEnum{} -> s { enumProtocols = ps }+ s -> s++suffixBase :: SwiftData -> SwiftData+suffixBase = \case+ s@SwiftStruct{} -> s { structName = structName s ++ "Base" }+ s@SwiftEnum{} -> s { enumName = enumName s ++ "Base" }+ s -> s++giveBase :: Maybe Ty -> [Protocol] -> SwiftData -> SwiftData+giveBase r ps = \case+ s@SwiftStruct{} -> s { structPrivateTypes = [giveProtos ps (suffixBase (stripFields s))] }+ s@SwiftEnum{} -> s { enumPrivateTypes = [ giveProtos ps (suffixBase (stripFields s)) { enumRawValue = r }] }+ s -> s++-- | Apply 'giveBase' to a 'SwiftData'.+--+-- Ideally we would offload this into+-- the first construction of the SwiftData,+-- inside structExp/enumExp.+--+--+-- should we strip tyvars as well?+applyBase :: (Bool, Maybe Ty, [Protocol]) -> Exp -> Exp+applyBase (b, r, ps) (ParensE -> s) = if b+ then+ AppE (AppE (AppE (VarE 'giveBase) (rawValueE r)) (protosExp ps)) s+ else s++protosExp :: [Protocol] -> Exp+protosExp = ListE . map (ConE . mkName . show)+
+ src/Shwifty/Class.hs view
@@ -0,0 +1,154 @@+{-# language+ CPP+ , FlexibleInstances+ , ScopedTypeVariables+ , TypeApplications+ #-}++module Shwifty.Class+ ( ToSwift(..)+ , ToSwiftData(..)+ ) where++import Data.List (intercalate)+import Data.Proxy (Proxy(..))+import Control.Monad.Except+import Data.CaseInsensitive (CI)+import Data.Foldable (foldlM,foldr',foldl')+import Data.Functor ((<&>))+import Data.Int (Int8,Int16,Int32,Int64)+import Data.Kind (Constraint)+import Data.List.NonEmpty ((<|), NonEmpty(..))+import Data.Maybe (mapMaybe, catMaybes)+import Data.Proxy (Proxy(..))+import Data.Time (UTCTime)+import Data.UUID.Types (UUID)+import Data.Vector (Vector)+import Data.Void (Void)+import Data.Word (Word8,Word16,Word32,Word64)+import GHC.TypeLits+ ( Symbol, KnownSymbol, symbolVal+ , TypeError, ErrorMessage(..)+ )+import Language.Haskell.TH hiding (stringE)+import Language.Haskell.TH.Datatype+import Prelude hiding (Enum(..))+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BL+import qualified Data.Char as Char+import qualified Data.HashMap.Strict as HM+import qualified Data.List as L+import qualified Data.List.NonEmpty as NE+import qualified Data.Map as M+import qualified Data.Text as TS+import qualified Data.Text.Lazy as TL+import qualified Data.Primitive as Prim++import Shwifty.Types++-- | The class for things which can be converted to+-- 'SwiftData'.+--+-- Typically the instance will be generated by+-- 'getShwifty'.+class ToSwiftData a where+ -- | Convert a type to 'SwiftData'+ toSwiftData :: Proxy a -> SwiftData++-- | The class for things which can be converted to+-- a Swift type ('Ty').+--+-- Typically the instance will be generated by+-- 'getShwifty'.+class ToSwift a where+ -- | Convert a type to its Swift 'Ty'.+ toSwift :: Proxy a -> Ty++instance ToSwift () where+ toSwift = const Unit++instance ToSwift Bool where+ toSwift = const Bool++instance ToSwift UUID where+ toSwift = const (Concrete "UUID" [])++instance ToSwift UTCTime where+ toSwift = const (Concrete "Date" [])++instance forall a b. (ToSwift a, ToSwift b) => ToSwift (a -> b) where+ toSwift = const (App (toSwift (Proxy @a)) (toSwift (Proxy @b)))++instance forall a. ToSwift a => ToSwift (Maybe a) where+ toSwift = const (Optional (toSwift (Proxy @a)))++-- | /Note/: In Swift, the ordering of the type+-- variables is flipped - Shwifty has made the+-- design choice to flip them for you. If you+-- take issue with this, please open an issue+-- for discussion on GitHub.+instance forall a b. (ToSwift a, ToSwift b) => ToSwift (Either a b) where+ toSwift = const (Result (toSwift (Proxy @b)) (toSwift (Proxy @a)))++instance ToSwift Integer where+ toSwift = const+#if WORD_SIZE_IN_BITS == 32+ BigSInt32+#else+ BigSInt64+#endif++instance ToSwift Int where toSwift = const I+instance ToSwift Int8 where toSwift = const I8+instance ToSwift Int16 where toSwift = const I16+instance ToSwift Int32 where toSwift = const I32+instance ToSwift Int64 where toSwift = const I64++instance ToSwift Word where toSwift = const U+instance ToSwift Word8 where toSwift = const U8+instance ToSwift Word16 where toSwift = const U16+instance ToSwift Word32 where toSwift = const U32+instance ToSwift Word64 where toSwift = const U64++instance ToSwift Float where toSwift = const F32+instance ToSwift Double where toSwift = const F64++instance ToSwift Char where toSwift = const Character++instance forall a. (ToSwift a) => ToSwift (Prim.Array a) where+ toSwift = const (Array (toSwift (Proxy @a)))++instance forall a. (ToSwift a) => ToSwift (Prim.SmallArray a) where+ toSwift = const (Array (toSwift (Proxy @a)))++instance ToSwift Prim.ByteArray where+ toSwift = const (Array U8)++instance forall a. (ToSwift a) => ToSwift (Prim.PrimArray a) where+ toSwift = const (Array (toSwift (Proxy @a)))++instance forall a. ToSwift a => ToSwift (Vector a) where+ toSwift = const (Array (toSwift (Proxy @a)))++instance {-# overlappable #-} forall a. ToSwift a => ToSwift [a] where+ toSwift = const (Array (toSwift (Proxy @a)))++instance {-# overlapping #-} ToSwift [Char] where toSwift = const Str++instance ToSwift TL.Text where toSwift = const Str+instance ToSwift TS.Text where toSwift = const Str++instance ToSwift BL.ByteString where toSwift = const (Array U8)+instance ToSwift BS.ByteString where toSwift = const (Array U8)++instance ToSwift (CI s) where toSwift = const Str++instance forall k v. (ToSwift k, ToSwift v) => ToSwift (M.Map k v) where toSwift = const (Dictionary (toSwift (Proxy @k)) (toSwift (Proxy @v)))++instance forall k v. (ToSwift k, ToSwift v) => ToSwift (HM.HashMap k v) where toSwift = const (Dictionary (toSwift (Proxy @k)) (toSwift (Proxy @v)))++instance forall a b. (ToSwift a, ToSwift b) => ToSwift ((,) a b) where+ toSwift = const (Tuple2 (toSwift (Proxy @a)) (toSwift (Proxy @b)))++instance forall a b c. (ToSwift a, ToSwift b, ToSwift c) => ToSwift ((,,) a b c) where+ toSwift = const (Tuple3 (toSwift (Proxy @a)) (toSwift (Proxy @b)) (toSwift (Proxy @c)))
+ src/Shwifty/Codec.hs view
@@ -0,0 +1,210 @@+{-# language+ AllowAmbiguousTypes+ , DataKinds+ , ExplicitNamespaces+ , FlexibleInstances+ , GADTs+ , KindSignatures+ , PolyKinds+ , ScopedTypeVariables+ , TypeApplications+ , TypeFamilies+ , TypeOperators+ , TypeSynonymInstances+ , UndecidableInstances+ #-}++module Shwifty.Codec+ ( Codec(..)+ , ModifyOptions(..)+ , AsIs+ , type (&)+ , Label(..)+ , Drop+ , DontGenerate+ , Implement+ , RawValue+ , CanBeRawValue+ , TypeAlias+ , NewtypeTag+ , DontLowercase+ , OmitField+ , OmitCase+ , MakeBase+ ) where++import Data.Kind (Constraint)+import Data.Proxy (Proxy(..))+import GHC.TypeLits+ ( KnownSymbol, Symbol, symbolVal+ , TypeError, ErrorMessage(..)+ )+import Shwifty.Class+import Shwifty.Types++-- | Modify options.+class ModifyOptions tag where+ modifyOptions :: Options -> Options++-- | No modifications+type AsIs = ()++instance ModifyOptions AsIs where+ modifyOptions = id++-- | A carrier for modifiers.+data Codec tag = Codec++instance ModifyOptions tag => ModifyOptions (Codec tag) where+ modifyOptions = modifyOptions @tag++infixr 6 &+-- | Combine modifications.+data a & b++instance forall a b. (ModifyOptions a, ModifyOptions b) => ModifyOptions (a & b) where+ modifyOptions = modifyOptions @a . modifyOptions @b++-- | Label modifiers.+data Label+ = TyCon+ -- ^ Type constructor modifier+ | DataCon+ -- ^ Data constructor modifiers+ | Field+ -- ^ Field label modifiers++-- | Modify a label by dropping a string+data Drop (label :: Label) (string :: Symbol)++instance KnownSymbol string => ModifyOptions (Drop 'TyCon string) where+ modifyOptions options = options+ { typeConstructorModifier = drop (length (symbolVal (Proxy @string)))+ }++instance KnownSymbol string => ModifyOptions (Drop 'DataCon string) where+ modifyOptions options = options+ { constructorModifier = drop (length (symbolVal (Proxy @string)))+ }++instance KnownSymbol string => ModifyOptions (Drop 'Field string) where+ modifyOptions options = options+ { fieldLabelModifier = drop (length (symbolVal (Proxy @string)))+ }++-- | Don't generate a specific class.+data DontGenerate (cls :: * -> Constraint)++class GenerateClass (c :: * -> Constraint) where+ classModifier :: Options -> Options++instance GenerateClass ToSwiftData where+ classModifier options = options { generateToSwiftData = False }++instance GenerateClass ToSwift where+ classModifier options = options { generateToSwift = False }++instance GenerateClass c => ModifyOptions (DontGenerate c) where+ modifyOptions = classModifier @c++-- | Add protocols+data Implement (protocol :: Protocol)++class KnownProtocol (p :: Protocol) where protocolVal :: Protocol+instance KnownProtocol 'Equatable where protocolVal = Equatable+instance KnownProtocol 'Hashable where protocolVal = Hashable+instance KnownProtocol 'Codable where protocolVal = Codable++instance ModifyOptions (Implement 'Equatable) where+ modifyOptions options = options { dataProtocols = Equatable : dataProtocols options }++instance ModifyOptions (Implement 'Hashable) where+ modifyOptions options = options { dataProtocols = Hashable : dataProtocols options }++instance ModifyOptions (Implement 'Codable) where+ modifyOptions options = options { dataProtocols = Codable : dataProtocols options }++-- | Add a rawValue+data RawValue (ty :: Ty)++-- | A Class that indicates that this swift type+-- can be a rawValue. The value of 'getRawValue'+-- will be its actual rawValue.+class CanBeRawValue (ty :: Ty) where+ getRawValue :: Ty++instance CanBeRawValue 'Str where getRawValue = Str+instance CanBeRawValue 'I where getRawValue = I+instance CanBeRawValue 'I8 where getRawValue = I8+instance CanBeRawValue 'I16 where getRawValue = I16+instance CanBeRawValue 'I32 where getRawValue = I32+instance CanBeRawValue 'I64 where getRawValue = I64+instance CanBeRawValue 'U where getRawValue = U+instance CanBeRawValue 'U8 where getRawValue = U8+instance CanBeRawValue 'U16 where getRawValue = U16+instance CanBeRawValue 'U32 where getRawValue = U32+instance CanBeRawValue 'U64 where getRawValue = U64++instance CanBeRawValue ty => ModifyOptions (RawValue ty) where+ modifyOptions options = options { dataRawValue = Just (getRawValue @ty) }++-- | Make it a type alias (only applies to newtypes)+data TypeAlias++instance ModifyOptions TypeAlias where+ modifyOptions options = options { typeAlias = True }++-- | Make it a newtype tag (only applies to newtype tags)+data NewtypeTag++instance ModifyOptions NewtypeTag where+ modifyOptions options = options { newtypeTag = True }++-- | Dont lower-case fields/cases+data DontLowercase (someKind :: Label)++instance TypeError ('Text "Cannot apply DontLowercase to TyCon") => ModifyOptions (DontLowercase 'TyCon) where+ modifyOptions _ = error "UNREACHABLE"++instance ModifyOptions (DontLowercase 'DataCon) where+ modifyOptions options = options { lowerFirstCase = False }++instance ModifyOptions (DontLowercase 'Field) where+ modifyOptions options = options { lowerFirstField = False }++-- | Omit a field+data OmitField (field :: Symbol)++instance KnownSymbol field => ModifyOptions (OmitField field) where+ modifyOptions options = options { omitFields = symbolVal (Proxy @field) : omitFields options }++-- | Omit a case+data OmitCase (cas :: Symbol)++instance KnownSymbol cas => ModifyOptions (OmitCase cas) where+ modifyOptions options = options { omitCases = symbolVal (Proxy @cas) : omitCases options }++-- | Make a base type+data MakeBase (rawValue :: Maybe Ty) (protocols :: [Protocol])++instance forall ty protocols. (CanBeRawValue ty, ProtocolList protocols) => ModifyOptions (MakeBase ('Just ty) protocols) where+ modifyOptions options = options+ { makeBase = (,,) True (Just (getRawValue @ty)) (protocolList @protocols)+ }++instance forall protocols. (ProtocolList protocols) => ModifyOptions (MakeBase 'Nothing protocols) where+ modifyOptions options = options+ { makeBase = (,,) True Nothing (protocolList @protocols)+ }++data SomeProtocol where+ SomeProtocol :: KnownProtocol p => SomeProtocol++class ProtocolList (x :: [Protocol]) where+ protocolList :: [Protocol]++instance ProtocolList '[] where+ protocolList = []++instance forall p ps. (KnownProtocol p, ProtocolList ps) => ProtocolList (p ': ps) where+ protocolList = protocolVal @p : protocolList @ps
+ src/Shwifty/Pretty.hs view
@@ -0,0 +1,204 @@+{-# language+ LambdaCase+ , RecordWildCards+ #-}++module Shwifty.Pretty+ ( prettySwiftData+ , prettyTy+ ) where++import Data.List (intercalate)++import Shwifty.Types++-- | Pretty-print a 'SwiftData'.+prettySwiftData :: SwiftData -> String+prettySwiftData = prettySwiftDataWith 4++-- | Pretty-print a 'SwiftData'.+-- This function cares about indent.+prettySwiftDataWith :: ()+ => Int -- ^ indent+ -> SwiftData+ -> String+prettySwiftDataWith indent = \case++ SwiftEnum {..} -> []+ ++ "enum "+ ++ prettyTypeHeader enumName enumTyVars+ ++ prettyRawValueAndProtocols enumRawValue enumProtocols+ ++ " {"+ ++ newlineNonEmpty enumCases+ ++ prettyEnumCases indents enumCases+ ++ newlineNonEmpty enumPrivateTypes+ ++ prettyPrivateTypes indents enumPrivateTypes+ ++ prettyTags indents enumTags+ ++ newlineNonEmpty enumTags+ ++ "}"++ SwiftStruct {..} -> []+ ++ "struct "+ ++ prettyTypeHeader structName structTyVars+ ++ prettyProtocols structProtocols+ ++ " {"+ ++ newlineNonEmpty structFields+ ++ prettyStructFields indents structFields+ ++ newlineNonEmpty structPrivateTypes+ ++ prettyPrivateTypes indents structPrivateTypes+ ++ prettyTags indents structTags+ ++ newlineNonEmpty structTags+ ++ "}"++ SwiftAlias{..} -> []+ ++ "typealias "+ ++ prettyTypeHeader aliasName aliasTyVars+ ++ " = "+ ++ prettyTy aliasTyp+ where+ indents = replicate indent ' '++ newlineNonEmpty [] = ""+ newlineNonEmpty _ = "\n"++prettyTypeHeader :: String -> [String] -> String+prettyTypeHeader name [] = name+prettyTypeHeader name tyVars = name ++ "<" ++ intercalate ", " tyVars ++ ">"++prettyRawValueAndProtocols :: Maybe Ty -> [Protocol] -> String+prettyRawValueAndProtocols Nothing ps = prettyProtocols ps+prettyRawValueAndProtocols (Just ty) [] = ": " ++ prettyTy ty+prettyRawValueAndProtocols (Just ty) ps = ": " ++ prettyTy ty ++ ", " ++ intercalate ", " (map show ps)++prettyProtocols :: [Protocol] -> String+prettyProtocols = \case+ [] -> ""+ ps -> ": " ++ intercalate ", " (map show ps)++prettyTags :: String -> [Ty] -> String+prettyTags indents = go where+ go [] = ""+ go (Tag{..}:ts) = []+ ++ "\n"+ ++ prettyTagDisambiguator tagDisambiguate indents tagName+ ++ indents+ ++ "typealias "+ ++ tagName+ ++ " = Tagged<"+ ++ (if tagDisambiguate then tagName ++ "Tag" else tagParent)+ ++ ", "+ ++ prettyTy tagTyp+ ++ ">"+ ++ go ts+ go _ = error "non-tag supplied to prettyTags"++prettyTagDisambiguator :: ()+ => Bool+ -- ^ disambiguate?+ -> String+ -- ^ indents+ -> String+ -- ^ parent type name+ -> String+prettyTagDisambiguator disambiguate indents parent+ = if disambiguate+ then []+ ++ indents+ ++ "enum "+ ++ parent+ ++ "Tag { }\n"+ else ""++labelCase :: Maybe String -> Ty -> String+labelCase Nothing ty = prettyTy ty+labelCase (Just label) ty = "_ " ++ label ++ ": " ++ prettyTy ty++-- | Pretty-print a 'Ty'.+prettyTy :: Ty -> String+prettyTy = \case+ Str -> "String"+ Unit -> "()"+ Bool -> "Bool"+ Character -> "Character"+ Tuple2 e1 e2 -> "(" ++ prettyTy e1 ++ ", " ++ prettyTy e2 ++ ")"+ Tuple3 e1 e2 e3 -> "(" ++ prettyTy e1 ++ ", " ++ prettyTy e2 ++ ", " ++ prettyTy e3 ++ ")"+ Optional e -> prettyTy e ++ "?"+ Result e1 e2 -> "Result<" ++ prettyTy e1 ++ ", " ++ prettyTy e2 ++ ">"+ Set e -> "Set<" ++ prettyTy e ++ ">"+ Dictionary e1 e2 -> "Dictionary<" ++ prettyTy e1 ++ ", " ++ prettyTy e2 ++ ">"+ Array e -> "[" ++ prettyTy e ++ "]"+ -- App is special, we recurse until we no longer+ -- any applications.+ App e1 e2 -> prettyApp e1 e2+ I -> "Int"+ I8 -> "Int8"+ I16 -> "Int16"+ I32 -> "Int32"+ I64 -> "Int64"+ U -> "UInt"+ U8 -> "UInt8"+ U16 -> "UInt16"+ U32 -> "UInt32"+ U64 -> "UInt64"+ F32 -> "Float"+ F64 -> "Double"+ Decimal -> "Decimal"+ BigSInt32 -> "BigSInt32"+ BigSInt64 -> "BigSInt64"+ Poly ty -> ty+ Concrete ty [] -> ty+ Concrete ty tys -> ty+ ++ "<"+ ++ intercalate ", " (map prettyTy tys)+ ++ ">"+ Tag {..} -> tagParent ++ "." ++ tagName++prettyApp :: Ty -> Ty -> String+prettyApp t1 t2 = "(("+ ++ intercalate ", " (map prettyTy as)+ ++ ") -> "+ ++ prettyTy r+ ++ ")"+ where+ (as, r) = go t1 t2+ go e1 (App e2 e3) = case go e2 e3 of+ (args, ret) -> (e1 : args, ret)+ go e1 e2 = ([e1], e2)++prettyEnumCases :: String -> [(String, [(Maybe String, Ty)])] -> String+prettyEnumCases indents = go+ where+ go = \case+ [] -> ""+ ((caseNm, []):xs) -> []+ ++ indents+ ++ "case "+ ++ caseNm+ ++ "\n"+ ++ go xs+ ((caseNm, cs):xs) -> []+ ++ indents+ ++ "case "+ ++ caseNm+ ++ "("+ ++ (intercalate ", " (map (uncurry labelCase) cs))+ ++ ")\n"+ ++ go xs++prettyStructFields :: String -> [(String, Ty)] -> String+prettyStructFields indents = go+ where+ go [] = ""+ go ((fieldName,ty):fs) = indents ++ "let " ++ fieldName ++ ": " ++ prettyTy ty ++ "\n" ++ go fs++prettyPrivateTypes :: String -> [SwiftData] -> String+prettyPrivateTypes indents = go+ where+ go [] = ""+ go (s:ss) = indents ++ "private " ++ unlines (onLast (indents ++) (lines (prettySwiftData s))) ++ go ss++-- map a function over everything but the+-- first element.+onLast :: (a -> a) -> [a] -> [a]+onLast f [] = []+onLast f (x:xs) = x : map f xs
+ src/Shwifty/Types.hs view
@@ -0,0 +1,314 @@+{-# language+ AllowAmbiguousTypes+ , DeriveGeneric+ , DeriveLift+ , DerivingStrategies+#-}++module Shwifty.Types+ ( Ty(..)+ , SwiftData(..)+ , Protocol(..)+ , Options(..)+ ) where++import GHC.Generics (Generic)+import Language.Haskell.TH.Syntax (Lift)++-- | An AST representing a Swift type.+data Ty+ = Unit+ -- ^ Unit (called "Unit/Void" in swift). Empty struct type.+ | Bool+ -- ^ Bool+ | Character+ -- ^ Character+ | Str+ -- ^ String. Named 'Str' to avoid conflicts with+ -- 'Data.Aeson.String'.+ | I+ -- ^ signed machine integer+ | I8+ -- ^ signed 8-bit integer+ | I16+ -- ^ signed 16-bit integer+ | I32+ -- ^ signed 32-bit integer+ | I64+ -- ^ signed 64-bit integer+ | U+ -- ^ unsigned machine integer+ | U8+ -- ^ unsigned 8-bit integer+ | U16+ -- ^ unsigned 16-bit integer+ | U32+ -- ^ unsigned 32-bit integer+ | U64+ -- ^ unsigned 64-bit integer+ | F32+ -- ^ 32-bit floating point+ | F64+ -- ^ 64-bit floating point+ | Decimal+ -- ^ Increased-precision floating point+ | BigSInt32+ -- ^ 32-bit big integer+ | BigSInt64+ -- ^ 64-bit big integer+ | Tuple2 Ty Ty+ -- ^ 2-tuple+ | Tuple3 Ty Ty Ty+ -- ^ 3-tuple+ | Optional Ty+ -- ^ Maybe type+ | Result Ty Ty+ -- ^ Either type+ --+ -- /Note/: The error type in Swift must+ -- implement the @Error@ protocol. This library+ -- currently does not enforce this.+ | Set Ty+ -- ^ Set type+ | Dictionary Ty Ty+ -- ^ Dictionary type+ | Array Ty+ -- ^ array type+ | App Ty Ty+ -- ^ function type+ | Poly String+ -- ^ polymorphic type variable+ | Concrete+ { concreteName :: String+ -- ^ the name of the type+ , concreteTyVars :: [Ty]+ -- ^ the type's type variables+ }+ -- ^ a concrete type variable, and its+ -- type variables. Will typically be generated+ -- by 'getShwifty'.+ | Tag+ { tagName :: String+ -- ^ the name of the type+ , tagParent :: String+ -- ^ the type constructor of the type+ -- to which this alias belongs+ , tagTyp :: Ty+ -- ^ the type that this represents+ , tagDisambiguate :: Bool+ -- ^ does the type need disambiguation?+ --+ -- This will happen if there are multiple+ -- tags with the same type. This is needed+ -- to maintain safety.+ }+ -- ^ A @Tagged@ typealias, for newtyping+ -- in a way that doesn't break Codable.+ --+ -- See 'getShwiftyWithTags' for examples.+ deriving stock (Eq, Show, Read)+ deriving stock (Generic)+ deriving stock (Lift)++-- | A Swift datatype, either a struct (product type)+-- or enum (sum type). Haskll types are+-- sums-of-products, so the way we differentiate+-- when doing codegen,+-- is that types with a single constructor+-- will be converted to a struct, and those with+-- two or more constructors will be converted to an+-- enum. Types with 0 constructors will be converted+-- to an empty enum.+data SwiftData+ = SwiftStruct+ { structName :: String+ -- ^ The name of the struct+ , structTyVars :: [String]+ -- ^ The struct's type variables+ , structProtocols :: [Protocol]+ -- ^ The protocols which the struct+ -- implements+ , structFields :: [(String, Ty)]+ -- ^ The fields of the struct. the pair+ -- is interpreted as (name, type).+ , structPrivateTypes :: [SwiftData]+ -- ^ Private types of the struct. Typically+ -- populated by setting 'makeBase'.+ , structTags :: [Ty]+ -- ^ The tags of the struct. See 'Tag'.+ }+ -- ^ A struct (product type)+ | SwiftEnum+ { enumName :: String+ -- ^ The name of the enum+ , enumTyVars :: [String]+ -- ^ The enum's type variables+ , enumProtocols :: [Protocol]+ -- ^ The protocols which the enum+ -- implements+ , enumCases :: [(String, [(Maybe String, Ty)])]+ -- ^ The cases of the enum. the type+ -- can be interpreted as+ -- (name, [(label, type)]).+ , enumRawValue :: Maybe Ty+ -- ^ The rawValue of an enum. See+ -- https://developer.apple.com/documentation/swift/rawrepresentable/1540698-rawvalue+ --+ -- Typically the 'Ty' will be+ -- 'I' or 'String'.+ --+ -- /Note/: Currently, nothing will prevent+ -- you from putting something+ -- nonsensical here.+ , enumPrivateTypes :: [SwiftData]+ -- ^ Private types of the enum. Typically+ -- populated by setting 'makeBase'.+ , enumTags :: [Ty]+ -- ^ The tags of the struct. See 'Tag'.+ }+ -- ^ An enum (sum type)+ | SwiftAlias+ { aliasName :: String+ -- ^ the name of the type alias+ , aliasTyVars :: [String]+ -- ^ the type variables of the type alias+ , aliasTyp :: Ty+ -- ^ the type this represents (RHS)+ }+ -- ^ A /top-level/ type alias+ deriving stock (Eq, Read, Show, Generic)++-- | Swift protocols.+-- Only a few are supported right now.+data Protocol+ = Hashable+ -- ^ The 'Hashable' protocol.+ -- See https://developer.apple.com/documentation/swift/hashable+ | Codable+ -- ^ The 'Codable' protocol.+ -- See https://developer.apple.com/documentation/swift/hashable+ | Equatable+ -- ^ The 'Equatable' protocol.+ -- See https://developer.apple.com/documentation/swift/hashable+ deriving stock (Eq, Read, Show, Generic)+ deriving stock (Lift)++-- | Options that specify how to+-- encode your 'SwiftData' to a swift type.+--+-- Options can be set using record syntax on+-- 'defaultOptions' with the fields below.+data Options = Options+ { typeConstructorModifier :: String -> String+ -- ^ Function applied to type constructor names.+ -- The default ('id') makes no changes.+ , fieldLabelModifier :: String -> String+ -- ^ Function applied to field labels.+ -- Handy for removing common record prefixes,+ -- for example. The default ('id') makes no+ -- changes.+ , constructorModifier :: String -> String+ -- ^ Function applied to value constructor names.+ -- The default ('id') makes no changes.+ , optionalExpand :: Bool+ -- ^ Whether or not to truncate Optional types.+ -- Normally, an Optional ('Maybe') is encoded+ -- as "A?", which is syntactic sugar for+ -- "Optional\<A\>". The default value ('False')+ -- will keep it as sugar. A value of 'True'+ -- will expand it to be desugared.+ , generateToSwift :: Bool+ -- ^ Whether or not to generate a 'ToSwift'+ -- instance. Sometime this can be desirable+ -- if you want to define the instance by hand,+ -- or the instance exists elsewhere.+ -- The default is 'True', i.e., to generate+ -- the instance.+ , generateToSwiftData :: Bool+ -- ^ Whether or not to generate a 'ToSwiftData'+ -- instance. Sometime this can be desirable+ -- if you want to define the instance by hand,+ -- or the instance exists elsewhere.+ -- The default is 'True', i.e., to generate+ -- the instance.+ , dataProtocols :: [Protocol]+ -- ^ Protocols to add to a type.+ -- The default (@[]@) will add none.+ , dataRawValue :: Maybe Ty+ -- ^ The rawValue of an enum. See+ -- https://developer.apple.com/documentation/swift/rawrepresentable/1540698-rawvalue+ --+ -- The default ('Nothing') will not+ -- include any rawValue.+ --+ -- Typically, if the type does have+ -- a 'rawValue', the 'Ty' will be+ -- 'I' or 'Str'.+ --+ -- /Note/: Currently, nothing will prevent+ -- you from putting something+ -- nonsensical here.+ , typeAlias :: Bool+ -- ^ Whether or not to generate a newtype as+ -- a type alias. Consider if you want this+ -- or to use 'getShwiftyWithTags' instead.+ --+ -- The default ('False') will generate newtypes+ -- as their own structs.+ , newtypeTag :: Bool+ -- ^ Whether or not to generate a newtype as an+ -- empty enum with a tag. This is for type+ -- safety reasons, but with retaining the+ -- ability to have Codable conformance.+ --+ -- The default ('False') will not do this.+ --+ -- /Note/: This takes priority over 'typeAlias'.+ --+ -- /Note/: This option is not currently+ -- supported for newtype instances.+ --+ -- === __Examples__+ --+ -- > newtype NonEmptyText = MkNonEmptyText String+ -- > $(getShwiftyWith (defaultOptions { newtypeTag = True }) ''NonEmpyText)+ --+ -- @+ -- enum NonEmptyTextTag {+ -- typealias NonEmptyText = Tagged\<NonEmptyTextTag, String\>+ -- }+ -- @+ , lowerFirstField :: Bool+ -- ^ Whether or not to lower-case the first+ -- character of a field after applying all+ -- modifiers to it.+ --+ -- The default ('True') will do so.+ , lowerFirstCase :: Bool+ -- ^ Whether or not to lower-case the first+ -- character of a case after applying all+ -- modifiers to it.+ --+ -- The default ('True') will do so.+ , omitFields :: [String]+ -- ^ Fields to omit from a struct when+ -- generating types.+ --+ -- The default (@[]@) will omit nothing.+ , omitCases :: [String]+ -- ^ Cases to omit from an enum when+ -- generating types.+ --+ -- The default (@[]@) will omit nothing.+ , makeBase :: (Bool, Maybe Ty, [Protocol])+ -- ^ Whether or not to make a base type,+ -- its raw value, and its protocols.+ --+ -- Here, "base type" refers to a+ -- version of the type without any fields.+ -- This can be useful for doing Codable+ -- conversions.+ --+ -- The default ('False', 'Nothing', @[]@)+ -- will not create the base type.+ }