git.iko.soy/rowtype-yoga/purescript-native
on golang help sign in
Merge pull request #1863 from hdgarrood/infer-docs-types-2

Infer types in psc-{docs,publish}

commit021a35164fb7347bc668ab52577a79456d92a09f authorPhil Freeman <paf31@cantab.net> dateSat 20 Feb 2016, 17:14 (11 years ago) parent32a54fd7d936d3
12 files changed+247−115
examples/docs/src/ExplicitTypeSignatures.purs+16 psc-docs/Main.hs+6−6 purescript.cabal+1−1 src/Language/PureScript/Docs.hs+1−1 src/Language/PureScript/Docs/AsMarkdown.hs+7−9 src/Language/PureScript/Docs/Convert.hs+139−30 src/Language/PureScript/Docs/Convert/Single.hs+7−3 src/Language/PureScript/Docs/ParseAndDesugar.hs → src/Language/PureScript/Docs/ParseAndBookmark.hs+16−53 src/Language/PureScript/Docs/Types.hs+3−3 src/Language/PureScript/Publish.hs+4−4 src/Language/PureScript/Sugar/Names.hs+2−1 tests/TestDocs.hs+45−4
examples/docs/src/ExplicitTypeSignatures.pursnew file+16
@@ -0,0 +1,16 @@
1+
2+module ExplicitTypeSignatures where
3+
4+-- This should use the explicit type signature so that the type variable name
5+-- is preserved.
6+explicit :: forall something. something -> something
7+explicit x
8+ | true = x
9+ | false = x
10+
11+-- This should use the inferred type.
12+anInt :: _
13+anInt = 0
14+
15+-- This should infer a type.
16+aNumber = 1.0
psc-docs/Main.hs+6−6
@@ -69,19 +69,19 @@ docgen (PSCDocsOptions fmt inputGlob output) = do
6969 Etags -> dumpTags input dumpEtags
7070 Ctags -> dumpTags input dumpCtags
7171 Markdown -> do
72− ms <- runExceptT (D.parseAndDesugar input []
73− >>= ((\(ms, _, env) -> D.convertModulesInPackage env ms)))
72+ ms <- runExceptT (D.parseAndBookmark input []
73+ >>= (fst >>> D.convertModulesInPackage))
7474 >>= successOrExit
7575
7676 case output of
7777 EverythingToStdOut ->
7878 putStrLn (D.runDocs (D.modulesAsMarkdown ms))
7979 ToStdOut names -> do
80− let (ms', missing) = takeByName ms (map P.runModuleName names)
80+ let (ms', missing) = takeByName ms names
8181 guardMissing missing
8282 putStrLn (D.runDocs (D.modulesAsMarkdown ms'))
8383 ToFiles names -> do
84− let (ms', missing) = takeByName' ms (map (first P.runModuleName) names)
84+ let (ms', missing) = takeByName' ms names
8585 guardMissing missing
8686
8787 let ms'' = groupBy ((==) `on` fst) . sortBy (compare `on` fst) $ map swap ms'
@@ -93,12 +93,12 @@ docgen (PSCDocsOptions fmt inputGlob output) = do
9393 where
9494 guardMissing [] = return ()
9595 guardMissing [mn] = do
96− hPutStrLn stderr ("psc-docs: error: unknown module \"" ++ mn ++ "\"")
96+ hPutStrLn stderr ("psc-docs: error: unknown module \"" ++ P.runModuleName mn ++ "\"")
9797 exitFailure
9898 guardMissing mns = do
9999 hPutStrLn stderr "psc-docs: error: unknown modules:"
100100 forM_ mns $ \mn ->
101− hPutStrLn stderr (" * " ++ mn)
101+ hPutStrLn stderr (" * " ++ P.runModuleName mn)
102102 exitFailure
103103
104104 successOrExit :: Either P.MultipleErrors a -> IO a
purescript.cabal+1−1
@@ -176,7 +176,7 @@ library
176176 Language.PureScript.Docs.RenderedCode.Types
177177 Language.PureScript.Docs.RenderedCode.Render
178178 Language.PureScript.Docs.AsMarkdown
179− Language.PureScript.Docs.ParseAndDesugar
179+ Language.PureScript.Docs.ParseAndBookmark
180180 Language.PureScript.Docs.Utils.MonoidExtras
181181
182182 Language.PureScript.Publish
src/Language/PureScript/Docs.hs+1−1
@@ -11,4 +11,4 @@ import Language.PureScript.Docs.RenderedCode.Types as Docs
1111import Language.PureScript.Docs.RenderedCode.Render as Docs
1212import Language.PureScript.Docs.Convert as Docs
1313import Language.PureScript.Docs.Render as Docs
14−import Language.PureScript.Docs.ParseAndDesugar as Docs
14+import Language.PureScript.Docs.ParseAndBookmark as Docs
src/Language/PureScript/Docs/AsMarkdown.hs+7−9
@@ -31,31 +31,29 @@ import qualified Language.PureScript.Docs.Render as Render
3131renderModulesAsMarkdown ::
3232 (Functor m, Applicative m,
3333 MonadError P.MultipleErrors m) =>
34− P.Env ->
3534 [P.Module] ->
3635 m String
37−renderModulesAsMarkdown env =
38− fmap (runDocs . modulesAsMarkdown) . Convert.convertModules env
36+renderModulesAsMarkdown =
37+ fmap (runDocs . modulesAsMarkdown) . Convert.convertModules
3938
4039modulesAsMarkdown :: [Module] -> Docs
4140modulesAsMarkdown = mapM_ moduleAsMarkdown
4241
4342moduleAsMarkdown :: Module -> Docs
4443moduleAsMarkdown Module{..} = do
45− headerLevel 2 $ "Module " ++ modName
44+ headerLevel 2 $ "Module " ++ P.runModuleName modName
4645 spacer
4746 for_ modComments tell'
4847 mapM_ (declAsMarkdown modName) modDeclarations
4948 spacer
5049 for_ modReExports $ \(mn, decls) -> do
51− let modName' = P.runModuleName mn
52− headerLevel 3 $ "Re-exported from " ++ modName' ++ ":"
50+ headerLevel 3 $ "Re-exported from " ++ P.runModuleName mn ++ ":"
5351 spacer
54− mapM_ (declAsMarkdown modName') decls
52+ mapM_ (declAsMarkdown mn) decls
5553
56−declAsMarkdown :: String -> Declaration -> Docs
54+declAsMarkdown :: P.ModuleName -> Declaration -> Docs
5755declAsMarkdown mn decl@Declaration{..} = do
58− let options = defaultRenderTypeOptions { currentModule = Just (P.moduleNameFromString mn) }
56+ let options = defaultRenderTypeOptions { currentModule = Just mn }
5957 headerLevel 4 (ticks declTitle)
6058 spacer
6159
src/Language/PureScript/Docs/Convert.hs+139−30
@@ -16,12 +16,17 @@ module Language.PureScript.Docs.Convert
1616import Prelude ()
1717import Prelude.Compat
1818
19−import Control.Monad.Error.Class (MonadError)
20−import Control.Arrow ((&&&))
19+import Control.Arrow ((&&&), second)
2120import Control.Category ((>>>))
21+import Control.Monad
22+import Control.Monad.State (runStateT)
23+import Control.Monad.Writer.Strict (runWriterT)
24+import Control.Monad.Error.Class (MonadError)
2225import qualified Data.Map as Map
2326
27+import Text.Parsec (eof)
2428import qualified Language.PureScript as P
29+import qualified Language.PureScript.Constants as C
2530
2631import Language.PureScript.Docs.Types
2732import Language.PureScript.Docs.Convert.Single (convertSingleModule, collectBookmarks)
@@ -33,18 +38,17 @@ import Language.PureScript.Docs.Convert.ReExports (updateReExports)
3338-- documentation.
3439--
3540convertModulesInPackage ::
36− (Functor m, MonadError P.MultipleErrors m) =>
37− P.Env ->
41+ (Functor m, Applicative m, MonadError P.MultipleErrors m) =>
3842 [InPackage P.Module] ->
3943 m [Module]
40−convertModulesInPackage env modules =
44+convertModulesInPackage modules =
4145 go modules
4246 where
4347 localNames =
44− map (P.runModuleName . P.getModuleName) (takeLocals modules)
48+ map P.getModuleName (takeLocals modules)
4549 go =
4650 map ignorePackage
47− >>> convertModules env
51+ >>> convertModules
4852 >>> fmap (filter ((`elem` localNames) . modName))
4953
5054-- |
@@ -53,35 +57,140 @@ convertModulesInPackage env modules =
5357-- imports/exports information about the list of modules, which is needed for
5458-- documenting re-exports.
5559--
56−-- Preconditions:
60+-- Note that the whole module dependency graph must be included in the list; if
61+-- some modules import things from other modules, then those modules must also
62+-- be included.
5763--
58−-- * If any module in the list re-exports documentation from other
59−-- modules, those modules must also be included in the list.
60−-- * The modules passed must have had names desugared and re-exports
61−-- elaborated first.
62−--
63−-- If either of these are not satisfied, an internal error will be thrown. To
64−-- avoid this, it is recommended to use
65−-- Language.PureScript.Docs.ParseAndDesugar to construct the inputs to this
66−-- function.
64+-- For value declarations, if explicit type signatures are omitted, or a
65+-- wildcard type is used, then we typecheck the modules and use the inferred
66+-- types.
6767--
6868convertModules ::
69− (Functor m, MonadError P.MultipleErrors m) =>
70− P.Env ->
69+ (Functor m, Applicative m, MonadError P.MultipleErrors m) =>
7170 [P.Module] ->
7271 m [Module]
73−convertModules env =
74− P.sortModules >>> fmap (convertSorted env . fst)
72+convertModules =
73+ P.sortModules
74+ >>> fmap (fst >>> map importPrim)
75+ >=> convertSorted
76+
77+importPrim :: P.Module -> P.Module
78+importPrim = P.addDefaultImport (P.ModuleName [P.ProperName C.prim])
7579
7680-- |
7781-- Convert a sorted list of modules.
7882--
79−convertSorted :: P.Env -> [P.Module] -> [Module]
80−convertSorted env modules =
81− let
82− traversalOrder =
83− map P.getModuleName modules
84− moduleMap =
85− Map.fromList $ map (P.getModuleName &&& convertSingleModule) modules
86− in
87− Map.elems (updateReExports env traversalOrder moduleMap)
83+convertSorted ::
84+ (Functor m, Applicative m, MonadError P.MultipleErrors m) =>
85+ [P.Module] ->
86+ m [Module]
87+convertSorted modules = do
88+ (env, convertedModules) <- second (map convertSingleModule) <$> partiallyDesugar modules
89+
90+ modulesWithTypes <- typeCheckIfNecessary modules convertedModules
91+ let moduleMap = Map.fromList (map (modName &&& id) modulesWithTypes)
92+
93+ let traversalOrder = map P.getModuleName modules
94+ pure (Map.elems (updateReExports env traversalOrder moduleMap))
95+
96+-- |
97+-- If any exported value declarations have either wildcard type signatures, or
98+-- none at all, then typecheck in order to fill them in with the inferred
99+-- types.
100+--
101+typeCheckIfNecessary ::
102+ (Functor m, Applicative m, MonadError P.MultipleErrors m) =>
103+ [P.Module] ->
104+ [Module] ->
105+ m [Module]
106+typeCheckIfNecessary modules convertedModules =
107+ if any hasWildcards convertedModules
108+ then go
109+ else pure convertedModules
110+
111+ where
112+ hasWildcards =
113+ any ((==) (ValueDeclaration P.TypeWildcard) . declInfo) . modDeclarations
114+
115+ go = do
116+ checkEnv <- snd <$> typeCheck modules
117+ pure (map (insertValueTypes checkEnv) convertedModules)
118+
119+-- |
120+-- Typechecks all the modules together. Also returns the final 'P.Environment',
121+-- which is useful for adding in inferred types where explicit declarations
122+-- were not provided.
123+--
124+typeCheck ::
125+ (Functor m, MonadError P.MultipleErrors m) =>
126+ [P.Module] ->
127+ m ([P.Module], P.Environment)
128+typeCheck =
129+ (P.desugar [] >=> check)
130+ >>> fmap (second P.checkEnv)
131+ >>> P.evalSupplyT 0
132+ >>> ignoreWarnings
133+
134+ where
135+ check ms =
136+ runStateT
137+ (traverse P.typeCheckModule ms)
138+ (P.emptyCheckState P.initEnvironment)
139+
140+ ignoreWarnings =
141+ fmap fst . runWriterT
142+
143+-- |
144+-- Updates all the types of the ValueDeclarations inside the module based on
145+-- their types inside the given Environment.
146+--
147+insertValueTypes ::
148+ P.Environment -> Module -> Module
149+insertValueTypes env m =
150+ m { modDeclarations = map go (modDeclarations m) }
151+ where
152+ go (d@Declaration { declInfo = ValueDeclaration P.TypeWildcard }) =
153+ let
154+ ident = parseIdent (declTitle d)
155+ ty = lookupName ident
156+ in
157+ d { declInfo = ValueDeclaration ty }
158+ go other =
159+ other
160+
161+ parseIdent =
162+ either (err . ("failed to parse Ident: " ++)) id . runParser P.parseIdent
163+
164+ lookupName name =
165+ let key = (modName m, name)
166+ in case Map.lookup key (P.names env) of
167+ Just (ty, _, _) ->
168+ ty
169+ Nothing ->
170+ err ("name not found: " ++ show key)
171+
172+ err msg =
173+ P.internalError ("Docs.Convert.insertValueTypes: " ++ msg)
174+
175+runParser :: P.TokenParser a -> String -> Either String a
176+runParser p s = either (Left . show) Right $ do
177+ ts <- P.lex "" s
178+ P.runTokenParser "" (p <* eof) ts
179+
180+-- |
181+-- Partially desugar modules so that they are suitable for extracting
182+-- documentation information from.
183+--
184+partiallyDesugar ::
185+ (Functor m, Applicative m, MonadError P.MultipleErrors m) =>
186+ [P.Module]
187+ -> m (P.Env, [P.Module])
188+partiallyDesugar = P.evalSupplyT 0 . desugar'
189+ where
190+ desugar' =
191+ traverse P.desugarDoModule
192+ >=> P.desugarCasesModule
193+ >=> P.desugarTypeDeclarationsModule
194+ >=> ignoreWarnings . P.desugarImportsWithEnv []
195+
196+ ignoreWarnings = fmap fst . runWriterT
src/Language/PureScript/Docs/Convert/Single.hs+7−3
@@ -29,7 +29,7 @@ import Language.PureScript.Docs.Types
2929--
3030convertSingleModule :: P.Module -> Module
3131convertSingleModule m@(P.Module _ coms moduleName _ _) =
32− Module (P.runModuleName moduleName) comments (declarations m) []
32+ Module moduleName comments (declarations m) []
3333 where
3434 comments = convertComments coms
3535 declarations =
@@ -109,7 +109,7 @@ addDefaultFixity decl@Declaration{..}
109109 defaultFixity = P.Fixity P.Infixl (-1)
110110
111111getDeclarationTitle :: P.Declaration -> Maybe String
112−getDeclarationTitle (P.TypeDeclaration name _) = Just (P.showIdent name)
112+getDeclarationTitle (P.ValueDeclaration name _ _ _) = Just (P.showIdent name)
113113getDeclarationTitle (P.ExternDeclaration name _) = Just (P.showIdent name)
114114getDeclarationTitle (P.DataDeclaration _ name _ _) = Just (P.runProperName name)
115115getDeclarationTitle (P.ExternDataDeclaration name _) = Just (P.runProperName name)
@@ -135,8 +135,12 @@ basicDeclaration :: String -> DeclarationInfo -> Maybe IntermediateDeclaration
135135basicDeclaration title info = Just $ Right $ mkDeclaration title info
136136
137137convertDeclaration :: P.Declaration -> String -> Maybe IntermediateDeclaration
138−convertDeclaration (P.TypeDeclaration _ ty) title =
138+convertDeclaration (P.ValueDeclaration _ _ _ (Right (P.TypedValue _ _ ty))) title =
139139 basicDeclaration title (ValueDeclaration ty)
140+convertDeclaration (P.ValueDeclaration _ _ _ _) title =
141+ -- If no explicit type declaration was provided, insert a wildcard, so that
142+ -- the actual type will be added during type checking.
143+ basicDeclaration title (ValueDeclaration P.TypeWildcard)
140144convertDeclaration (P.ExternDeclaration _ ty) title =
141145 basicDeclaration title (ValueDeclaration ty)
142146convertDeclaration (P.DataDeclaration dtype _ args ctors) title =
src/Language/PureScript/Docs/ParseAndDesugar.hs → src/Language/PureScript/Docs/ParseAndBookmark.hsrenamed+16−53
@@ -1,8 +1,8 @@
11{-# LANGUAGE TupleSections #-}
22{-# LANGUAGE FlexibleContexts #-}
33
4−module Language.PureScript.Docs.ParseAndDesugar
5− ( parseAndDesugar
4+module Language.PureScript.Docs.ParseAndBookmark
5+ ( parseAndBookmark
66 ) where
77
88import Prelude ()
@@ -10,16 +10,13 @@ import Prelude.Compat
1010
1111import qualified Data.Map as M
1212import Control.Arrow (first)
13−import Control.Monad
1413
15−import Control.Monad.Writer.Strict (runWriterT)
1614import Control.Monad.Error.Class (MonadError(..))
1715import Control.Monad.IO.Class (MonadIO(..))
1816
1917import Web.Bower.PackageMeta (PackageName)
2018
2119import qualified Language.PureScript as P
22−import qualified Language.PureScript.Constants as C
2320import Language.PureScript.Docs.Types
2421import Language.PureScript.Docs.Convert (collectBookmarks)
2522
@@ -35,23 +32,18 @@ import Language.PureScript.Docs.Convert (collectBookmarks)
3532-- * Parse all of the input and dependency source files
3633-- * Associate each dependency module with its package name, thereby
3734-- distinguishing these from local modules
38−-- * Partially desugar all of the resulting modules (just enough for
39−-- producing documentation from them)
4035-- * Collect a list of bookmarks from the whole set of source files
41−-- * Return the desugared modules, the bookmarks, and the imports/exports
42−-- Env (which is needed for producing documentation).
43−parseAndDesugar ::
36+-- * Return the parsed modules and the bookmarks
37+parseAndBookmark ::
4438 (Functor m, Applicative m, MonadError P.MultipleErrors m, MonadIO m) =>
4539 [FilePath]
4640 -> [(PackageName, FilePath)]
47− -> m ([InPackage P.Module], [Bookmark], P.Env)
48−parseAndDesugar inputFiles depsFiles = do
41+ -> m ([InPackage P.Module], [Bookmark])
42+parseAndBookmark inputFiles depsFiles = do
4943 inputFiles' <- traverse (parseAs Local) inputFiles
5044 depsFiles' <- traverse (\(pkgName, f) -> parseAs (FromDep pkgName) f) depsFiles
5145
52− ms <- parseFiles (inputFiles' ++ depsFiles')
53− ms' <- sortModules (map snd ms)
54− desugarWithBookmarks ms ms'
46+ addBookmarks <$> parseFiles (inputFiles' ++ depsFiles')
5547
5648parseFiles ::
5749 (MonadError P.MultipleErrors m, MonadIO m) =>
@@ -60,29 +52,16 @@ parseFiles ::
6052parseFiles =
6153 throwLeft . P.parseModulesFromFiles fileInfoToString
6254
63−sortModules ::
64− (Functor m, MonadError P.MultipleErrors m, MonadIO m) =>
65− [P.Module]
66− -> m [P.Module]
67−sortModules =
68− fmap fst . throwLeft . sortModules' . map importPrim
69− where
70− sortModules' :: [P.Module] -> Either P.MultipleErrors ([P.Module], P.ModuleGraph)
71− sortModules' = P.sortModules
72−
73−desugarWithBookmarks ::
74− (MonadError P.MultipleErrors m, MonadIO m) =>
55+addBookmarks ::
7556 [(FileInfo, P.Module)]
76− -> [P.Module]
77− -> m ([InPackage P.Module], [Bookmark], P.Env)
78−desugarWithBookmarks msInfo msSorted = do
79− (env, msDesugared) <- throwLeft (desugar msSorted)
80−
81− let msDeps = getDepsModuleNames (map (\(fp, m) -> (,m) <$> fp) msInfo)
82− msPackages = map (addPackage msDeps) msDesugared
83− bookmarks = concatMap collectBookmarks msPackages
84−
85− return (msPackages, bookmarks, env)
57+ -> ([InPackage P.Module], [Bookmark])
58+addBookmarks msInfo =
59+ let
60+ msDeps = getDepsModuleNames (map (\(fp, m) -> (,m) <$> fp) msInfo)
61+ msPackages = map (addPackage msDeps . snd) msInfo
62+ bookmarks = concatMap collectBookmarks msPackages
63+ in
64+ (msPackages, bookmarks)
8665
8766throwLeft :: (MonadError l m) => Either l r -> m r
8867throwLeft = either throwError return
@@ -100,22 +79,6 @@ fileInfoToString :: FileInfo -> FilePath
10079fileInfoToString (Local fn) = fn
10180fileInfoToString (FromDep _ fn) = fn
10281
103−importPrim :: P.Module -> P.Module
104−importPrim = P.addDefaultImport (P.ModuleName [P.ProperName C.prim])
105−
106−desugar ::
107− (Functor m, Applicative m, MonadError P.MultipleErrors m) =>
108− [P.Module]
109− -> m (P.Env, [P.Module])
110−desugar = P.evalSupplyT 0 . desugar'
111− where
112− desugar' =
113− traverse P.desugarDoModule
114− >=> P.desugarCasesModule
115− >=> ignoreWarnings . P.desugarImportsWithEnv []
116−
117− ignoreWarnings m = liftM fst (runWriterT m)
118−
11982parseFile :: FilePath -> IO (FilePath, String)
12083parseFile input' = (,) input' <$> readFile input'
12184
src/Language/PureScript/Docs/Types.hs+3−3
@@ -73,7 +73,7 @@ packageName :: Package a -> PackageName
7373packageName = bowerName . pkgMeta
7474
7575data Module = Module
76− { modName :: String
76+ { modName :: P.ModuleName
7777 , modComments :: Maybe String
7878 , modDeclarations :: [Declaration]
7979 -- Re-exported values from other modules
@@ -346,7 +346,7 @@ parseVersion' str =
346346
347347asModule :: Parse PackageError Module
348348asModule =
349− Module <$> key "name" asString
349+ Module <$> key "name" (P.moduleNameFromString <$> asString)
350350 <*> key "comments" (perhaps asString)
351351 <*> key "declarations" (eachInArray asDeclaration)
352352 <*> key "reExports" (eachInArray asReExport)
@@ -512,7 +512,7 @@ instance A.ToJSON NotYetKnown where
512512
513513instance A.ToJSON Module where
514514 toJSON Module{..} =
515− A.object [ "name" .= modName
515+ A.object [ "name" .= P.runModuleName modName
516516 , "comments" .= modComments
517517 , "declarations" .= modDeclarations
518518 , "reExports" .= map toObj modReExports
src/Language/PureScript/Publish.hs+4−4
@@ -146,15 +146,15 @@ preparePackage' opts = do
146146getModulesAndBookmarks :: PrepareM ([D.Bookmark], [D.Module])
147147getModulesAndBookmarks = do
148148 (inputFiles, depsFiles) <- liftIO getInputAndDepsFiles
149− (modules', bookmarks, env) <- parseAndDesugar inputFiles depsFiles
149+ (modules', bookmarks) <- parseAndBookmark inputFiles depsFiles
150150
151− case runExcept (D.convertModulesInPackage env modules') of
151+ case runExcept (D.convertModulesInPackage modules') of
152152 Right modules -> return (bookmarks, modules)
153153 Left err -> userError (CompileError err)
154154
155155 where
156− parseAndDesugar inputFiles depsFiles = do
157− r <- liftIO . runExceptT $ D.parseAndDesugar inputFiles depsFiles
156+ parseAndBookmark inputFiles depsFiles = do
157+ r <- liftIO . runExceptT $ D.parseAndBookmark inputFiles depsFiles
158158 case r of
159159 Right r' ->
160160 return r'
src/Language/PureScript/Sugar/Names.hs+2−1
@@ -17,6 +17,7 @@ import Prelude.Compat
1717import Data.List (find, nub)
1818import Data.Maybe (fromMaybe, mapMaybe)
1919
20+import Control.Arrow (first)
2021import Control.Monad
2122import Control.Monad.Error.Class (MonadError(..))
2223import Control.Monad.Writer (MonadWriter(..), censor)
@@ -54,7 +55,7 @@ desugarImportsWithEnv
5455desugarImportsWithEnv externs modules = do
5556 env <- silence $ foldM externsEnv primEnv externs
5657 modules' <- traverse updateExportRefs modules
57− (modules'', env') <- foldM updateEnv ([], env) modules'
58+ (modules'', env') <- first reverse <$> foldM updateEnv ([], env) modules'
5859 (env',) <$> traverse (renameInModule' env') modules''
5960 where
6061 silence :: m a -> m a
tests/TestDocs.hs+45−4
@@ -12,6 +12,7 @@ import Data.Version (Version(..))
1212import Data.Monoid ((<>))
1313import Data.Maybe (fromMaybe)
1414import Data.List ((\\))
15+import Data.Monoid
1516import Data.Foldable
1617import System.Exit
1718
@@ -32,8 +33,8 @@ main :: IO ()
3233main = do
3334 pushd "examples/docs" $ do
3435 Docs.Package{..} <- Publish.preparePackage publishOpts
35− forM_ testCases $ \(mn, pragmas) ->
36− let mdl = takeJust ("module not found in docs: " ++ mn)
36+ forM_ testCases $ \(P.moduleNameFromString -> mn, pragmas) ->
37+ let mdl = takeJust ("module not found in docs: " ++ P.runModuleName mn)
3738 (find ((==) mn . Docs.modName) pkgModules)
3839 in forM_ pragmas (flip runAssertionIO mdl)
3940
@@ -52,8 +53,16 @@ data Assertion
5253 -- | Assert that a particular declaration has a particular type class
5354 -- constraint.
5455 | ShouldBeConstrained P.ModuleName String String
56+ -- | Assert that a particular value declaration exists, and its type
57+ -- satisfies the given predicate.
58+ | ValueShouldHaveTypeSignature P.ModuleName String (ShowFn (P.Type -> Bool))
5559 deriving (Show)
5660
61+newtype ShowFn a = ShowFn a
62+
63+instance Show (ShowFn a) where
64+ show _ = "<function>"
65+
5766data AssertionFailure
5867 -- | A declaration was not documented, but should have been
5968 = NotDocumented P.ModuleName String
@@ -68,6 +77,11 @@ data AssertionFailure
6877 -- | A declaration had the wrong "type" (ie, value, type, type class)
6978 -- Fields: declaration title, expected "type", actual "type".
7079 | WrongDeclarationType P.ModuleName String String String
80+ -- | A value declaration had the wrong type (in the sense of "type
81+ -- checking"), eg, because the inferred type was used when the explicit type
82+ -- should have been.
83+ -- Fields: module name, declaration name, actual type.
84+ | ValueDeclarationWrongType P.ModuleName String P.Type
7185 deriving (Show)
7286
7387data AssertionResult
@@ -117,9 +131,24 @@ runAssertion assertion Docs.Module{..} =
117131 Fail (WrongDeclarationType mn decl "value"
118132 (Docs.declInfoToString declInfo))
119133
134+ ValueShouldHaveTypeSignature mn decl (ShowFn tyPredicate) ->
135+ case find ((==) decl . Docs.declTitle) (declarationsFor mn) of
136+ Nothing ->
137+ Fail (NotDocumented mn decl)
138+ Just Docs.Declaration{..} ->
139+ case declInfo of
140+ Docs.ValueDeclaration ty ->
141+ if tyPredicate ty
142+ then Pass
143+ else Fail
144+ (ValueDeclarationWrongType mn decl ty)
145+ _ ->
146+ Fail (WrongDeclarationType mn decl "value"
147+ (Docs.declInfoToString declInfo))
148+
120149 where
121150 declarationsFor mn =
122− if P.runModuleName mn == modName
151+ if mn == modName
123152 then modDeclarations
124153 else fromMaybe [] (lookup mn modReExports)
125154
@@ -146,7 +175,7 @@ checkConstrained ty tyClass =
146175
147176runAssertionIO :: Assertion -> Docs.Module -> IO ()
148177runAssertionIO assertion mdl = do
149− putStrLn ("In " ++ Docs.modName mdl ++ ": " ++ show assertion)
178+ putStrLn ("In " ++ P.runModuleName (Docs.modName mdl) ++ ": " ++ show assertion)
150179 case runAssertion assertion mdl of
151180 Pass -> pure ()
152181 Fail reason -> do
@@ -223,7 +252,19 @@ testCases =
223252 , ("NewOperators",
224253 [ ShouldBeDocumented (n "NewOperators2") "(>>>)" []
225254 ])
255+
256+ , ("ExplicitTypeSignatures",
257+ [ ValueShouldHaveTypeSignature (n "ExplicitTypeSignatures") "explicit" (ShowFn (hasTypeVar "something"))
258+ , ValueShouldHaveTypeSignature (n "ExplicitTypeSignatures") "anInt" (ShowFn ((==) P.tyInt))
259+ , ValueShouldHaveTypeSignature (n "ExplicitTypeSignatures") "aNumber" (ShowFn ((==) P.tyNumber))
260+ ])
226261 ]
227262
228263 where
229264 n = P.moduleNameFromString
265+
266+ hasTypeVar varName =
267+ getAny . P.everythingOnTypes (<>) (Any . isVar varName)
268+
269+ isVar varName (P.TypeVar name) | varName == name = True
270+ isVar _ _ = False