{-# OPTIONS_GHC -Wunused-imports #-} module Agda.TypeChecking.Datatypes where import Control.Monad ( filterM ) import Control.Monad.Except ( MonadError(..), ExceptT(..), runExceptT ) import Data.Maybe (fromMaybe) import Agda.Syntax.Common import Agda.Syntax.Internal import Agda.TypeChecking.Monad import Agda.TypeChecking.Telescope import Agda.TypeChecking.Reduce import Agda.TypeChecking.Substitute import Agda.TypeChecking.Pretty import Agda.Utils.Either import Agda.Utils.Functor ( (<.>) ) import Agda.Syntax.Common.Pretty ( prettyShow ) import Agda.Utils.Size import Agda.Utils.Impossible --------------------------------------------------------------------------- -- * Constructors --------------------------------------------------------------------------- -- | Get true constructor with record fields. getConHead :: (HasConstInfo m) => QName -> m (Either SigError ConHead) getConHead c = runExceptT $ do def <- ExceptT $ getConstInfo' c case theDef def of Constructor { conSrcCon = c' } -> return c' Record { recConHead = c' } -> return c' _ -> throwError $ SigUnknown $ prettyShow c ++ " is not a constructor" isConstructor :: (HasConstInfo m) => QName -> m Bool isConstructor q = isRight <$> getConHead q -- | Get true constructor with fields, expanding literals to constructors -- if possible. getConForm :: QName -> TCM (Either SigError ConHead) getConForm c = caseEitherM (getConHead c) (return . Left) $ \ ch -> do Con con _ [] <- constructorForm (Con ch ConOCon []) return $ Right con -- | Augment constructor with record fields (preserve constructor name). -- The true constructor might only surface via 'reduce'. getOrigConHead :: QName -> TCM (Either SigError ConHead) getOrigConHead c = mapRight (setConName c) <$> getConHead c -- | Get the name of the datatype constructed by a given constructor. -- Precondition: The argument must refer to a constructor {-# SPECIALIZE getConstructorData :: QName -> TCM QName #-} getConstructorData :: HasConstInfo m => QName -> m QName getConstructorData c = do def <- getConstInfo c case theDef def of Constructor{conData = d} -> return d _ -> __IMPOSSIBLE__ -- | Is the datatype of this constructor a Higher Inductive Type? -- Precondition: The argument must refer to a constructor of a datatype or record. consOfHIT :: HasConstInfo m => QName -> m Bool consOfHIT c = do d <- getConstructorData c def <- theDef <$> getConstInfo d case def of Datatype {dataPathCons = xs} -> return $ not $ null xs Record{} -> return False _ -> __IMPOSSIBLE__ isPathCons :: HasConstInfo m => QName -> m Bool isPathCons c = do d <- getConstructorData c def <- theDef <$> getConstInfo d case def of Datatype {dataPathCons = xs} -> return $ c `elem` xs Record{} -> return False _ -> __IMPOSSIBLE__ -- | @getFullyAppliedConType c t@ computes the constructor parameters -- from data type @t@ and returns them -- plus the instantiated type of constructor @c@. -- -- @Nothing@ if @t@ is not a data/record type or does not have -- a constructor @c@. -- -- Precondition: @t@ is reduced. getFullyAppliedConType :: PureTCM m => ConHead -- ^ Constructor. -> Type -- ^ Reduced type of the fully applied constructor. -> m (Maybe ((QName, Type, Args), Type)) -- ^ @Nothing@ if not data or record type. -- -- @Just ((d, dt, pars), ct)@ otherwise, where -- @d@ is the data or record type name, -- @dt@ is the type of the data or record name, -- @pars@ are the reconstructed parameters, -- @ct@ is the type of the constructor instantiated to the parameters. getFullyAppliedConType c t = do reportSLn "tc.getConType" 35 $ unwords $ [ "getFullyAppliedConType", prettyShow c, prettyShow t ] c <- fromRight __IMPOSSIBLE__ <$> getConHead (conName c) cdef <- getConstInfo $ conName c let ctype = defType cdef cdata = conData $ theDef cdef npars = conPars $ theDef cdef case unEl t of Def d es | d == cdata -> do reportSLn "tc.getConType" 35 $ unwords $ [ "getFullyAppliedConType: case Def", prettyShow d, prettyShow es ] dt <- defType <$> getConstInfo d let pars = fromMaybe __IMPOSSIBLE__ $ allApplyElims $ take npars es ctPars <- ctype `piApplyM` pars return $ Just ((d, dt, pars), ctPars) _ -> return Nothing -- | Make sure a constructor is fully applied and infer the type of the constructor. -- Raises a type error if the constructor does not belong to the given type. fullyApplyCon :: (PureTCM m, MonadBlock m, MonadTCError m) => ConHead -- ^ Constructor. -> Elims -- ^ Constructor arguments. -> Type -- ^ Type of the constructor application. -> (QName -> Type -> Args -> Type -> Elims -> Telescope -> Type -> m a) -- ^ Name of the data/record type, -- type of the data/record type, -- reconstructed parameters, -- type of the constructor (applied to parameters), -- full application arguments, -- types of missing arguments (already added to context), -- type of the full application. -> m a fullyApplyCon c vs t ret = fullyApplyCon' c vs t ret $ typeError . DoesNotConstructAnElementOf (conName c) -- | Like @fullyApplyCon@, but calls the given fallback function if -- it encounters something other than a datatype. fullyApplyCon' :: (PureTCM m, MonadBlock m) => ConHead -- ^ Constructor. -> Elims -- ^ Constructor arguments. -> Type -- ^ Type of the constructor application. -> (QName -> Type -> Args -> Type -> Elims -> Telescope -> Type -> m a) -- ^ See @fullyApplyCon@ -> (Type -> m a) -- ^ Fallback function -> m a fullyApplyCon' c vs t0 ret err = do reportSDoc "tc.getConType" 30 $ sep $ [ "fullyApplyCon': constructor " , prettyTCM c , " with arguments" , prettyTCM vs , " at type " , prettyTCM t0 ] (TelV tel t, boundary) <- telViewPathBoundaryP t0 -- The type of the constructor application may still be a function -- type. In this case, we introduce the domains @tel@ into the context -- and apply the constructor to these fresh variables. addContext tel $ do reportSLn "tc.getConType" 35 $ " target type: " ++ prettyShow t t <- abortIfBlocked t getFullyAppliedConType c t >>= \case Nothing -> err t Just ((d, dt, pars), a) -> ret d dt pars a (raise (size tel) vs ++ teleElims tel boundary) tel t -- | @getConType c t@ computes the constructor parameters from type @t@ -- and returns them plus the instantiated type of constructor @c@. -- This works also if @t@ is a function type ending in a data/record type; -- the term from which @c@ comes need not be fully applied -- -- @Nothing@ if @t@ is not a data/record type or does not have -- a constructor @c@. getConType :: (PureTCM m, MonadBlock m) => ConHead -- ^ Constructor. -> Type -- ^ Ending in data/record type. -> m (Maybe ((QName, Type, Args), Type)) -- ^ @Nothing@ if not ends in data or record type. -- -- @Just ((d, dt, pars), ct)@ otherwise, where -- @d@ is the data or record type name, -- @dt@ is the type of the data or record name, -- @pars@ are the reconstructed parameters, -- @ct@ is the type of the constructor instantiated to the parameters. getConType ch t = do let c = conName ch -- Optimization: if the constructor has no parameters, there -- is no need to reduce the type. npars <- getNumberOfParameters c if | npars == Just 0 -> do ctype <- defType <$> getConstInfo c d <- getConstructorData c dtype <- defType <$> getConstInfo d return $ Just ((d,dtype,[]),ctype) | otherwise -> fullyApplyCon' ch [] t (\d dt pars ct es tel a -> return $ -- Now @dt@, @pars@, and @ct@ live under @tel@, -- so we need to remove the dependency on @tel@. let escape = applySubst (strengthenS impossible (size tel)) in Just $ escape ((d, dt, pars), ct)) (\_ -> return Nothing) data ConstructorInfo = DataCon Arity -- ^ Arity of the data constructor. | RecordCon PatternOrCopattern HasEta Arity -- ^ Arity of the record constructor. [Dom QName] -- ^ List of field names. Has length 'Arity'. -- | Return the number of non-parameter arguments to a constructor (arity). -- In case of record constructors, also return the field names (plus other info). -- getConstructorInfo :: HasConstInfo m => QName -> m ConstructorInfo getConstructorInfo c = fromMaybe __IMPOSSIBLE__ <$> getConstructorInfo' c getConstructorInfo' :: HasConstInfo m => QName -> m (Maybe ConstructorInfo) getConstructorInfo' c = do (theDef <$> getConstInfo c) >>= \case Constructor{ conData = d, conArity = n } -> Just <$> do (theDef <$> getConstInfo d) >>= \case r@Record{ recFields = fs } -> return $ RecordCon (recPatternMatching r) (recEtaEquality r) n fs Datatype{} -> return $ DataCon n _ -> __IMPOSSIBLE__ _ -> return Nothing --------------------------------------------------------------------------- -- * Data types --------------------------------------------------------------------------- -- | Check if a name refers to a datatype or a record with a named constructor. isDatatype :: QName -> TCM Bool isDatatype d = do def <- getConstInfo d case theDef def of Datatype{} -> return True Record{recNamedCon = namedC} -> return namedC _ -> return False -- | Check if a name refers to a datatype or a record. isDataOrRecordType :: QName -> TCM (Maybe DataOrRecord) isDataOrRecordType d = do (theDef <$> getConstInfo d) >>= \case Record{ recEtaEquality', recPatternMatching } -> return $ Just $ IsRecord $ case recPatternMatching of -- If the user explicitly asked for @pattern@, pattern matching is allowed. p@PatternMatching -> p -- Otherwise, 'recEtaEquality' might allow pattern matching. CopatternMatching -> if patternMatchingAllowed recEtaEquality' then PatternMatching else CopatternMatching Datatype{} -> return $ Just IsData _ -> return $ Nothing -- | Precodition: 'Term' is 'reduce'd. isDataOrRecord :: Term -> TCM (Maybe (QName, DataOrRecord)) isDataOrRecord = \case Def d _ -> fmap (d,) <$> isDataOrRecordType d _ -> return Nothing getNumberOfParameters :: HasConstInfo m => QName -> m (Maybe Nat) getNumberOfParameters d = do def <- getConstInfo d case theDef def of Datatype{ dataPars = n } -> return $ Just n Record{ recPars = n } -> return $ Just n Constructor{ conPars = n } -> return $ Just n _ -> return Nothing -- | This is a simplified version of @isDatatype@ from @Coverage@, -- useful when we do not want to import the module. getDatatypeArgs :: HasConstInfo m => Type -> m (Maybe (QName, Args, Args)) getDatatypeArgs t = do case unEl t of Def d es -> do let ~(Just args) = allApplyElims es def <- theDef <$> getConstInfo d case def of Datatype{dataPars = np} -> do let !(ps, is) = splitAt np args return $ Just (d, ps, is) Record{} -> do return $ Just (d, args, []) _ -> return Nothing _ -> return Nothing getNotErasedConstructors :: QName -> TCM [QName] getNotErasedConstructors d = do filterM (usableModality <.> getConstInfo) =<< getConstructors d -- | Precondition: Name is a data or record type. getConstructors :: QName -> TCM [QName] getConstructors d = fromMaybe __IMPOSSIBLE__ <$> getConstructors' d -- | 'Nothing' if not data or record type name. getConstructors' :: QName -> TCM (Maybe [QName]) getConstructors' d = getConstructors_ . theDef <$> getConstInfo d -- | 'Nothing' if not data or record definition. getConstructors_ :: Defn -> Maybe [QName] getConstructors_ = \case Datatype{dataCons = cs} -> Just cs Record{recConHead = h} -> Just [conName h] _ -> Nothing