purescript-0.14.2: src/Language/PureScript/TypeChecker/Entailment.hs
-- |
-- Type class entailment
--
module Language.PureScript.TypeChecker.Entailment
( InstanceContext
, SolverOptions(..)
, replaceTypeClassDictionaries
, newDictionaries
, entails
) where
import Prelude.Compat
import Protolude (ordNub)
import Control.Arrow (second, (&&&))
import Control.Monad.Error.Class (MonadError(..))
import Control.Monad.State
import Control.Monad.Supply.Class (MonadSupply(..))
import Control.Monad.Writer
import Data.Foldable (for_, fold, toList)
import Data.Function (on)
import Data.Functor (($>))
import Data.List (findIndices, minimumBy, groupBy, nubBy, sortOn)
import Data.Maybe (fromMaybe, listToMaybe, mapMaybe)
import qualified Data.Map as M
import qualified Data.Set as S
import Data.Traversable (for)
import Data.Text (Text, stripPrefix, stripSuffix)
import qualified Data.Text as T
import qualified Data.List.NonEmpty as NEL
import Language.PureScript.AST
import Language.PureScript.Crash
import Language.PureScript.Environment
import Language.PureScript.Errors
import Language.PureScript.Names
import Language.PureScript.TypeChecker.Entailment.Coercible
import Language.PureScript.TypeChecker.Kinds (elaborateKind, unifyKinds')
import Language.PureScript.TypeChecker.Monad
import Language.PureScript.TypeChecker.Unify
import Language.PureScript.TypeClassDictionaries
import Language.PureScript.Types
import Language.PureScript.Label (Label(..))
import Language.PureScript.PSString (PSString, mkString, decodeString)
import qualified Language.PureScript.Constants.Prelude as C
import qualified Language.PureScript.Constants.Prim as C
-- | Describes what sort of dictionary to generate for type class instances
data Evidence
-- | An existing named instance
= NamedInstance (Qualified Ident)
-- | Computed instances
| WarnInstance SourceType -- ^ Warn type class with a user-defined warning message
| IsSymbolInstance PSString -- ^ The IsSymbol type class for a given Symbol literal
| EmptyClassInstance -- ^ For any solved type class with no members
deriving (Show, Eq)
-- | Extract the identifier of a named instance
namedInstanceIdentifier :: Evidence -> Maybe (Qualified Ident)
namedInstanceIdentifier (NamedInstance i) = Just i
namedInstanceIdentifier _ = Nothing
-- | Description of a type class dictionary with instance evidence
type TypeClassDict = TypeClassDictionaryInScope Evidence
-- | The 'InstanceContext' tracks those constraints which can be satisfied.
type InstanceContext = M.Map (Maybe ModuleName)
(M.Map (Qualified (ProperName 'ClassName))
(M.Map (Qualified Ident) (NEL.NonEmpty NamedDict)))
-- | A type substitution which makes an instance head match a list of types.
--
-- Note: we store many types per type variable name. For any name, all types
-- should unify if we are going to commit to an instance.
type Matching a = M.Map Text a
combineContexts :: InstanceContext -> InstanceContext -> InstanceContext
combineContexts = M.unionWith (M.unionWith (M.unionWith (<>)))
-- | Replace type class dictionary placeholders with inferred type class dictionaries
replaceTypeClassDictionaries
:: forall m
. (MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m, MonadSupply m)
=> Bool
-> Expr
-> m (Expr, [(Ident, InstanceContext, SourceConstraint)])
replaceTypeClassDictionaries shouldGeneralize expr = flip evalStateT M.empty $ do
-- Loop, deferring any unsolved constraints, until there are no more
-- constraints which can be solved, then make a generalization pass.
let loop e = do
(e', solved) <- deferPass e
if getAny solved
then loop e'
else return e'
loop expr >>= generalizePass
where
-- This pass solves constraints where possible, deferring constraints if not.
deferPass :: Expr -> StateT InstanceContext m (Expr, Any)
deferPass = fmap (second fst) . runWriterT . f where
f :: Expr -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext m) Expr
(_, f, _) = everywhereOnValuesTopDownM return (go True) return
-- This pass generalizes any remaining constraints
generalizePass :: Expr -> StateT InstanceContext m (Expr, [(Ident, InstanceContext, SourceConstraint)])
generalizePass = fmap (second snd) . runWriterT . f where
f :: Expr -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext m) Expr
(_, f, _) = everywhereOnValuesTopDownM return (go False) return
go :: Bool -> Expr -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext m) Expr
go deferErrors (TypeClassDictionary constraint context hints) =
rethrow (addHints hints) $ entails (SolverOptions shouldGeneralize deferErrors) constraint context hints
go _ other = return other
-- | Three options for how we can handle a constraint, depending on the mode we're in.
data EntailsResult a
= Solved a TypeClassDict
-- ^ We solved this constraint
| Unsolved SourceConstraint
-- ^ We couldn't solve this constraint right now, it will be generalized
| Deferred
-- ^ We couldn't solve this constraint right now, so it has been deferred
deriving Show
-- | Options for the constraint solver
data SolverOptions = SolverOptions
{ solverShouldGeneralize :: Bool
-- ^ Should the solver be allowed to generalize over unsolved constraints?
, solverDeferErrors :: Bool
-- ^ Should the solver be allowed to defer errors by skipping constraints?
}
data Matched t
= Match t
| Apart
| Unknown
deriving (Eq, Show, Functor)
instance Semigroup t => Semigroup (Matched t) where
(Match l) <> (Match r) = Match (l <> r)
Apart <> _ = Apart
_ <> Apart = Apart
_ <> _ = Unknown
instance Monoid t => Monoid (Matched t) where
mempty = Match mempty
-- | Check that the current set of type class dictionaries entail the specified type class goal, and, if so,
-- return a type class dictionary reference.
entails
:: forall m
. (MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m, MonadSupply m)
=> SolverOptions
-- ^ Solver options
-> SourceConstraint
-- ^ The constraint to solve
-> InstanceContext
-- ^ The contexts in which to solve the constraint
-> [ErrorMessageHint]
-- ^ Error message hints to apply to any instance errors
-> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext m) Expr
entails SolverOptions{..} constraint context hints =
solve constraint
where
forClassNameM :: Environment -> InstanceContext -> Qualified (ProperName 'ClassName) -> [SourceType] -> [SourceType] -> m [TypeClassDict]
forClassNameM env ctx cn@C.Coercible kinds args =
fromMaybe (forClassName env ctx cn kinds args) <$>
solveCoercible env ctx kinds args
forClassNameM env ctx cn kinds args =
pure $ forClassName env ctx cn kinds args
forClassName :: Environment -> InstanceContext -> Qualified (ProperName 'ClassName) -> [SourceType] -> [SourceType] -> [TypeClassDict]
forClassName _ ctx cn@C.Warn _ [msg] =
-- Prefer a warning dictionary in scope if there is one available.
-- This allows us to defer a warning by propagating the constraint.
findDicts ctx cn Nothing ++ [TypeClassDictionaryInScope Nothing 0 (WarnInstance msg) [] C.Warn [] [] [msg] Nothing]
forClassName _ _ C.IsSymbol _ args | Just dicts <- solveIsSymbol args = dicts
forClassName _ _ C.SymbolCompare _ args | Just dicts <- solveSymbolCompare args = dicts
forClassName _ _ C.SymbolAppend _ args | Just dicts <- solveSymbolAppend args = dicts
forClassName _ _ C.SymbolCons _ args | Just dicts <- solveSymbolCons args = dicts
forClassName _ _ C.RowUnion kinds args | Just dicts <- solveUnion kinds args = dicts
forClassName _ _ C.RowNub kinds args | Just dicts <- solveNub kinds args = dicts
forClassName _ _ C.RowLacks kinds args | Just dicts <- solveLacks kinds args = dicts
forClassName _ _ C.RowCons kinds args | Just dicts <- solveRowCons kinds args = dicts
forClassName _ _ C.RowToList kinds args | Just dicts <- solveRowToList kinds args = dicts
forClassName _ ctx cn@(Qualified (Just mn) _) _ tys = concatMap (findDicts ctx cn) (ordNub (Nothing : Just mn : map Just (mapMaybe ctorModules tys)))
forClassName _ _ _ _ _ = internalError "forClassName: expected qualified class name"
ctorModules :: SourceType -> Maybe ModuleName
ctorModules (TypeConstructor _ (Qualified (Just mn) _)) = Just mn
ctorModules (TypeConstructor _ (Qualified Nothing _)) = internalError "ctorModules: unqualified type name"
ctorModules (TypeApp _ ty _) = ctorModules ty
ctorModules (KindApp _ ty _) = ctorModules ty
ctorModules (KindedType _ ty _) = ctorModules ty
ctorModules _ = Nothing
findDicts :: InstanceContext -> Qualified (ProperName 'ClassName) -> Maybe ModuleName -> [TypeClassDict]
findDicts ctx cn = fmap (fmap NamedInstance) . foldMap NEL.toList . foldMap M.elems . (>>= M.lookup cn) . flip M.lookup ctx
valUndefined :: Expr
valUndefined = Var nullSourceSpan (Qualified (Just C.Prim) (Ident C.undefined))
solve :: SourceConstraint -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext m) Expr
solve = go 0 hints
where
go :: Int -> [ErrorMessageHint] -> SourceConstraint -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext m) Expr
go work _ (Constraint _ className' _ tys' _) | work > 1000 = throwError . errorMessage $ PossiblyInfiniteInstance className' tys'
go work hints' con@(Constraint _ className' kinds' tys' conInfo) = WriterT . StateT . (withErrorMessageHint (ErrorSolvingConstraint con) .) . runStateT . runWriterT $ do
-- We might have unified types by solving other constraints, so we need to
-- apply the latest substitution.
latestSubst <- lift . lift $ gets checkSubstitution
let kinds'' = map (substituteType latestSubst) kinds'
tys'' = map (substituteType latestSubst) tys'
-- Get the inferred constraint context so far, and merge it with the global context
inferred <- lift get
-- We need information about functional dependencies, so we have to look up the class
-- name in the environment:
env <- lift . lift $ gets checkEnv
let classesInScope = typeClasses env
TypeClassData
{ typeClassDependencies
, typeClassIsEmpty
, typeClassCoveringSets
} <- case M.lookup className' classesInScope of
Nothing -> throwError . errorMessage $ UnknownClass className'
Just tcd -> pure tcd
dicts <- lift . lift $ forClassNameM env (combineContexts context inferred) className' kinds'' tys''
let instances = do
chain <- groupBy ((==) `on` tcdChain) $
sortOn (tcdChain &&& tcdIndex)
dicts
-- process instances in a chain in index order
let found = for chain $ \tcd ->
-- Make sure the type unifies with the type in the type instance definition
case matches typeClassDependencies tcd tys'' of
Apart -> Right () -- keep searching
Match substs -> Left (Just (substs, tcd)) -- found a match
Unknown -> Left Nothing -- can't continue with this chain yet, need proof of apartness
case found of
Right _ -> [] -- all apart
Left Nothing -> [] -- last unknown
Left (Just substsTcd) -> [substsTcd] -- found a match
solution <- lift . lift $ unique kinds'' tys'' instances (unknownsInAllCoveringSets tys'' typeClassCoveringSets)
case solution of
Solved substs tcd -> do
-- Note that we solved something.
tell (Any True, mempty)
-- Make sure the substitution is valid:
lift . lift . for_ substs $ pairwiseM unifyTypes
-- Now enforce any functional dependencies, using unification
-- Note: we need to generate fresh types for any unconstrained
-- type variables before unifying.
let subst = fmap head substs
currentSubst <- lift . lift $ gets checkSubstitution
subst' <- lift . lift $ withFreshTypes tcd (fmap (substituteType currentSubst) subst)
lift . lift $ zipWithM_ (\t1 t2 -> do
let inferredType = replaceAllTypeVars (M.toList subst') t1
unifyTypes inferredType t2) (tcdInstanceTypes tcd) tys''
currentSubst' <- lift . lift $ gets checkSubstitution
let subst'' = fmap (substituteType currentSubst') subst'
-- Solve any necessary subgoals
args <- solveSubgoals subst'' (ErrorSolvingConstraint con) (tcdDependencies tcd)
initDict <- lift . lift $ mkDictionary (tcdValue tcd) args
let match = foldr (\(className, index) dict -> subclassDictionaryValue dict className index)
initDict
(tcdPath tcd)
return (if typeClassIsEmpty then Unused match else match)
Unsolved unsolved -> do
-- Generate a fresh name for the unsolved constraint's new dictionary
ident <- freshIdent ("dict" <> runProperName (disqualify (constraintClass unsolved)))
let qident = Qualified Nothing ident
-- Store the new dictionary in the InstanceContext so that we can solve this goal in
-- future.
newDicts <- lift . lift $ newDictionaries [] qident unsolved
let newContext = mkContext newDicts
modify (combineContexts newContext)
-- Mark this constraint for generalization
tell (mempty, [(ident, context, unsolved)])
return (Var nullSourceSpan qident)
Deferred ->
-- Constraint was deferred, just return the dictionary unchanged,
-- with no unsolved constraints. Hopefully, we can solve this later.
return (TypeClassDictionary (srcConstraint className' kinds'' tys'' conInfo) context hints')
where
-- | When checking functional dependencies, we need to use unification to make
-- sure it is safe to use the selected instance. We will unify the solved type with
-- the type in the instance head under the substitution inferred from its instantiation.
-- As an example, when solving MonadState t0 (State Int), we choose the
-- MonadState s (State s) instance, and we unify t0 with Int, since the functional
-- dependency from MonadState dictates that t0 should unify with s\[s -> Int], which is
-- Int. This is fine, but in some cases, the substitution does not remove all TypeVars
-- from the type, so we end up with a unification error. So, any type arguments which
-- appear in the instance head, but not in the substitution need to be replaced with
-- fresh type variables. This function extends a substitution with fresh type variables
-- as necessary, based on the types in the instance head. It also unifies kinds based on
-- the substitution so kind information propagates correctly through the solver.
withFreshTypes
:: TypeClassDict
-> Matching SourceType
-> m (Matching SourceType)
withFreshTypes TypeClassDictionaryInScope{..} initSubst = do
subst <- foldM withFreshType initSubst $ filter (flip M.notMember initSubst . fst) tcdForAll
for_ (M.toList initSubst) $ unifySubstKind subst
pure subst
where
withFreshType subst (var, kind) = do
ty <- freshTypeWithKind $ replaceAllTypeVars (M.toList subst) kind
pure $ M.insert var ty subst
unifySubstKind subst (var, ty) =
for_ (lookup var tcdForAll) $ \instKind -> do
tyKind <- elaborateKind ty
currentSubst <- gets checkSubstitution
unifyKinds'
(substituteType currentSubst . replaceAllTypeVars (M.toList subst) $ instKind)
(substituteType currentSubst tyKind)
unique :: [SourceType] -> [SourceType] -> [(a, TypeClassDict)] -> Bool -> m (EntailsResult a)
unique kindArgs tyArgs [] unks
| solverDeferErrors = return Deferred
-- We need a special case for nullary type classes, since we want
-- to generalize over Partial constraints.
| solverShouldGeneralize && ((null kindArgs && null tyArgs) || any canBeGeneralized kindArgs || any canBeGeneralized tyArgs) =
return (Unsolved (srcConstraint className' kindArgs tyArgs conInfo))
| otherwise = throwError . errorMessage $ NoInstanceFound (srcConstraint className' kindArgs tyArgs conInfo) unks
unique _ _ [(a, dict)] _ = return $ Solved a dict
unique _ tyArgs tcds _
| pairwiseAny overlapping (map snd tcds) =
throwError . errorMessage $ OverlappingInstances className' tyArgs (tcds >>= (toList . namedInstanceIdentifier . tcdValue . snd))
| otherwise = return $ uncurry Solved (minimumBy (compare `on` length . tcdPath . snd) tcds)
canBeGeneralized :: Type a -> Bool
canBeGeneralized TUnknown{} = True
canBeGeneralized (KindedType _ t _) = canBeGeneralized t
canBeGeneralized _ = False
-- |
-- Check if two dictionaries are overlapping
--
-- Dictionaries which are subclass dictionaries cannot overlap, since otherwise the overlap would have
-- been caught when constructing superclass dictionaries.
overlapping :: TypeClassDict -> TypeClassDict -> Bool
overlapping TypeClassDictionaryInScope{ tcdPath = _ : _ } _ = False
overlapping _ TypeClassDictionaryInScope{ tcdPath = _ : _ } = False
overlapping TypeClassDictionaryInScope{ tcdDependencies = Nothing } _ = False
overlapping _ TypeClassDictionaryInScope{ tcdDependencies = Nothing } = False
overlapping tcd1 tcd2 = tcdValue tcd1 /= tcdValue tcd2
-- Create dictionaries for subgoals which still need to be solved by calling go recursively
-- E.g. the goal (Show a, Show b) => Show (Either a b) can be satisfied if the current type
-- unifies with Either a b, and we can satisfy the subgoals Show a and Show b recursively.
solveSubgoals :: Matching SourceType -> ErrorMessageHint -> Maybe [SourceConstraint] -> WriterT (Any, [(Ident, InstanceContext, SourceConstraint)]) (StateT InstanceContext m) (Maybe [Expr])
solveSubgoals _ _ Nothing = return Nothing
solveSubgoals subst hint (Just subgoals) =
Just <$> traverse (rethrow (addHint hint) . go (work + 1) (hints' <> [hint]) . mapConstraintArgsAll (map (replaceAllTypeVars (M.toList subst)))) subgoals
-- We need subgoal dictionaries to appear in the term somewhere
-- If there aren't any then the dictionary is just undefined
useEmptyDict :: Maybe [Expr] -> Expr
useEmptyDict args = Unused (foldl (App . Abs (VarBinder nullSourceSpan UnusedIdent)) valUndefined (fold args))
-- Make a dictionary from subgoal dictionaries by applying the correct function
mkDictionary :: Evidence -> Maybe [Expr] -> m Expr
mkDictionary (NamedInstance n) args = return $ foldl App (Var nullSourceSpan n) (fold args)
mkDictionary EmptyClassInstance args = return (useEmptyDict args)
mkDictionary (WarnInstance msg) args = do
tell . errorMessage $ UserDefinedWarning msg
-- We cannot call the type class constructor here because Warn is declared in Prim.
-- This means that it doesn't have a definition that we can import.
-- So pass an empty placeholder (undefined) instead.
return (useEmptyDict args)
mkDictionary (IsSymbolInstance sym) _ =
let fields = [ ("reflectSymbol", Abs (VarBinder nullSourceSpan UnusedIdent) (Literal nullSourceSpan (StringLiteral sym))) ] in
return $ TypeClassDictionaryConstructorApp C.IsSymbol (Literal nullSourceSpan (ObjectLiteral fields))
unknownsInAllCoveringSets :: [SourceType] -> S.Set (S.Set Int) -> Bool
unknownsInAllCoveringSets tyArgs = all (\s -> any (`S.member` s) unkIndices)
where unkIndices = findIndices containsUnknowns tyArgs
-- Turn a DictionaryValue into a Expr
subclassDictionaryValue :: Expr -> Qualified (ProperName 'ClassName) -> Integer -> Expr
subclassDictionaryValue dict className index =
App (Accessor (mkString (superclassName className index)) dict) valUndefined
solveCoercible :: Environment -> InstanceContext -> [SourceType] -> [SourceType] -> m (Maybe [TypeClassDict])
solveCoercible env ctx kinds [a, b] = do
let coercibleDictsInScope = findDicts ctx C.Coercible Nothing
givens = flip mapMaybe coercibleDictsInScope $ \case
dict | [a', b'] <- tcdInstanceTypes dict -> Just (a', b')
| otherwise -> Nothing
GivenSolverState{ inertGivens } <- execStateT (solveGivens env) $
initialGivenSolverState givens
(WantedSolverState{ inertWanteds }, hints') <- runWriterT . execStateT (solveWanteds env) $
initialWantedSolverState inertGivens a b
-- Solving fails when there's irreducible wanteds left.
--
-- We report the first residual constraint instead of the initial wanted,
-- unless we just swapped its arguments.
--
-- We may have collected hints for the solving failure along the way, in
-- which case we decorate the error with the first one.
maybe id addHint (listToMaybe hints') `rethrow` case inertWanteds of
[] -> pure $ Just [TypeClassDictionaryInScope Nothing 0 EmptyClassInstance [] C.Coercible [] kinds [a, b] Nothing]
(k, a', b') : _ | a' == b && b' == a -> throwError $ insoluble k b' a'
(k, a', b') : _ -> throwError $ insoluble k a' b'
solveCoercible _ _ _ _ = pure Nothing
solveIsSymbol :: [SourceType] -> Maybe [TypeClassDict]
solveIsSymbol [TypeLevelString ann sym] = Just [TypeClassDictionaryInScope Nothing 0 (IsSymbolInstance sym) [] C.IsSymbol [] [] [TypeLevelString ann sym] Nothing]
solveIsSymbol _ = Nothing
solveSymbolCompare :: [SourceType] -> Maybe [TypeClassDict]
solveSymbolCompare [arg0@(TypeLevelString _ lhs), arg1@(TypeLevelString _ rhs), _] =
let ordering = case compare lhs rhs of
LT -> C.orderingLT
EQ -> C.orderingEQ
GT -> C.orderingGT
args' = [arg0, arg1, srcTypeConstructor ordering]
in Just [TypeClassDictionaryInScope Nothing 0 EmptyClassInstance [] C.SymbolCompare [] [] args' Nothing]
solveSymbolCompare _ = Nothing
solveSymbolAppend :: [SourceType] -> Maybe [TypeClassDict]
solveSymbolAppend [arg0, arg1, arg2] = do
(arg0', arg1', arg2') <- appendSymbols arg0 arg1 arg2
let args' = [arg0', arg1', arg2']
pure [TypeClassDictionaryInScope Nothing 0 EmptyClassInstance [] C.SymbolAppend [] [] args' Nothing]
solveSymbolAppend _ = Nothing
-- | Append type level symbols, or, run backwards, strip a prefix or suffix
appendSymbols :: SourceType -> SourceType -> SourceType -> Maybe (SourceType, SourceType, SourceType)
appendSymbols arg0@(TypeLevelString _ lhs) arg1@(TypeLevelString _ rhs) _ = Just (arg0, arg1, srcTypeLevelString (lhs <> rhs))
appendSymbols arg0@(TypeLevelString _ lhs) _ arg2@(TypeLevelString _ out) = do
lhs' <- decodeString lhs
out' <- decodeString out
rhs <- stripPrefix lhs' out'
pure (arg0, srcTypeLevelString (mkString rhs), arg2)
appendSymbols _ arg1@(TypeLevelString _ rhs) arg2@(TypeLevelString _ out) = do
rhs' <- decodeString rhs
out' <- decodeString out
lhs <- stripSuffix rhs' out'
pure (srcTypeLevelString (mkString lhs), arg1, arg2)
appendSymbols _ _ _ = Nothing
solveSymbolCons :: [SourceType] -> Maybe [TypeClassDict]
solveSymbolCons [arg0, arg1, arg2] = do
(arg0', arg1', arg2') <- consSymbol arg0 arg1 arg2
let args' = [arg0', arg1', arg2']
pure [TypeClassDictionaryInScope Nothing 0 EmptyClassInstance [] C.SymbolCons [] [] args' Nothing]
solveSymbolCons _ = Nothing
consSymbol :: SourceType -> SourceType -> SourceType -> Maybe (SourceType, SourceType, SourceType)
consSymbol _ _ arg@(TypeLevelString _ s) = do
(h, t) <- T.uncons =<< decodeString s
pure (mkTLString (T.singleton h), mkTLString t, arg)
where mkTLString = srcTypeLevelString . mkString
consSymbol arg1@(TypeLevelString _ h) arg2@(TypeLevelString _ t) _ = do
h' <- decodeString h
t' <- decodeString t
guard (T.length h' == 1)
pure (arg1, arg2, srcTypeLevelString (mkString $ h' <> t'))
consSymbol _ _ _ = Nothing
solveUnion :: [SourceType] -> [SourceType] -> Maybe [TypeClassDict]
solveUnion kinds [l, r, u] = do
(lOut, rOut, uOut, cst, vars) <- unionRows kinds l r u
pure [ TypeClassDictionaryInScope Nothing 0 EmptyClassInstance [] C.RowUnion vars kinds [lOut, rOut, uOut] cst ]
solveUnion _ _ = Nothing
-- | Left biased union of two row types
unionRows :: [SourceType] -> SourceType -> SourceType -> SourceType -> Maybe (SourceType, SourceType, SourceType, Maybe [SourceConstraint], [(Text, SourceType)])
unionRows kinds l r _ =
guard canMakeProgress $> (l, r, rowFromList out, cons, vars)
where
(fixed, rest) = rowToList l
rowVar = srcTypeVar "r"
(canMakeProgress, out, cons, vars) =
case rest of
-- If the left hand side is a closed row, then we can merge
-- its labels into the right hand side.
REmptyKinded _ _ -> (True, (fixed, r), Nothing, [])
-- If the left hand side is not definitely closed, then the only way we
-- can safely make progress is to move any known labels from the left
-- input into the output, and add a constraint for any remaining labels.
-- Otherwise, the left hand tail might contain the same labels as on
-- the right hand side, and we can't be certain we won't reorder the
-- types for such labels.
_ -> ( not (null fixed)
, (fixed, rowVar)
, Just [ srcConstraint C.RowUnion kinds [rest, r, rowVar] Nothing ]
, [("r", kindRow (head kinds))]
)
solveRowCons :: [SourceType] -> [SourceType] -> Maybe [TypeClassDict]
solveRowCons kinds [TypeLevelString ann sym, ty, r, _] =
Just [ TypeClassDictionaryInScope Nothing 0 EmptyClassInstance [] C.RowCons [] kinds [TypeLevelString ann sym, ty, r, srcRCons (Label sym) ty r] Nothing ]
solveRowCons _ _ = Nothing
solveRowToList :: [SourceType] -> [SourceType] -> Maybe [TypeClassDict]
solveRowToList [kind] [r, _] = do
entries <- rowToRowList kind r
pure [ TypeClassDictionaryInScope Nothing 0 EmptyClassInstance [] C.RowToList [] [kind] [r, entries] Nothing ]
solveRowToList _ _ = Nothing
-- | Convert a closed row to a sorted list of entries
rowToRowList :: SourceType -> SourceType -> Maybe SourceType
rowToRowList kind r =
guard (isREmpty rest) $>
foldr rowListCons (srcKindApp (srcTypeConstructor C.RowListNil) kind) fixed
where
(fixed, rest) = rowToSortedList r
rowListCons (RowListItem _ lbl ty) tl =
foldl srcTypeApp (srcKindApp (srcTypeConstructor C.RowListCons) kind)
[ srcTypeLevelString (runLabel lbl)
, ty
, tl ]
solveNub :: [SourceType] -> [SourceType] -> Maybe [TypeClassDict]
solveNub kinds [r, _] = do
r' <- nubRows r
pure [ TypeClassDictionaryInScope Nothing 0 EmptyClassInstance [] C.RowNub [] kinds [r, r'] Nothing ]
solveNub _ _ = Nothing
nubRows :: SourceType -> Maybe SourceType
nubRows r =
guard (isREmpty rest) $>
rowFromList (nubBy ((==) `on` rowListLabel) fixed, rest)
where
(fixed, rest) = rowToSortedList r
solveLacks :: [SourceType] -> [SourceType] -> Maybe [TypeClassDict]
solveLacks kinds tys@[_, REmptyKinded _ _] =
pure [ TypeClassDictionaryInScope Nothing 0 EmptyClassInstance [] C.RowLacks [] kinds tys Nothing ]
solveLacks kinds [TypeLevelString ann sym, r] = do
(r', cst) <- rowLacks kinds sym r
pure [ TypeClassDictionaryInScope Nothing 0 EmptyClassInstance [] C.RowLacks [] kinds [TypeLevelString ann sym, r'] cst ]
solveLacks _ _ = Nothing
rowLacks :: [SourceType] -> PSString -> SourceType -> Maybe (SourceType, Maybe [SourceConstraint])
rowLacks kinds sym r =
guard (lacksSym && canMakeProgress) $> (r, cst)
where
(fixed, rest) = rowToList r
lacksSym =
sym `notElem` (runLabel . rowListLabel <$> fixed)
(canMakeProgress, cst) = case rest of
REmptyKinded _ _ -> (True, Nothing)
_ -> (not (null fixed), Just [ srcConstraint C.RowLacks kinds [srcTypeLevelString sym, rest] Nothing ])
-- Check if an instance matches our list of types, allowing for types
-- to be solved via functional dependencies. If the types match, we return a
-- substitution which makes them match. If not, we return 'Nothing'.
matches :: [FunctionalDependency] -> TypeClassDict -> [SourceType] -> Matched (Matching [SourceType])
matches deps TypeClassDictionaryInScope{..} tys =
-- First, find those types which match exactly
let matched = zipWith typeHeadsAreEqual tys tcdInstanceTypes in
-- Now, use any functional dependencies to infer any remaining types
if not (covers matched)
then if any ((==) Apart . fst) matched then Apart else Unknown
else -- Verify that any repeated type variables are unifiable
let determinedSet = foldMap (S.fromList . fdDetermined) deps
solved = map snd . filter ((`S.notMember` determinedSet) . fst) $ zipWith (\(_, ts) i -> (i, ts)) matched [0..]
in verifySubstitution (M.unionsWith (++) solved)
where
-- | Find the closure of a set of functional dependencies.
covers :: [(Matched (), subst)] -> Bool
covers ms = finalSet == S.fromList [0..length ms - 1]
where
initialSet :: S.Set Int
initialSet = S.fromList . map snd . filter ((==) (Match ()) . fst . fst) $ zip ms [0..]
finalSet :: S.Set Int
finalSet = untilFixedPoint applyAll initialSet
untilFixedPoint :: Eq a => (a -> a) -> a -> a
untilFixedPoint f = go
where
go a | a' == a = a'
| otherwise = go a'
where a' = f a
applyAll :: S.Set Int -> S.Set Int
applyAll s = foldr applyDependency s deps
applyDependency :: FunctionalDependency -> S.Set Int -> S.Set Int
applyDependency FunctionalDependency{..} xs
| S.fromList fdDeterminers `S.isSubsetOf` xs = xs <> S.fromList fdDetermined
| otherwise = xs
--
-- Check whether the type heads of two types are equal (for the purposes of type class dictionary lookup),
-- and return a substitution from type variables to types which makes the type heads unify.
--
typeHeadsAreEqual :: Type a -> Type a -> (Matched (), Matching [Type a])
typeHeadsAreEqual (KindedType _ t1 _) t2 = typeHeadsAreEqual t1 t2
typeHeadsAreEqual t1 (KindedType _ t2 _) = typeHeadsAreEqual t1 t2
typeHeadsAreEqual (TUnknown _ u1) (TUnknown _ u2) | u1 == u2 = (Match (), M.empty)
typeHeadsAreEqual (Skolem _ _ _ s1 _) (Skolem _ _ _ s2 _) | s1 == s2 = (Match (), M.empty)
typeHeadsAreEqual t (TypeVar _ v) = (Match (), M.singleton v [t])
typeHeadsAreEqual (TypeConstructor _ c1) (TypeConstructor _ c2) | c1 == c2 = (Match (), M.empty)
typeHeadsAreEqual (TypeLevelString _ s1) (TypeLevelString _ s2) | s1 == s2 = (Match (), M.empty)
typeHeadsAreEqual (TypeApp _ h1 t1) (TypeApp _ h2 t2) =
both (typeHeadsAreEqual h1 h2) (typeHeadsAreEqual t1 t2)
typeHeadsAreEqual (KindApp _ h1 t1) (KindApp _ h2 t2) =
both (typeHeadsAreEqual h1 h2) (typeHeadsAreEqual t1 t2)
typeHeadsAreEqual (REmpty _) (REmpty _) = (Match (), M.empty)
typeHeadsAreEqual r1@RCons{} r2@RCons{} =
foldr both (uncurry go rest) common
where
(common, rest) = alignRowsWith typeHeadsAreEqual r1 r2
go :: ([RowListItem a], Type a) -> ([RowListItem a], Type a) -> (Matched (), Matching [Type a])
go (l, KindedType _ t1 _) (r, t2) = go (l, t1) (r, t2)
go (l, t1) (r, KindedType _ t2 _) = go (l, t1) (r, t2)
go (l, KindApp _ t1 k1) (r, KindApp _ t2 k2) | eqType k1 k2 = go (l, t1) (r, t2)
go ([], REmpty _) ([], REmpty _) = (Match (), M.empty)
go ([], TUnknown _ u1) ([], TUnknown _ u2) | u1 == u2 = (Match (), M.empty)
go ([], TypeVar _ v1) ([], TypeVar _ v2) | v1 == v2 = (Match (), M.empty)
go ([], Skolem _ _ _ sk1 _) ([], Skolem _ _ _ sk2 _) | sk1 == sk2 = (Match (), M.empty)
go ([], TUnknown _ _) _ = (Unknown, M.empty)
go (sd, r) ([], TypeVar _ v) = (Match (), M.singleton v [rowFromList (sd, r)])
go _ _ = (Apart, M.empty)
typeHeadsAreEqual (TUnknown _ _) _ = (Unknown, M.empty)
typeHeadsAreEqual _ _ = (Apart, M.empty)
both :: (Matched (), Matching [Type a]) -> (Matched (), Matching [Type a]) -> (Matched (), Matching [Type a])
both (b1, m1) (b2, m2) = (b1 <> b2, M.unionWith (++) m1 m2)
-- Ensure that a substitution is valid
verifySubstitution :: Matching [Type a] -> Matched (Matching [Type a])
verifySubstitution mts = foldMap meet mts $> mts where
meet = pairwiseAll typesAreEqual
-- Note that unknowns are only allowed to unify if they came from a type
-- which was _not_ solved, i.e. one which was inferred by a functional
-- dependency.
typesAreEqual :: Type a -> Type a -> Matched ()
typesAreEqual (KindedType _ t1 _) t2 = typesAreEqual t1 t2
typesAreEqual t1 (KindedType _ t2 _) = typesAreEqual t1 t2
typesAreEqual (TUnknown _ u1) (TUnknown _ u2) | u1 == u2 = Match ()
typesAreEqual (Skolem _ _ _ s1 _) (Skolem _ _ _ s2 _) | s1 == s2 = Match ()
typesAreEqual (Skolem _ _ _ _ _) _ = Unknown
typesAreEqual _ (Skolem _ _ _ _ _) = Unknown
typesAreEqual (TypeVar _ v1) (TypeVar _ v2) | v1 == v2 = Match ()
typesAreEqual (TypeLevelString _ s1) (TypeLevelString _ s2) | s1 == s2 = Match ()
typesAreEqual (TypeConstructor _ c1) (TypeConstructor _ c2) | c1 == c2 = Match ()
typesAreEqual (TypeApp _ h1 t1) (TypeApp _ h2 t2) = typesAreEqual h1 h2 <> typesAreEqual t1 t2
typesAreEqual (KindApp _ h1 t1) (KindApp _ h2 t2) = typesAreEqual h1 h2 <> typesAreEqual t1 t2
typesAreEqual (REmpty _) (REmpty _) = Match ()
typesAreEqual r1 r2 | isRCons r1 || isRCons r2 =
let (common, rest) = alignRowsWith typesAreEqual r1 r2
in fold common <> uncurry go rest
where
go :: ([RowListItem a], Type a) -> ([RowListItem a], Type a) -> Matched ()
go (l, KindedType _ t1 _) (r, t2) = go (l, t1) (r, t2)
go (l, t1) (r, KindedType _ t2 _) = go (l, t1) (r, t2)
go ([], KindApp _ t1 k1) ([], KindApp _ t2 k2) = typesAreEqual t1 t2 <> typesAreEqual k1 k2
go ([], TUnknown _ u1) ([], TUnknown _ u2) | u1 == u2 = Match ()
go ([], Skolem _ _ _ s1 _) ([], Skolem _ _ _ s2 _) | s1 == s2 = Match ()
go ([], Skolem _ _ _ _ _) _ = Unknown
go _ ([], Skolem _ _ _ _ _) = Unknown
go ([], REmpty _) ([], REmpty _) = Match ()
go ([], TypeVar _ v1) ([], TypeVar _ v2) | v1 == v2 = Match ()
go _ _ = Apart
typesAreEqual _ _ = Apart
isRCons :: Type a -> Bool
isRCons RCons{} = True
isRCons _ = False
-- | Add a dictionary for the constraint to the scope, and dictionaries
-- for all implied superclass instances.
newDictionaries
:: MonadState CheckState m
=> [(Qualified (ProperName 'ClassName), Integer)]
-> Qualified Ident
-> SourceConstraint
-> m [NamedDict]
newDictionaries path name (Constraint _ className instanceKinds instanceTy _) = do
tcs <- gets (typeClasses . checkEnv)
let TypeClassData{..} = fromMaybe (internalError "newDictionaries: type class lookup failed") $ M.lookup className tcs
supDicts <- join <$> zipWithM (\(Constraint ann supName supKinds supArgs _) index ->
let sub = zip (map fst typeClassArguments) instanceTy in
newDictionaries ((supName, index) : path)
name
(Constraint ann supName
(replaceAllTypeVars sub <$> supKinds)
(replaceAllTypeVars sub <$> supArgs)
Nothing)
) typeClassSuperclasses [0..]
return (TypeClassDictionaryInScope Nothing 0 name path className [] instanceKinds instanceTy Nothing : supDicts)
mkContext :: [NamedDict] -> InstanceContext
mkContext = foldr combineContexts M.empty . map fromDict where
fromDict d = M.singleton Nothing (M.singleton (tcdClassName d) (M.singleton (tcdValue d) (pure d)))
-- | Check all pairs of values in a list match a predicate
pairwiseAll :: Monoid m => (a -> a -> m) -> [a] -> m
pairwiseAll _ [] = mempty
pairwiseAll _ [_] = mempty
pairwiseAll p (x : xs) = foldMap (p x) xs <> pairwiseAll p xs
-- | Check any pair of values in a list match a predicate
pairwiseAny :: (a -> a -> Bool) -> [a] -> Bool
pairwiseAny _ [] = False
pairwiseAny _ [_] = False
pairwiseAny p (x : xs) = any (p x) xs || pairwiseAny p xs
pairwiseM :: Applicative m => (a -> a -> m ()) -> [a] -> m ()
pairwiseM _ [] = pure ()
pairwiseM _ [_] = pure ()
pairwiseM p (x : xs) = traverse (p x) xs *> pairwiseM p xs