module Language.PureScript.TypeChecker.Skolems
( newSkolemConstant
, introduceSkolemScope
, newSkolemScope
, skolemize
, skolemizeTypesInValue
, skolemEscapeCheck
) where
import Prelude.Compat
import Control.Monad.Error.Class (MonadError(..))
import Control.Monad.State.Class (MonadState(..), gets, modify)
import Data.Foldable (traverse_)
import Data.Functor.Identity (Identity(), runIdentity)
import Data.Set (Set, fromList, notMember)
import Data.Text (Text)
import Language.PureScript.AST
import Language.PureScript.Crash
import Language.PureScript.Errors
import Language.PureScript.Traversals (defS)
import Language.PureScript.TypeChecker.Monad
import Language.PureScript.Types
newSkolemConstant :: MonadState CheckState m => m Int
newSkolemConstant = do
s <- gets checkNextSkolem
modify $ \st -> st { checkNextSkolem = s + 1 }
return s
introduceSkolemScope :: MonadState CheckState m => Type a -> m (Type a)
introduceSkolemScope = everywhereOnTypesM go
where
go (ForAll ann ident mbK ty Nothing) = ForAll ann ident mbK ty <$> (Just <$> newSkolemScope)
go other = return other
newSkolemScope :: MonadState CheckState m => m SkolemScope
newSkolemScope = do
s <- gets checkNextSkolemScope
modify $ \st -> st { checkNextSkolemScope = s + 1 }
return $ SkolemScope s
skolemize :: a -> Text -> Int -> SkolemScope -> Type a -> Type a
skolemize ann ident sko scope = replaceTypeVars ident (Skolem ann ident sko scope)
skolemizeTypesInValue :: SourceAnn -> Text -> Int -> SkolemScope -> Expr -> Expr
skolemizeTypesInValue ann ident sko scope =
runIdentity . onExpr'
where
onExpr' :: Expr -> Identity Expr
(_, onExpr', _, _, _) = everywhereWithContextOnValuesM [] defS onExpr onBinder defS defS
onExpr :: [Text] -> Expr -> Identity ([Text], Expr)
onExpr sco (DeferredDictionary c ts)
| ident `notElem` sco = return (sco, DeferredDictionary c (map (skolemize ann ident sko scope) ts))
onExpr sco (TypedValue check val ty)
| ident `notElem` sco = return (sco ++ peelTypeVars ty, TypedValue check val (skolemize ann ident sko scope ty))
onExpr sco other = return (sco, other)
onBinder :: [Text] -> Binder -> Identity ([Text], Binder)
onBinder sco (TypedBinder ty b)
| ident `notElem` sco = return (sco ++ peelTypeVars ty, TypedBinder (skolemize ann ident sko scope ty) b)
onBinder sco other = return (sco, other)
peelTypeVars :: SourceType -> [Text]
peelTypeVars (ForAll _ i _ ty _) = i : peelTypeVars ty
peelTypeVars _ = []
skolemEscapeCheck :: MonadError MultipleErrors m => Expr -> m ()
skolemEscapeCheck (TypedValue False _ _) = return ()
skolemEscapeCheck expr@TypedValue{} =
traverse_ (throwError . singleError) (toSkolemErrors expr)
where
toSkolemErrors :: Expr -> [ErrorMessage]
(_, toSkolemErrors, _, _, _) = everythingWithContextOnValues (mempty, Nothing) [] (<>) def go def def def
def s _ = (s, [])
go :: (Set SkolemScope, Maybe SourceSpan)
-> Expr
-> ((Set SkolemScope, Maybe SourceSpan), [ErrorMessage])
go (scopes, _) (PositionedValue ss _ _) = ((scopes, Just ss), [])
go (scopes, ssUsed) val@(TypedValue _ _ ty) =
( (allScopes, ssUsed)
, [ ErrorMessage (maybe id ((:) . positionedError) ssUsed [ ErrorInExpression val ]) $
EscapedSkolem name (nonEmptySpan ssBound) ty
| (ssBound, name, scope) <- collectSkolems ty
, notMember scope allScopes
]
)
where
newScopes :: [SkolemScope]
newScopes = collectScopes ty
allScopes :: Set SkolemScope
allScopes = fromList newScopes <> scopes
collectScopes :: SourceType -> [SkolemScope]
collectScopes (ForAll _ _ _ t (Just sco)) = sco : collectScopes t
collectScopes ForAll{} = internalError "skolemEscapeCheck: No skolem scope"
collectScopes _ = []
collectSkolems :: SourceType -> [(SourceAnn, Text, SkolemScope)]
collectSkolems = everythingOnTypes (++) collect where
collect (Skolem ss name _ scope) = [(ss, name, scope)]
collect _ = []
go scos _ = (scos, [])
skolemEscapeCheck _ = internalError "skolemEscapeCheck: untyped value"