packages feed

alms-0.6.0: src/Statics/Decl.hs

-- | Type checking declarations
module Statics.Decl (
  tcProg, tcDecls, tcDecl, tcSigExp,
) where

import Util
import qualified AST
import qualified Data.Loc
import Meta.Quasi
import Type
import Statics.Constraint
import Statics.Env as Env
import Statics.Error
import Statics.Type
import Statics.Expr
import Statics.Sealing

import Prelude ()
import Data.IORef (IORef)
import qualified Data.List as L
import qualified Data.Map  as M
import qualified Data.Set  as S

-- | Type check a program
tcProg  ∷ MonadConstraint tv r m ⇒
          Γ tv → AST.Prog R → m (AST.Prog R, Maybe (Type tv))
tcProg γ prog0 = withLocation prog0 $ case prog0 of
  [prQ| $list:ds in $opt:me |]                  → do
    (ds', γ', _)        ← tcDecls [] γ ds
    meσ'                ← mapM (tcExpr γ') me
    let me' = fst <$> meσ'
    return ([prQ| $list:ds' in $opt:me' |], snd <$> meσ')

-- | Type check a declaration.
tcDecl  ∷ MonadConstraint tv r m ⇒
          [ModId] → Γ tv → AST.Decl R → m
          (AST.Decl R, Γ tv, Signature tv)
tcDecl μ γ d0 = withLocation d0 $ case d0 of
  [dc| let $π = $e |]                           → do
    (e', σs)    ← tcExprPatt γ e π
    γ'          ← γ !+! π -:*- σs
    return ([dc| let $π = $e' |], γ', zipWith SgVal (AST.dv π) σs)
  [dc| let rec $list:bns |]                     → do
    (bns', ns, σs) ← tcLetRecBindings γ bns
    γ'          ← γ !+! ns -:*- σs
    return ([dc| let rec $list:bns' |], γ', zipWith SgVal ns σs)
  [dc| type $tid:lhs = type $qtid:rhs |]        → do
    tc          ← γ !.! rhs
    let sig     = [SgTyp lhs tc { tcName = J (reverse μ) lhs }]
    return (d0, γ =+= sig, sig)
  [dc| abstype $list:at with $list:ds end |]    → do
    (sigC, sigA)        ← tcAbsTys μ γ at
    (ds', _, sig1)      ← tcDecls μ (γ =+= sigC) ds
    let sig             = sigA ++ replaceTyCons (getSigTyCons sigA) sig1
    γ'                  ← γ !+! sig
    return ([dc| abstype $list:at with $list:ds' end |], γ', sig)
  [dc| type $list:tds |]                        → do
    sig         ← tcTyDecs μ γ tds
    return (d0, γ =+= sig, sig)
  [dc| module type $sid:n = $sigexp |]          → do
    sig1        ← tcSigExp γ sigexp
    let sig     = [SgSig n sig1]
    return (d0, γ =+= sig, sig)
  [dc| module $mid:n = $modexp |]               → do
    (modexp', sig1)     ← tcModExp (n:μ) γ modexp
    let sig     = [SgMod n sig1]
    γ'          ← γ !+! sig
    return ([dc| module $mid:n = $modexp' |], γ', sig)
  [dc| open $modexp |]                          → do
    (modexp', sig) ← tcModExp μ γ modexp
    γ'          ← γ !+! sig
    return ([dc| open $modexp' |], γ', sig)
  [dc| local $list:ds0 with $list:ds1 end |]    → do
    (ds0', γ', _)    ← tcDecls (AST.ident "?LocalModule":μ) γ ds0
    (ds1', _,  sig1) ← tcDecls μ γ' ds1
    γ''              ← γ !+! sig1
    return ([dc| local $list:ds0' with $list:ds1' end |], γ'', sig1)
  [dc| exception $cid:c of $opt:mt |]           → do
    mσ ← toEmptyF <$$> mapM (tcType mempty γ) mt
    let sig     = [SgExn c mσ]
    return (d0, γ =+= sig, sig)
  [dc| $anti:a |]                               → $(AST.antifail)

-- | Type check a sequence of declarations
tcDecls ∷ MonadConstraint tv r m ⇒
          [ModId] → Γ tv → [AST.Decl R] →
          m ([AST.Decl R], Γ tv, Signature tv)
tcDecls _ γ []     = return ([], γ, [])
tcDecls μ γ (d:ds) = do
  (d', γ', sig0)   ← tcDecl μ γ d
  (ds', γ'', sig1) ← tcDecls μ γ' ds
  return (d':ds', γ'', sig0 ++ sig1)

-- | Type check a module expression
tcModExp ∷ MonadConstraint tv r m ⇒
           [ModId] → Γ tv → AST.ModExp R →
           m (AST.ModExp R, Signature tv)
tcModExp μ γ modexp0 = withLocation modexp0 $ case modexp0 of
  [meQ| struct $list:ds end |]                  → do
    (ds', _, sig)       ← tcDecls μ γ ds
    return ([meQ| struct $list:ds' end |], sig)
  [meQ| $qmid:n $list:_ |]                      → do
    (sig, _) ← γ !.! n
    return (modexp0, sig)
  [meQ| $modexp : $sigexp |]                    → do
    (modexp', sig0)     ← tcModExp μ γ modexp
    sig1                ← tcSigExp γ sigexp
    sig                 ← sealWith μ sig0 sig1
    return ([meQ| $modexp' : $sigexp |], sig)
  [meQ| $anti:a |]                              → $(AST.antifail)

-- | Type check a single signature item
tcSigItem ∷ MonadConstraint tv r m ⇒
            Γ tv → AST.SigItem R → m (Signature tv)
tcSigItem γ sigitem0 = withLocation sigitem0 $ case sigitem0 of
  [sgQ| val $vid:n : $t |]                      → do
    σ           ← tcType mempty γ t
    return [SgVal n σ]
  [sgQ| type $list:tds |]                       → tcTyDecs [] γ tds
  [sgQ| type $tid:lhs = type $qtid:rhs |]       → do
    tc          ← γ !.! rhs
    return [SgTyp lhs tc { tcName = J [] lhs }]
  [sgQ| module $mid:n : $sigexp |]              → do
    sig         ← tcSigExp γ sigexp
    return [SgMod n sig]
  [sgQ| module type $sid:n = $sigexp |]         → do
    sig         ← tcSigExp γ sigexp
    return [SgSig n sig]
  [sgQ| include $sigexp |]                      → tcSigExp γ sigexp
  [sgQ| exception $cid:c of $opt:mt |]          → do
    mσ ← toEmptyF <$$> mapM (tcType mempty γ) mt
    return [SgExn c mσ]
  [sgQ| $anti:a |]                              → $(AST.antifail)

-- | Type check a signature body
tcSigItems   ∷ MonadConstraint tv r m ⇒
               Γ tv → [AST.SigItem R] → m (Signature tv)
tcSigItems _ []       = return []
tcSigItems γ (sg:sgs) = do
  sig0  ← tcSigItem γ sg
  γ'    ← γ !+! sig0
  sig1  ← tcSigItems γ' sgs
  return (sig0 ++ sig1)

-- | Type check a signature expression
tcSigExp ∷ MonadConstraint tv r m ⇒
           Γ tv → AST.SigExp R → m (Signature tv)
tcSigExp γ sigexp0 = withLocation sigexp0 $ case sigexp0 of
  [seQ| sig $list:sgs end |]                    → tcSigItems γ sgs
  [seQ| $qsid:n $list:_ |]                      → fst <$> γ !.! n
  [seQ| $sigexp with type $list:αs $qtid:qc = $t |]
                                                → do
    sig         ← tcSigExp γ sigexp
    let td      = AST.tdSyn (jname qc) [(AST.tpVar <$> αs <*> pure 1, t)]
    [(_, tc)]   ← tcTyDecs' [] γ [td]
    return (fibrate qc tc sig)
  [seQ| $anti:a |]                              → $(AST.antifail)

-- | Type check the type declarations of an abstype block
tcAbsTys ∷ MonadConstraint tv r m ⇒
           [ModId] → Γ tv → [AST.AbsTy R] →
           m (Signature tv, Signature tv)
tcAbsTys μ γ ats = do
  (arities, quals, tydecs) ← unzip3 <$> mapM unAbsTy ats
  ntcs0                    ← tcTyDecs' μ γ tydecs
  ntcs1 ← sequence
    [ do
        qe ← indexQuals (AST.tdParams (view td)) qual
        let tc' = tc {
                    tcArity = arity,
                    tcQual  = qe,
                    tcCons  = mempty,
                    tcNext  = Nothing
                  }
        checkTyConMonotone (AST.tdParams (view td)) tc
        return (n, tc')
    | (n, tc) ← ntcs0
    | arity   ← arities
    | qual    ← quals
    | td      ← tydecs ]
  return (uncurry SgTyp <$> ntcs0, uncurry SgTyp <$> ntcs1)
  where
    unAbsTy (AST.N _ (AST.AbsTy arity qual td)) = return (arity, qual, td)
    unAbsTy (AST.N _ (AST.AbsTyAnti a))         = $(AST.antifail)

-- | Type check a type declaration group
tcTyDecs  ∷ MonadConstraint tv r m ⇒
            [ModId] → Γ tv → [AST.TyDec R] → m (Signature tv)
tcTyDecs  = uncurry SgTyp <$$$$$> tcTyDecs'

-- | Type check a type declaration group
tcTyDecs' ∷ MonadConstraint tv r m ⇒
            [ModId] → Γ tv → [AST.TyDec R] → m [(TypId, TyCon)]
tcTyDecs' μ γ tds = do
  stub_sig  ← forM tds $ \td → withLocation td $ case view td of
    AST.TdDat tid params _
      → allocStub tid (AST.tvqual <$> params)
    AST.TdSyn tid ((tps,_):_)
      → allocStub tid (Qa <$ tps)
    AST.TdAbs tid params variances guards qual
      → do
        qe ← indexQuals params qual
        ix ← tvUniqueID <$> newTV
        let tc = mkTC ix (J (reverse μ) tid)
                         qe
                         (zip3 variances
                               (AST.tvqual <$> params)
                               ((`elem` guards) <$> params))
        checkTyConMonotone params tc
        return (tid, tc)
    AST.TdSyn _ []
      → typeBug "tcTyDecs'" "Saw type synonym with 0 clauses."
    AST.TdAnti a
      → $(AST.antifail)
  real_sig ← iterChanging <-> stub_sig $ \sig →
    zipWithM (tcTyDec (γ =+= Env.fromList sig)) tds sig
  return (second (replaceTyCons (snd <$> real_sig)) <$> real_sig)
  where
    allocStub tid bounds = do
      ix ← tvUniqueID <$> newTV
      return (tid, mkTC ix (J (reverse μ) tid)
                           ((Omnivariant,,False) <$> bounds) ∷ TyCon)
    --

checkTyConMonotone ∷ MonadAlmsError m ⇒ [AST.TyVar R] → TyCon → m ()
checkTyConMonotone params tc = do
  let ftv_qe  = ftvSet (tcQual tc)
      bad_tvs = map ([msg| $2 (variance $1, at $3) |]
                       <$> sel2 <*> sel3 <*> AST.getLoc. sel3) .
                filter (\tup → S.member (sel1 tup) ftv_qe) .
                filter (\tup → sel2 tup ⊑ Contravariant) $
                zip3 [ 0 .. ] (tcArity tc) params
      name    = tcName tc
  unless (null bad_tvs) $
    typeError [msg| Type declaration for $q:name is inadmissable
      because it doesn’t satisfy the monotonicity condition for
      type constructors.
      All type variable parameters that appear in the qualifier of
      a type must be covariant or invariant, this is not satisfied
      by all parameters of $q:name:
      $ul:bad_tvs
    |]

tcTyDec ∷ MonadConstraint tv r m ⇒
          Γ tv → AST.TyDec R → (TypId, TyCon) → m (TypId, TyCon)
tcTyDec γ td (tid, tc) = withLocation td $ case view td of
  AST.TdDat _ params alts
    → do
      αs        ← mapM (curry newTV' Skolem) params
      let δ     = params -:*- αs
      mσs       ← mapM (mapM (tcType δ γ) . snd) alts
      let mσs'          = toEmptyF . closeTy 0 αs <$$> mσs
          arity         = M.findWithDefault 0 <-> ftvV mσs <$> αs
          bounds        = AST.tvqual <$> params
          guards        = M.findWithDefault True <-> ftvG mσs <$> αs
          qual          = case qualifierEnv [bounds] mσs' of
            QeA     → QeA
            QeU set → QeU (S.mapMonotonic each set)
              where each (Bound _ j _) = j
                    each (Free r)      = elimEmpty r
      when (arity  /= tcArity tc
         || bounds /= tcBounds tc
         || guards /= tcGuards tc
         || qual   /= tcQual tc)
        setChanged
      return (tid, tc {
                     tcArity  = arity,
                     tcBounds = bounds,
                     tcGuards = guards,
                     tcQual   = qual,
                     tcCons   = map fst alts -:*- mσs'
                   })
  AST.TdSyn _ cs@((tps0, _):_)
    → do
      let nparams = length tps0
      tassert (all ((== nparams) . length . fst) cs)
        [msg| In definition of type operator $q:tid, not all clauses
              have the same number of parameters. |]
      (cs', infos) ← unzip <$$> for cs $ \(tps, rhs) → do
        (tps', αss)     ← unzip <$> mapM (tcTyPat γ) tps
        αss'            ← mapM (mapM (const newTV . fst)) αss
        let (dot, nonDot)
                        = L.partition (snd . fst)
                            (zip (concat αss) (concat αss'))
            dot_αs      = first fst <$> dot
            αs          = fst . fst <$> nonDot
            αs'         = snd <$> nonDot
        σ               ← tcTypeRowDots (αs -:*- αs') dot_αs γ rhs
        qlss            ← mapM getTVBounds αss'
        let σ'          = toEmptyF (closeTy 0 (concat αss') σ)
            -- For each pattern, for each of its type variables,
            -- a triple of its variance, inclusion in the qualifer,
            -- and guardedness:
            kindses     = tyPatKinds <$> tps'
        -- Bounds are computed by checking which type variables need to
        -- be bounded for the right-hand side to be well-formed, then
        -- checking which are similarly bounded on the left.  For those
        -- that are not yet bounded on the left, if they are involved in
        -- the qualifier of the pattern then we can bound them by bounding
        -- the pattern, but otherwise it's an error.
        bounds  ← sequence
          [ bigMeet <$> sequence
            [ if qll ⊑ qlr then return Qa
              else if inQExp then return Qu
              else do
                typeError_ $ uncurry
                  [msg|
                    Ill-formed type $1 declaration.
                    <br>
                    Type variable $α must be bounded by U (unlimited)
                    for the type on the right-hand side of $2 to be
                    well-formed, but it is not bounded by its appearance
                    in the pattern, and because it does not contribute
                    to the qualifier of the pattern, bounded the pattern
                    cannot effectively bound $α.
                  |] $
                  if length cs == 1
                    then ("synonym", "the declaration")
                    else ("operator", "clause " ++ show i)
                return Qa
            | (_, inQExp, _, qll) ← kindsi
            | α   ← αsi
            | qlr ← qlsi ]
          | i      ← [1 ∷ Int .. ]
          | kindsi ← kindses
          | αsi    ← αss
          | qlsi   ← qlss ]
            -- The arity of each parameter is the join of the products
            -- of the arities of the type variables in the pattern and
            -- rhs type.
        let varmap      = ftvV σ
            arity       = [ bigJoin $
                              zipWith (*)
                                (sel1 <$> kindsi)
                                (M.findWithDefault 0 <-> varmap <$> αsi')
                          | kindsi ← kindses
                          | αsi'   ← αss' ]
            -- This is very permissive:
            guardmap    = ftvG σ
            guards      = [ all2 (||)
                              (M.findWithDefault True <-> guardmap <$> αsi')
                              (sel3 <$> kindsi)
                          | kindsi ← kindses
                          | αsi'   ← αss' ]
            -- For each parameter, a list of which of its type
            -- variables are significant to the qualifier
            qinvolveds = [ map snd . filter fst $
                              zip (sel2 <$> kindsi)
                                  αsi'
                          | kindsi ← kindses
                          | αsi'   ← αss' ]
            qual        = case qualifier σ of
              QeA       → QeA
              QeU βs    → bigJoin
               [ case L.findIndex (β `elem`) qinvolveds of
                   Nothing → QeA
                   Just ix
                     | Qu:_ ← drop ix bounds → qlitexp Qu
                     | otherwise             → qvarexp ix
               | Free β ← S.toList βs ]
        return ((tps', σ'), (arity, bounds, guards, qual))
      let (arities, boundses, guardses, quals) = unzip4 infos
          arity  = foldl1 (zipWith (⊔)) arities
          bounds = foldl1 (zipWith (⊓)) boundses
          guards = foldl1 (zipWith (&&)) guardses
          qual   = bigJoin quals
      when (arity  /= tcArity tc
         || bounds /= tcBounds tc
         || guards /= tcGuards tc
         || qual   /= tcQual tc)
        setChanged
      traceN 1 ("bounds", bounds)
      return (tid, tc {
                     tcArity  = arity,
                     tcBounds = bounds,
                     tcGuards = guards,
                     tcQual   = qual,
                     tcNext   = Just cs'
                   })
  AST.TdAbs _ _ _ _ _
    → return (tid, tc)
  AST.TdSyn _ []
    → typeBug "tcTyDec" "Saw type synonym with 0 clauses."
  AST.TdAnti a
    → $(AST.antifail)

-- | Convert a syntactic qualifier expression into an internal
--   qualifier expression over 'Int'.
indexQuals ∷ MonadAlmsError m ⇒
             [AST.TyVar R] → AST.QExp R → m (QExp Int)
indexQuals params = qInterpret resolver where
  resolver tv = case L.findIndex (== tv) params of
    Nothing → typeBug "indexQuals" "tv not found in type params"
    Just ix → return ix

-- | Given a functor, replace the contents with 'Empty', provided
--   there is no contents. If an 'Empty' value is actually required,
--   this is an error.
toEmptyF ∷ Functor f ⇒ f a → f Empty
toEmptyF = fmap toEmpty where
  toEmpty _ = throw (almsBug StaticsPhase "tcDecl" "saw free type variable")

---
--- MODULE SYSTEM
---

-- | Functional update on a signature
fibrate  ∷ QTypId → TyCon → Signature tv → Signature tv
fibrate (J [] tid) tc sig = map eachItem sig where
  eachItem (SgTyp tid' _)
    | tid == tid'       = SgTyp tid tc
  eachItem sigitem      = sigitem
fibrate (J (mid:rest) tid) tc sig = map eachItem sig where
  eachItem (SgMod mid' sig')
    | mid == mid'       = SgMod mid (fibrate (J rest tid) tc sig')
  eachItem sigitem      = sigitem

---
--- TESTING
---

test_tcProg ∷ AST.Prog R →
              IO (Either [AlmsError]
                         (Maybe (Type (TV IORef)),
                          ConstraintState (TV IORef) IORef))
test_tcProg p =
  runConstraintIO
    constraintState0
    (subst =<< snd <$> tcProg test_g0 p)