Skip to content

Commit 950f184

Browse files
parsonsmatthdgarrood
authored andcommitted
Fix error on missing type class name with non-exported superclass (purescript#3132) (purescript#3173)
Resolves purescript#3132. Attempting to export a type class where the superclass is defined in the same module but not exported now triggers a TransitiveExportError.
1 parent 3679da4 commit 950f184

4 files changed

Lines changed: 91 additions & 14 deletions

File tree

examples/failing/3132.purs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-- @shouldFailWith TransitiveExportError
2+
module Main (class C3) where
3+
4+
import Prelude
5+
6+
import Control.Monad.Eff (Eff)
7+
import Control.Monad.Eff.Console (CONSOLE, log)
8+
9+
class C1
10+
instance inst1 :: C1
11+
12+
class C1 <= C2 a
13+
14+
class (C2 a) <= C3 a b
15+
16+
main :: forall e. Eff (console :: CONSOLE | e) Unit
17+
main = do
18+
log "Done"

src/Language/PureScript/AST/Declarations.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,7 @@ data Expr
800800
--
801801
| AnonymousArgument
802802
-- |
803-
-- A typed hole that will be turned into a hint/error duing typechecking
803+
-- A typed hole that will be turned into a hint/error during typechecking
804804
--
805805
| Hole Text
806806
-- |

src/Language/PureScript/Environment.hs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ data Environment = Environment
3737
, typeSynonyms :: M.Map (Qualified (ProperName 'TypeName)) ([(Text, Maybe Kind)], Type)
3838
-- ^ Type synonyms currently in scope
3939
, typeClassDictionaries :: M.Map (Maybe ModuleName) (M.Map (Qualified (ProperName 'ClassName)) (M.Map (Qualified Ident) NamedDict))
40-
-- ^ Available type class dictionaries
40+
-- ^ Available type class dictionaries. When looking up 'Nothing' in the
41+
-- outer map, this returns the map of type class dictionaries in local
42+
-- scope (ie dictionaries brought in by a constrained type).
4143
, typeClasses :: M.Map (Qualified (ProperName 'ClassName)) TypeClassData
4244
-- ^ Type classes
4345
, kinds :: S.Set (Qualified (ProperName 'KindName))

src/Language/PureScript/TypeChecker.hs

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import Protolude (ordNub)
1414

1515
import Control.Monad (when, unless, void, forM)
1616
import Control.Monad.Error.Class (MonadError(..))
17-
import Control.Monad.State.Class (MonadState(..), modify)
17+
import Control.Monad.State.Class (MonadState(..), modify, gets)
1818
import Control.Monad.Supply.Class (MonadSupply)
1919
import Control.Monad.Writer.Class (MonadWriter(..))
2020
import Control.Lens ((^..), _1, _2)
@@ -432,31 +432,87 @@ typeCheckModule (Module ss coms mn decls (Just exps)) =
432432
warnAndRethrow (addHint (ErrorInModule mn)) $ do
433433
modify (\s -> s { checkCurrentModule = Just mn })
434434
decls' <- typeCheckAll mn exps decls
435+
checkSuperClassesAreExported <- getSuperClassExportCheck
435436
for_ exps $ \e -> do
436437
checkTypesAreExported e
437438
checkClassMembersAreExported e
438439
checkClassesAreExported e
440+
checkSuperClassesAreExported e
439441
return $ Module ss coms mn decls' (Just exps)
440442
where
443+
qualify' :: a -> Qualified a
444+
qualify' = Qualified (Just mn)
445+
446+
getSuperClassExportCheck = do
447+
classesToSuperClasses <- gets
448+
( M.map
449+
( S.fromList
450+
. filter (\(Qualified mn' _) -> mn' == Just mn)
451+
. fmap constraintClass
452+
. typeClassSuperclasses
453+
)
454+
. typeClasses
455+
. checkEnv
456+
)
457+
let
458+
-- A function that, given a class name, returns the set of
459+
-- transitive class dependencies that are defined in this
460+
-- module.
461+
transitiveSuperClassesFor
462+
:: Qualified (ProperName 'ClassName)
463+
-> S.Set (Qualified (ProperName 'ClassName))
464+
transitiveSuperClassesFor qname =
465+
untilSame
466+
(\s -> s <> foldMap (\n -> fromMaybe S.empty (M.lookup n classesToSuperClasses)) s)
467+
(fromMaybe S.empty (M.lookup qname classesToSuperClasses))
468+
469+
superClassesFor qname =
470+
fromMaybe S.empty (M.lookup qname classesToSuperClasses)
471+
472+
pure $ checkSuperClassExport superClassesFor transitiveSuperClassesFor
473+
moduleClassExports :: S.Set (Qualified (ProperName 'ClassName))
474+
moduleClassExports = S.fromList $ mapMaybe (\x -> case x of
475+
TypeClassRef _ name -> Just (qualify' name)
476+
_ -> Nothing) exps
477+
478+
untilSame :: Eq a => (a -> a) -> a -> a
479+
untilSame f a = let a' = f a in if a == a' then a else untilSame f a'
441480

442481
checkMemberExport :: (Type -> [DeclarationRef]) -> DeclarationRef -> m ()
443482
checkMemberExport extract dr@(TypeRef _ name dctors) = do
444483
env <- getEnv
445-
case M.lookup (Qualified (Just mn) name) (typeSynonyms env) of
446-
Nothing -> return ()
447-
Just (_, ty) -> checkExport dr extract ty
448-
case dctors of
449-
Nothing -> return ()
450-
Just dctors' -> for_ dctors' $ \dctor ->
451-
case M.lookup (Qualified (Just mn) dctor) (dataConstructors env) of
452-
Nothing -> return ()
453-
Just (_, _, ty, _) -> checkExport dr extract ty
454-
return ()
484+
for_ (M.lookup (qualify' name) (typeSynonyms env)) $ \(_, ty) ->
485+
checkExport dr extract ty
486+
for_ dctors $ \dctors' ->
487+
for_ dctors' $ \dctor ->
488+
for_ (M.lookup (qualify' dctor) (dataConstructors env)) $ \(_, _, ty, _) ->
489+
checkExport dr extract ty
455490
checkMemberExport extract dr@(ValueRef _ name) = do
456-
ty <- lookupVariable (Qualified (Just mn) name)
491+
ty <- lookupVariable (qualify' name)
457492
checkExport dr extract ty
458493
checkMemberExport _ _ = return ()
459494

495+
checkSuperClassExport
496+
:: (Qualified (ProperName 'ClassName) -> S.Set (Qualified (ProperName 'ClassName)))
497+
-> (Qualified (ProperName 'ClassName) -> S.Set (Qualified (ProperName 'ClassName)))
498+
-> DeclarationRef
499+
-> m ()
500+
checkSuperClassExport superClassesFor transitiveSuperClassesFor dr@(TypeClassRef drss className) = do
501+
let superClasses = superClassesFor (qualify' className)
502+
-- thanks to laziness, the computation of the transitive
503+
-- superclasses defined in-module will only occur if we actually
504+
-- throw the error. Constructing the full set of transitive
505+
-- superclasses is likely to be costly for every single term.
506+
transitiveSuperClasses = transitiveSuperClassesFor (qualify' className)
507+
unexported = S.difference superClasses moduleClassExports
508+
unless (null unexported)
509+
. throwError . errorMessage' drss
510+
. TransitiveExportError dr
511+
. map (TypeClassRef drss . disqualify)
512+
$ toList transitiveSuperClasses
513+
checkSuperClassExport _ _ _ =
514+
return ()
515+
460516
checkExport :: DeclarationRef -> (Type -> [DeclarationRef]) -> Type -> m ()
461517
checkExport dr extract ty = case filter (not . exported) (extract ty) of
462518
[] -> return ()
@@ -512,3 +568,4 @@ typeCheckModule (Module ss coms mn decls (Just exps)) =
512568
extractMemberName (TypeDeclaration td) = tydeclIdent td
513569
extractMemberName _ = internalError "Unexpected declaration in typeclass member list"
514570
checkClassMembersAreExported _ = return ()
571+

0 commit comments

Comments
 (0)