git.iko.soy/rowtype-yoga/purescript-native
on golang help sign in
Merge pull request #1676 from purescript/dupe-ref-warnings

Warn on duplicate refs in import or export lists

commita365b87e264ef5d15f83ddb047fb09e27a793990 authorGary Burgess <gary.burgess@gmail.com> dateMon 30 Nov 2015, 18:06 (11 years ago) parent948e45d4ed379b
5 files changed+115−19
src/Language/PureScript/AST/Declarations.hs+25 src/Language/PureScript/Errors.hs+10 src/Language/PureScript/Linter/Imports.hs+2−2 src/Language/PureScript/Sugar/Names/Exports.hs+20−1 src/Language/PureScript/Sugar/Names/Imports.hs+58−16
src/Language/PureScript/AST/Declarations.hs+25
@@ -15,6 +15,7 @@
1515{-# LANGUAGE DeriveDataTypeable #-}
1616{-# LANGUAGE ScopedTypeVariables #-}
1717{-# LANGUAGE TemplateHaskell #-}
18+{-# LANGUAGE LambdaCase #-}
1819
1920module Language.PureScript.AST.Declarations where
2021
@@ -22,6 +23,8 @@ import Prelude ()
2223import Prelude.Compat
2324
2425import Data.Aeson.TH
26+import Data.List (nub, (\\))
27+import Data.Maybe (mapMaybe)
2528
2629import qualified Data.Data as D
2730import qualified Data.Map as M
@@ -93,6 +96,28 @@ isModuleRef :: DeclarationRef -> Bool
9396isModuleRef (ModuleRef _) = True
9497isModuleRef _ = False
9598
99+-- |
100+-- Finds duplicate values in a list of declaration refs. The returned values
101+-- are the duplicate refs with data constructors elided, and then a separate
102+-- list of duplicate data constructors.
103+--
104+findDuplicateRefs :: [DeclarationRef] -> ([DeclarationRef], [ProperName])
105+findDuplicateRefs refs =
106+ let positionless = stripPosInfo `map` refs
107+ simplified = simplifyTypeRefs `map` positionless
108+ dupeRefs = nub $ simplified \\ nub simplified
109+ dupeCtors = concat $ flip mapMaybe positionless $ \case
110+ TypeRef _ (Just dctors) ->
111+ let dupes = dctors \\ nub dctors
112+ in if null dupes then Nothing else Just dupes
113+ _ -> Nothing
114+ in (dupeRefs, dupeCtors)
115+ where
116+ stripPosInfo (PositionedDeclarationRef _ _ ref) = stripPosInfo ref
117+ stripPosInfo other = other
118+ simplifyTypeRefs (TypeRef pn _) = TypeRef pn Nothing
119+ simplifyTypeRefs other = other
120+
96121-- |
97122-- The data type which specifies type of import declaration
98123--
src/Language/PureScript/Errors.hs+10
@@ -146,6 +146,8 @@ data SimpleErrorMessage
146146 | RedundantUnqualifiedImport ModuleName ImportDeclarationType
147147 | DuplicateSelectiveImport ModuleName
148148 | DuplicateImport ModuleName ImportDeclarationType (Maybe ModuleName)
149+ | DuplicateImportRef String
150+ | DuplicateExportRef String
149151 | IntOutOfRange Integer String Integer Integer
150152 deriving (Show)
151153
@@ -285,6 +287,8 @@ errorCode em = case unwrapErrorMessage em of
285287 RedundantUnqualifiedImport{} -> "RedundantUnqualifiedImport"
286288 DuplicateSelectiveImport{} -> "DuplicateSelectiveImport"
287289 DuplicateImport{} -> "DuplicateImport"
290+ DuplicateImportRef{} -> "DuplicateImportRef"
291+ DuplicateExportRef{} -> "DuplicateExportRef"
288292 IntOutOfRange{} -> "IntOutOfRange"
289293
290294
@@ -790,6 +794,12 @@ prettyPrintSingleError full level e = do
790794 renderSimpleErrorMessage (DuplicateImport name imp qual) =
791795 line $ "Duplicate import of " ++ prettyPrintImport name imp qual
792796
797+ renderSimpleErrorMessage (DuplicateImportRef ref) =
798+ line $ "Import list contains multiple references to " ++ ref
799+
800+ renderSimpleErrorMessage (DuplicateExportRef ref) =
801+ line $ "Export list contains multiple references to " ++ ref
802+
793803 renderSimpleErrorMessage (IntOutOfRange value backend lo hi) =
794804 paras [ line $ "Integer value " ++ show value ++ " is out of range for the " ++ backend ++ " backend."
795805 , line $ "Acceptable values fall within the range " ++ show lo ++ " to " ++ show hi ++ " (inclusive)." ]
src/Language/PureScript/Linter/Imports.hs+2−2
@@ -8,7 +8,7 @@ import Prelude.Compat
88
99import qualified Data.Map as M
1010import Data.Maybe (mapMaybe)
11−import Data.List ((\\), find, intersect)
11+import Data.List ((\\), find, intersect, nub)
1212import Control.Monad.Error.Class (MonadError(..))
1313import Control.Monad.Writer.Class
1414import Control.Monad(unless,when)
@@ -50,7 +50,7 @@ findUnusedImports (Module _ _ _ mdecls mexports) env usedImps = do
5050 in case declType of
5151 Implicit -> when (null usedNames) $ tell $ errorMessage $ UnusedImport mni
5252 Explicit declrefs -> do
53− let idents = mapMaybe runDeclRef declrefs
53+ let idents = nub (mapMaybe runDeclRef declrefs)
5454 let diff = idents \\ usedNames
5555 case (length diff, length idents) of
5656 (0, _) -> return ()
src/Language/PureScript/Sugar/Names/Exports.hs+20−1
@@ -15,6 +15,7 @@
1515{-# LANGUAGE RankNTypes #-}
1616{-# LANGUAGE ScopedTypeVariables #-}
1717{-# LANGUAGE TupleSections #-}
18+{-# LANGUAGE LambdaCase #-}
1819
1920module Language.PureScript.Sugar.Names.Exports
2021 ( findExportable
@@ -29,6 +30,7 @@ import Data.Maybe (fromMaybe, mapMaybe)
2930import Data.Foldable (traverse_)
3031
3132import Control.Monad
33+import Control.Monad.Writer.Class (MonadWriter(..))
3234import Control.Monad.Error.Class (MonadError(..))
3335
3436import qualified Data.Map as M
@@ -66,14 +68,31 @@ findExportable (Module _ _ mn ds _) =
6668-- Resolves the exports for a module, filtering out members that have not been
6769-- exported and elaborating re-exports of other modules.
6870--
69−resolveExports :: forall m. (Applicative m, MonadError MultipleErrors m) => Env -> ModuleName -> Imports -> Exports -> [DeclarationRef] -> m Exports
71+resolveExports :: forall m. (Applicative m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) => Env -> ModuleName -> Imports -> Exports -> [DeclarationRef] -> m Exports
7072resolveExports env mn imps exps refs =
7173 rethrow (addHint (ErrorInModule mn)) $ do
7274 filtered <- filterModule mn exps refs
75+ let (dupeRefs, dupeDctors) = findDuplicateRefs refs
76+ warnDupeRefs dupeRefs
77+ warnDupeDctors dupeDctors
7378 foldM elaborateModuleExports filtered refs
7479
7580 where
7681
82+ warnDupeRefs :: [DeclarationRef] -> m ()
83+ warnDupeRefs = traverse_ $ \case
84+ TypeRef name _ -> warnDupe $ "type " ++ runProperName name
85+ ValueRef name -> warnDupe $ "value " ++ runIdent name
86+ TypeClassRef name -> warnDupe $ "class " ++ runProperName name
87+ ModuleRef name -> warnDupe $ "module " ++ runModuleName name
88+ _ -> return ()
89+
90+ warnDupeDctors :: [ProperName] -> m ()
91+ warnDupeDctors = traverse_ (warnDupe . ("data constructor " ++) . runProperName)
92+
93+ warnDupe :: String -> m ()
94+ warnDupe ref = tell . errorMessage $ DuplicateExportRef ref
95+
7796 -- Takes the current module's imports, the accumulated list of exports, and a
7897 -- `DeclarationRef` for an explicit export. When the ref refers to another
7998 -- module, export anything from the imports that matches for that module.
src/Language/PureScript/Sugar/Names/Imports.hs+58−16
@@ -14,6 +14,7 @@
1414{-# LANGUAGE FlexibleContexts #-}
1515{-# LANGUAGE ScopedTypeVariables #-}
1616{-# LANGUAGE TupleSections #-}
17+{-# LANGUAGE LambdaCase #-}
1718
1819module Language.PureScript.Sugar.Names.Imports
1920 ( resolveImports
@@ -24,9 +25,10 @@ module Language.PureScript.Sugar.Names.Imports
2425import Prelude ()
2526import Prelude.Compat
2627
27−import Data.List (find, delete)
28+import Data.List (find, delete, (\\))
2829import Data.Maybe (fromMaybe, isJust, isNothing, fromJust)
2930import Data.Foldable (traverse_, for_)
31+import Data.Traversable (for)
3032
3133import Control.Arrow (first)
3234import Control.Monad
@@ -64,33 +66,48 @@ findImports = foldM (go Nothing) M.empty
6466 checkImportRef (ModuleRef name) = throwError . errorMessage $ ImportHidingModule name
6567 checkImportRef _ = return ()
6668
69+type ImportDef = (Maybe SourceSpan, ImportDeclarationType, Maybe ModuleName)
70+
6771-- |
6872-- Constructs a set of imports for a module.
6973--
70−resolveImports :: (Applicative m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) => Env -> Module -> m Imports
74+resolveImports :: forall m. (Applicative m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) => Env -> Module -> m Imports
7175resolveImports env (Module _ _ currentModule decls _) =
7276 warnAndRethrow (addHint (ErrorInModule currentModule)) $ do
7377 imports <- findImports decls
7478
7579 for_ (M.toList imports) $ \(mn, imps) -> do
76− let unqual = filter (\(_, _, q) -> isJust q) (reverse imps)
7780
78− when (length imps > 1) $ for_ (selfCartesianSubset imps) $
79− \((_, t1, q1), (pos, t2, q2)) ->
80− when (t1 == t2 && q1 == q2) $
81− maybe id warnWithPosition pos $
82− tell . errorMessage $ DuplicateImport mn t2 q2
81+ -- Better ordering for the warnings: the list is in last-import-first
82+ -- order, but we want the first appearence of an import to be the primary,
83+ -- and warnings to appear for later imports
84+ let imps' = reverse imps
85+
86+ warned <- foldM (checkDuplicateImports mn) [] (selfCartesianSubset imps')
87+
88+ let unqual = filter (\(_, _, q) -> isJust q) (imps' \\ warned)
8389
84− when (length unqual > 1) $
85− case find (\(_, typ, _) -> typ == Implicit) unqual of
90+ warned' <- (warned ++) <$>
91+ if (length unqual < 2)
92+ then return []
93+ else case find (\(_, typ, _) -> typ == Implicit) unqual of
8694 Just i ->
87− for_ (delete i unqual) $ \(pos, typ, _) ->
88− maybe id warnWithPosition pos $
89− tell $ errorMessage $ RedundantUnqualifiedImport mn typ
95+ for (delete i unqual) $ \i'@(pos, typ, _) -> do
96+ warn pos $ RedundantUnqualifiedImport mn typ
97+ return i'
9098 Nothing ->
91− for_ (tail unqual) $ \(pos, _, _) ->
92− maybe id warnWithPosition pos $
93− tell $ errorMessage $ DuplicateSelectiveImport mn
99+ for (tail unqual) $ \i@(pos, _, _) -> do
100+ warn pos $ DuplicateSelectiveImport mn
101+ return i
102+
103+ for_ (imps' \\ warned') $ \(pos, typ, _) ->
104+ let (dupeRefs, dupeDctors) = findDuplicateRefs $ case typ of
105+ Explicit refs -> refs
106+ Hiding refs -> refs
107+ _ -> []
108+ in warnDupeRefs pos dupeRefs >> warnDupeDctors pos dupeDctors
109+
110+ return ()
94111
95112 let scope = M.insert currentModule [(Nothing, Implicit, Nothing)] imports
96113 foldM (resolveModuleImport currentModule env) nullImports (M.toList scope)
@@ -100,6 +117,31 @@ resolveImports env (Module _ _ currentModule decls _) =
100117 selfCartesianSubset (x : xs) = [(x, y) | y <- xs] ++ selfCartesianSubset xs
101118 selfCartesianSubset [] = []
102119
120+ checkDuplicateImports :: ModuleName -> [ImportDef] -> (ImportDef, ImportDef) -> m [ImportDef]
121+ checkDuplicateImports mn xs ((_, t1, q1), (pos, t2, q2)) =
122+ if (t1 == t2 && q1 == q2)
123+ then do
124+ warn pos $ DuplicateImport mn t2 q2
125+ return $ (pos, t2, q2) : xs
126+ else return xs
127+
128+ warnDupeRefs :: Maybe SourceSpan -> [DeclarationRef] -> m ()
129+ warnDupeRefs pos = traverse_ $ \case
130+ TypeRef name _ -> warnDupe pos $ "type " ++ runProperName name
131+ ValueRef name -> warnDupe pos $ "value " ++ runIdent name
132+ TypeClassRef name -> warnDupe pos $ "class " ++ runProperName name
133+ ModuleRef name -> warnDupe pos $ "module " ++ runModuleName name
134+ _ -> return ()
135+
136+ warnDupeDctors :: Maybe SourceSpan -> [ProperName] -> m ()
137+ warnDupeDctors pos = traverse_ (warnDupe pos . ("data constructor " ++) . runProperName)
138+
139+ warnDupe :: Maybe SourceSpan -> String -> m ()
140+ warnDupe pos ref = warn pos $ DuplicateImportRef ref
141+
142+ warn :: Maybe SourceSpan -> SimpleErrorMessage -> m ()
143+ warn pos msg = maybe id warnWithPosition pos $ tell . errorMessage $ msg
144+
103145-- | Constructs a set of imports for a single module import.
104146resolveModuleImport ::
105147 forall m. (Applicative m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) =>