File: OverloadedMethods.hs

package info (click to toggle)
haskell-haskell-gi 0.26.12-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 800 kB
  • sloc: haskell: 8,617; ansic: 74; makefile: 4
file content (204 lines) | stat: -rw-r--r-- 8,794 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
module Data.GI.CodeGen.OverloadedMethods
    ( genMethodList
    , genMethodInfo
    , genUnsupportedMethodInfo
    ) where

import Control.Monad (forM, forM_, when)
#if !MIN_VERSION_base(4,11,0)
import Data.Monoid ((<>))
#endif
import Data.Text (Text)
import qualified Data.Text as T

import Data.GI.CodeGen.API
import Data.GI.CodeGen.Conversions (ExposeClosures(..))
import Data.GI.CodeGen.Callable (callableSignature, Signature(..),
                                 ForeignSymbol(..), fixupCallerAllocates)
import Data.GI.CodeGen.Code
import Data.GI.CodeGen.ModulePath (dotModulePath)
import Data.GI.CodeGen.SymbolNaming (lowerName, upperName, qualifiedSymbol,
                                     moduleLocation, hackageModuleLink)
import Data.GI.CodeGen.Util (ucFirst)

-- | Qualified name for the info for a given method.
methodInfoName :: Name -> Method -> CodeGen e Text
methodInfoName n method =
    let infoName = upperName n <> (ucFirst . lowerName . methodName) method
                   <> "MethodInfo"
    in qualifiedSymbol infoName n

-- | Appropriate instances so overloaded labels are properly resolved.
genMethodResolver :: Text -> CodeGen e ()
genMethodResolver n = do
  addLanguagePragma "TypeApplications"
  group $ do
    line $ "instance (info ~ Resolve" <> n <> "Method t " <> n <> ", "
          <> "O.OverloadedMethod info " <> n <> " p) => OL.IsLabel t ("
          <> n <> " -> p) where"
    line $ "#if MIN_VERSION_base(4,10,0)"
    indent $ line $ "fromLabel = O.overloadedMethod @info"
    line $ "#else"
    indent $ line $ "fromLabel _ = O.overloadedMethod @info"
    line $ "#endif"

  -- The circular instance trick is to avoid the liberal coverage
  -- condition. We should be using DYSFUNCTIONAL pragmas instead, once
  -- those are implemented:
  -- https://github.com/ghc-proposals/ghc-proposals/pull/374
  cppIf (CPPMinVersion "base" (4,13,0)) $ group $ do
    line $ "instance (info ~ Resolve" <> n <> "Method t " <> n <> ", "
          <> "O.OverloadedMethod info " <> n <> " p, "
          <> "R.HasField t " <> n <> " p) => "
          <> "R.HasField t " <> n <> " p where"
    indent $ line $ "getField = O.overloadedMethod @info"

  group $ do
    line $ "instance (info ~ Resolve" <> n <> "Method t " <> n <> ", "
          <> "O.OverloadedMethodInfo info " <> n <> ") => "
          <> "OL.IsLabel t (O.MethodProxy info "
          <> n <> ") where"
    line $ "#if MIN_VERSION_base(4,10,0)"
    indent $ line $ "fromLabel = O.MethodProxy"
    line $ "#else"
    indent $ line $ "fromLabel _ = O.MethodProxy"
    line $ "#endif"

-- | Generate the `MethodList` instance given the list of methods for
-- the given named type. Returns a Haddock comment summarizing the
-- list of methods available.
genMethodList :: Name -> [(Name, Method)] -> CodeGen e ()
genMethodList n methods = do
  let name = upperName n
  let filteredMethods = filter isOrdinaryMethod methods
      gets = filter isGet filteredMethods
      sets = filter isSet filteredMethods
      others = filter (\m -> not (isSet m || isGet m)) filteredMethods
      orderedMethods = others ++ gets ++ sets
  infos <- forM orderedMethods $ \(owner, method) ->
           do mi <- methodInfoName owner method
              return ((lowerName . methodName) method, mi)
  group $ do
    let resolver = "Resolve" <> name <> "Method"
    export (Section MethodSection) resolver
    line $ "type family " <> resolver <> " (t :: Symbol) (o :: DK.Type) :: DK.Type where"
    indent $ forM_ infos $ \(label, info) -> do
        line $ resolver <> " \"" <> label <> "\" o = " <> info
    indent $ line $ resolver <> " l o = O.MethodResolutionFailed l o"

  genMethodResolver name

  docs <- methodListDocumentation others gets sets
  prependSectionFormattedDocs (Section MethodSection) docs

  where isOrdinaryMethod :: (Name, Method) -> Bool
        isOrdinaryMethod (_, m) = methodType m == OrdinaryMethod

        isGet :: (Name, Method) -> Bool
        isGet (_, m) = "get_" `T.isPrefixOf` (name . methodName) m

        isSet :: (Name, Method) -> Bool
        isSet (_, m) = "set_" `T.isPrefixOf` (name . methodName) m

