rzk-0.11.4: src/Rzk/TypeCheck/MetaPrefix.hs
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Diagnostics for unsaturated schematic parameters.
--
-- A declaration's /meta prefix/ ends at its last schematic parameter:
-- a universe, @CUBE@, @TOPE@, or a function quantifying over or into one.
--
-- * Structural checking requires the full prefix at object-level uses.
-- * Strict checking confines schema arguments to a top-level receiver's meta prefix.
--
-- Strict checking is the default. Neither check establishes universe
-- levels; the Hurkens construction still passes these checks.
module Rzk.TypeCheck.MetaPrefix (
metaPrefixOf,
isMetaType,
recordMetaPrefixUses,
) where
import Control.Monad (forM_, when)
import Control.Monad.Except (catchError)
import Control.Monad.Reader (ask, asks)
import Data.Bifoldable (bifoldr)
import Data.Maybe (fromMaybe)
import Control.Monad.Foil (Distinct)
import qualified Control.Monad.Foil as Foil
import Control.Monad.Free.Foil (AST (Node, Var))
import Control.Monad.Free.Foil.Annotated (AnnSig (..))
import Language.Rzk.Foil.Names (TModality (..), TypeInfo (..),
VarIdent, binderName)
import Language.Rzk.Foil.Syntax
import Rzk.TypeCheck.Context
import Rzk.TypeCheck.Eval
import Rzk.TypeCheck.Monad
-- * Classifying types
-- | Recognise universes and functions quantifying over or into universes.
-- Includes @CUBE@ and @TOPE@; propagates classification failures.
isMetaType :: Distinct n => TermT n -> TypeCheck n Bool
isMetaType t = do
t' <- headView t
case t' of
UniverseT{} -> pure True
UniverseCubeT{} -> pure True
UniverseTopeT{} -> pure True
TypeFunT _ orig md param _mtope ret -> do
paramMeta <- isMetaType param
if paramMeta
then pure True
else inScope orig md param ret isMetaType
_ -> pure False
-- | Count parameters through the last schematic one.
-- An incomplete classification is reported when meta-prefix checking is enabled.
metaPrefixOf :: Distinct n => TermT n -> TypeCheck n Int
metaPrefixOf ty = go 1 0 ty `catchError` \err -> do
sensitivity <- asks effectiveMetaPrefixSensitivity
when (sensitivity /= MetaPrefixOff) $
reportIncompleteRSTTCheck "RSTT meta-prefix classification incomplete" err
pure 0
where
go :: Distinct l => Int -> Int -> TermT l -> TypeCheck l Int
go pos acc t = headView t >>= \case
TypeFunT _ orig md param _mtope ret -> do
meta <- isMetaType param
let acc' = if meta then pos else acc
inScope orig md param ret (go (pos + 1) acc')
_ -> pure acc
-- Strip restrictions and unfold only when the head needs classification.
headView :: Distinct n => TermT n -> TypeCheck n (TermT n)
headView t = case stripTypeRestrictions t of
t'@UniverseT{} -> pure t'
t'@UniverseCubeT{} -> pure t'
t'@UniverseTopeT{} -> pure t'
t'@TypeFunT{} -> pure t'
t'@TypeSigmaT{} -> pure t'
t'@TypeIdT{} -> pure t'
t' -> stripTypeRestrictions <$> whnfT t'
-- * The use-site walk
-- | How a position is classified under the two candidate rules.
data PosKind = MetaPos | ObjectPos
deriving (Eq)
data Positions = Positions
{ posStructural :: PosKind
, posStrict :: PosKind
}
rootPositions, typePositions, objectPositions :: Positions
rootPositions = Positions MetaPos MetaPos
typePositions = Positions MetaPos MetaPos
objectPositions = Positions ObjectPos ObjectPos
-- | Report uses that supply too few schematic arguments.
-- Violations are errors when @rstt-safe = "error"@.
recordMetaPrefixUses
:: forall n. Distinct n
=> VarIdent -> TermT n -> Maybe (TermT n) -> TypeCheck n ()
recordMetaPrefixUses defName ty mval =
asks effectiveMetaPrefixSensitivity >>= \case
MetaPrefixOff -> pure ()
_ -> localVerbosity Silent $
flip catchError (reportIncompleteRSTTCheck ("RSTT meta-parameter check incomplete in " <> show defName)) $ do
go rootPositions ty
mapM_ (go rootPositions) mval
where
go :: forall l. Distinct l => Positions -> TermT l -> TypeCheck l ()
go pos t = case t of
Var v -> checkHead pos v 0
AppT{} -> do
let (h, args) = collectSpine t []
mk <- case h of
Var v -> do
ctx <- ask
let info = lookupVarInfo v ctx
checkHead pos v (length args)
pure (if varIsTopLevel info then Just (varMetaPrefix info) else Nothing)
_ -> do
go objectPositions h
pure Nothing
forM_ (zip [1 :: Int ..] args) $ \(i, (fnode, arg)) -> do
domMeta <- domainIsMeta fnode
let strict = case mk of
Just k | domMeta && i <= k -> MetaPos
_ -> ObjectPos
go (Positions (if domMeta then MetaPos else ObjectPos) strict) arg
LambdaT info orig mparam body -> do
(mdom, md) <- case mparam of
Just (LambdaParam m ty' mtope) -> do
go typePositions ty'
forM_ mtope $ \tope -> inScope orig m ty' tope (go objectPositions)
pure (Just ty', m)
-- A bare λ: the domain of its own Π-type.
Nothing -> do
dom <- funDomain (infoType info)
pure (dom, Id)
inScope orig md (fromMaybe universeT mdom) body (go pos)
TypeFunT _ orig md param mtope ret -> do
go typePositions param
forM_ mtope $ \tope -> inScope orig md param tope (go objectPositions)
inScope orig md param ret (go typePositions)
TypeSigmaT _ orig md a bscope -> do
go typePositions a
inScope orig md a bscope (go typePositions)
-- The endpoints are terms; storing an unsaturated schema in an
-- identity type is an object-level use.
TypeIdT _ a mtA b -> do
go objectPositions a
mapM_ (go typePositions) mtA
go objectPositions b
TypeRestrictedT _ ty' rs -> do
go typePositions ty'
forM_ rs $ \(tope, term) -> do
go objectPositions tope
go objectPositions term
LetT _ orig manno value body -> do
mapM_ (go typePositions) manno
go objectPositions value
let valueType = case typeInfoOf value of
Just valueInfo -> Just (infoType valueInfo)
Nothing -> manno
inScopeWith orig Id (fromMaybe universeT valueType) (Just value) body (go pos)
LetModT _ orig _nu mu manno mmotive value body -> do
mapM_ (go typePositions) manno
mapM_ (go typePositions) mmotive
go objectPositions value
unwrapped <- case typeInfoOf value of
Nothing -> pure Nothing
Just valueInfo -> modalDomain (infoType valueInfo)
inScope orig mu (fromMaybe universeT unwrapped) body (go pos)
Node (AnnSig _ f) ->
mapM_ (go objectPositions) (bifoldr (\_ acc -> acc) (:) [] f)
-- An unsaturated top-level head at an object position warns; at a
-- position only the strict rule rejects, the warning is marked so.
checkHead :: forall l. Distinct l => Positions -> Foil.Name l -> Int -> TypeCheck l ()
checkHead pos v nargs = do
ctx <- ask
let info = lookupVarInfo v ctx
k = varMetaPrefix info
when (varIsTopLevel info && k > nargs) $ do
sensitivity <- asks effectiveMetaPrefixSensitivity
let mrule = case (posStructural pos, posStrict pos) of
(ObjectPos, _) -> Just MetaPrefixBoth
(MetaPos, ObjectPos)
| MetaPrefixStrict <- sensitivity -> Just MetaPrefixStrictOnly
_ -> Nothing
forM_ mrule $ \rule -> do
loc <- asks ctxLocation
recordCheckWarning $ MetaPrefixWarning
defName
(fromMaybe "_" (binderName (varOrig info)))
nargs
k
rule
loc
-- Is the Π-domain of this function node meta-shaped?
domainIsMeta :: forall l. Distinct l => TermT l -> TypeCheck l Bool
domainIsMeta f = do
tf <- typeOfUncomputed f
headView tf >>= \case
TypeFunT _ _ _ dom _ _ -> isMetaType dom
_ -> pure False
funDomain :: forall l. Distinct l => TermT l -> TypeCheck l (Maybe (TermT l))
funDomain tf =
headView tf >>= \case
TypeFunT _ _ _ dom _ _ -> pure (Just dom)
_ -> pure Nothing
modalDomain :: forall l. TermT l -> TypeCheck l (Maybe (TermT l))
modalDomain tv =
pure $ case stripTypeRestrictions tv of
TypeModalT _ _ a -> Just a
_ -> Nothing
collectSpine :: TermT n -> [(TermT n, TermT n)] -> (TermT n, [(TermT n, TermT n)])
collectSpine (AppT _ f x) acc = collectSpine f ((f, x) : acc)
collectSpine h acc = (h, acc)