-- | Format a haddock comment with the information about available
-- methods.
methodListDocumentation :: [(Name, Method)] -> [(Name, Method)]
                           -> [(Name, Method)] -> CodeGen e Text
methodListDocumentation [] [] [] = return ""
methodListDocumentation ordinary getters setters = do
  ordinaryFormatted <- formatMethods ordinary
  gettersFormatted <- formatMethods getters
  settersFormatted <- formatMethods setters

  return $ "\n\n === __Click to display all available methods, including inherited ones__\n"
    <> "==== Methods\n" <> ordinaryFormatted
    <> "\n==== Getters\n" <> gettersFormatted
    <> "\n==== Setters\n" <> settersFormatted

  where formatMethods :: [(Name, Method)] -> CodeGen e Text
        formatMethods [] = return "/None/.\n"
        formatMethods methods = do
          qualifiedMethods <- forM methods $ \(owner, m) -> do
            api <- findAPIByName owner
            let mn = lowerName (methodName m)
            return $ "[" <> mn <>
              "](\"" <> dotModulePath (moduleLocation owner api)
              <> "#g:method:" <> mn <> "\")"
          return $ T.intercalate ", " qualifiedMethods <> ".\n"

-- | Treat the instance argument of a method as non-null, even if the
-- introspection data may say otherwise. Returns the modified
-- callable, together with a boolean value indicating where the
-- nullability annotation has been erased.
nonNullableInstanceArg :: Callable -> (Callable, Bool)
nonNullableInstanceArg c = case args c of
  inst:rest -> (c {args = inst {mayBeNull = False} : rest}, mayBeNull inst)
  [] -> (c, False)

-- | Generate the `MethodInfo` type and instance for the given method.
genMethodInfo :: Name -> Method -> ExcCodeGen ()
genMethodInfo n m =
    when (methodType m == OrdinaryMethod) $
      group $ do
        api <- findAPIByName n
        infoName <- methodInfoName n m
        let (callable, nullableInstance) =
              nonNullableInstanceArg . fixupCallerAllocates $ methodCallable m
        sig <- callableSignature callable (KnownForeignSymbol undefined) WithoutClosures
        bline $ "data " <> infoName
        let (obj, otherTypes) = case map snd (signatureArgTypes sig) of
              -- This should not happen, since ordinary methods always
              -- have the instance as first argument.
              [] -> error $ "Internal error: too few parameters! " ++ show m
              (obj':otherTypes') -> (obj', otherTypes')
            sigConstraint = "signature ~ (" <> T.intercalate " -> "
              (otherTypes ++ [signatureReturnType sig]) <> ")"

        hackageLink <- hackageModuleLink n
        let mn = methodName m
            mangled = lowerName (mn {name = name n <> "_" <> name mn})
            dbgInfo = dotModulePath (moduleLocation n api) <> "." <> mangled

        group $ do
          line $ "instance ("
            <> T.intercalate ", " (sigConstraint : signatureConstraints sig)
            <> ") => O.OverloadedMethod " <> infoName <> " " <> obj
            <> " signature where"
          if nullableInstance
            then indent $ line $ "overloadedMethod i = " <> mangled <> " (Just i)"
            else indent $ line $ "overloadedMethod = " <> mangled

        group $ do
          line $ "instance O.OverloadedMethodInfo " <> infoName <> " " <> obj
            <> " where"
          indent $ do
            line $ "overloadedMethodInfo = P.Just (O.ResolvedSymbolInfo {"
            indent $ do
              line $ "O.resolvedSymbolName = \"" <> dbgInfo <> "\","
              line $ "O.resolvedSymbolURL = \"" <>
                hackageLink <> "#v:" <> mangled <> "\""
              line $ "})"

        export (NamedSubsection MethodSection $ lowerName mn) infoName

-- | Generate a method info that is not actually callable, but rather
-- gives a type error when trying to use it.
genUnsupportedMethodInfo :: Name -> Method -> CodeGen e ()
genUnsupportedMethodInfo n m = do
  infoName <- methodInfoName n m
  line $ "-- XXX: Dummy instance, since code generation failed.\n"
           <> "-- Please file a bug at http://github.com/haskell-gi/haskell-gi."
  bline $ "data " <> infoName
  group $ do
    line $ "instance (p ~ (), o ~ O.UnsupportedMethodError \""
      <> lowerName (methodName m) <> "\" " <> name n
      <> ") => O.OverloadedMethod " <> infoName <> " o p where"
    indent $ line $ "overloadedMethod = undefined"

  group $ do
    line $ "instance (o ~ O.UnsupportedMethodError \""
      <> lowerName (methodName m) <> "\" " <> name n
      <> ") => O.OverloadedMethodInfo " <> infoName <> " o where"
    indent $ line $ "overloadedMethodInfo = undefined"

  export ToplevelSection infoName