{-# LANGUAGE CPP, RelaxedPolyRec, GeneralizedNewtypeDeriving #-} module Agda.TypeChecking.MetaVars where import Control.Monad.Reader import Control.Monad.State import Control.Monad.Error import Data.Generics import Data.List as List hiding (sort) import Data.Map (Map) import qualified Data.Map as Map import qualified Agda.Utils.IO.Locale as LocIO import Agda.Syntax.Common import qualified Agda.Syntax.Info as Info import Agda.Syntax.Internal import Agda.Syntax.Position import Agda.Syntax.Literal import qualified Agda.Syntax.Abstract as A import Agda.TypeChecking.Monad import Agda.TypeChecking.Monad.Builtin import Agda.TypeChecking.Reduce import Agda.TypeChecking.Substitute import Agda.TypeChecking.Constraints import Agda.TypeChecking.Errors import Agda.TypeChecking.Free import Agda.TypeChecking.Records import Agda.TypeChecking.Pretty import Agda.TypeChecking.Irrelevance import Agda.TypeChecking.EtaContract import Agda.TypeChecking.MetaVars.Occurs import {-# SOURCE #-} Agda.TypeChecking.Conversion -- SOURCE NECESSARY import Agda.Utils.Fresh import Agda.Utils.List import Agda.Utils.Monad import Agda.Utils.Size import Agda.Utils.Permutation import qualified Agda.Utils.VarSet as Set import Agda.TypeChecking.Monad.Debug #include "../undefined.h" import Agda.Utils.Impossible -- | Find position of a value in a list. -- Used to change metavar argument indices during assignment. -- -- @reverse@ is necessary because we are directly abstracting over the list. -- findIdx :: Eq a => [a] -> a -> Maybe Int findIdx vs v = findIndex (==v) (reverse vs) -- | Check whether a meta variable is a place holder for a blocked term. isBlockedTerm :: MetaId -> TCM Bool isBlockedTerm x = do reportSLn "tc.meta.blocked" 12 $ "is " ++ show x ++ " a blocked term? " i <- mvInstantiation <$> lookupMeta x let r = case i of BlockedConst{} -> True PostponedTypeCheckingProblem{} -> True InstV{} -> False InstS{} -> False Open{} -> False OpenIFS{} -> False reportSLn "tc.meta.blocked" 12 $ if r then " yes, because " ++ show i else " no" return r isEtaExpandable :: MetaId -> TCM Bool isEtaExpandable x = do i <- mvInstantiation <$> lookupMeta x return $ case i of Open{} -> True OpenIFS{} -> False InstV{} -> False InstS{} -> False BlockedConst{} -> False PostponedTypeCheckingProblem{} -> False -- * Performing the assignment -- | Performing the meta variable assignment. -- -- The instantiation should not be an 'InstV' or 'InstS' and the 'MetaId' -- should point to something 'Open' or a 'BlockedConst'. -- Further, the meta variable may not be 'Frozen'. assignTerm :: MetaId -> Term -> TCM () assignTerm x t = do reportSLn "tc.meta.assign" 70 $ show x ++ " := " ++ show t whenM (isFrozen x) __IMPOSSIBLE__ -- verify (new) invariant let i = metaInstance (killRange t) verboseS "profile.metas" 10 $ liftTCM $ tickMax "max-open-metas" . size =<< getOpenMetas modifyMetaStore $ ins x i etaExpandListeners x wakeupConstraints x reportSLn "tc.meta.assign" 20 $ "completed assignment of " ++ show x where metaInstance = InstV ins x i store = Map.adjust (inst i) x store inst i mv = mv { mvInstantiation = i } -- * Creating meta variables. newSortMeta :: TCM Sort newSortMeta = ifM typeInType (return $ mkType 0) $ ifM hasUniversePolymorphism (newSortMetaCtx =<< getContextArgs) -- else (no universe polymorphism) $ do i <- createMetaInfo x <- newMeta i normalMetaPriority (idP 0) (IsSort () topSort) return $ Type $ Max [Plus 0 $ MetaLevel x []] newSortMetaCtx :: Args -> TCM Sort newSortMetaCtx vs = ifM typeInType (return $ mkType 0) $ do i <- createMetaInfo tel <- getContextTelescope let t = telePi_ tel topSort x <- newMeta i normalMetaPriority (idP 0) (IsSort () t) reportSDoc "tc.meta.new" 50 $ text "new sort meta" <+> prettyTCM x <+> text ":" <+> prettyTCM t return $ Type $ Max [Plus 0 $ MetaLevel x vs] newTypeMeta :: Sort -> TCM Type newTypeMeta s = El s <$> newValueMeta (sort s) newTypeMeta_ :: TCM Type newTypeMeta_ = newTypeMeta =<< (workOnTypes $ newSortMeta) -- TODO: (this could be made work with new uni-poly) -- Andreas, 2011-04-27: If a type meta gets solved, than we do not have to check -- that it has a sort. The sort comes from the solution. -- newTypeMeta_ = newTypeMeta Inf -- | Create a new "implicit from scope" metavariable newIFSMeta :: Type -> TCM Term newIFSMeta t = do vs <- getContextArgs tel <- getContextTelescope newIFSMetaCtx (telePi_ tel t) vs -- | Create a new value meta with specific dependencies. newIFSMetaCtx :: Type -> Args -> TCM Term newIFSMetaCtx t vs = do i <- createMetaInfo let TelV tel _ = telView' t perm = idP (size tel) x <- newMeta' OpenIFS i normalMetaPriority perm (HasType () t) reportSDoc "tc.meta.new" 50 $ fsep [ text "new ifs meta:" , nest 2 $ prettyTCM vs <+> text "|-" , nest 2 $ text (show x) <+> text ":" <+> prettyTCM t ] solveConstraint_ $ FindInScope x return (MetaV x vs) -- | Create a new metavariable, possibly η-expanding in the process. newValueMeta :: Type -> TCM Term newValueMeta t = do vs <- getContextArgs tel <- getContextTelescope newValueMetaCtx (telePi_ tel t) vs newValueMetaCtx :: Type -> Args -> TCM Term newValueMetaCtx t ctx = do m@(MetaV i _) <- newValueMetaCtx' t ctx instantiateFull m -- | Create a new value meta without η-expanding. newValueMeta' :: Type -> TCM Term newValueMeta' t = do vs <- getContextArgs tel <- getContextTelescope newValueMetaCtx' (telePi_ tel t) vs -- | Create a new value meta with specific dependencies. newValueMetaCtx' :: Type -> Args -> TCM Term newValueMetaCtx' t vs = do i <- createMetaInfo let TelV tel _ = telView' t perm = idP (size tel) x <- newMeta i normalMetaPriority perm (HasType () t) reportSDoc "tc.meta.new" 50 $ fsep [ text "new meta:" , nest 2 $ prettyTCM vs <+> text "|-" , nest 2 $ text (show x) <+> text ":" <+> prettyTCM t ] etaExpandMetaSafe x return $ MetaV x vs newTelMeta :: Telescope -> TCM Args newTelMeta tel = newArgsMeta (abstract tel $ El Prop $ Sort Prop) newArgsMeta :: Type -> TCM Args newArgsMeta t = do args <- getContextArgs tel <- getContextTelescope newArgsMetaCtx t tel args newArgsMetaCtx :: Type -> Telescope -> Args -> TCM Args newArgsMetaCtx (El s tm) tel ctx = do tm <- reduce tm case tm of Pi (Arg h r a) _ -> do arg <- (Arg h r) <$> {- -- Andreas, 2010-09-24 skip irrelevant record fields when eta-expanding a meta var -- Andreas, 2010-10-11 this is WRONG, see Issue 347 if r == Irrelevant then return DontCare else -} newValueMetaCtx (telePi_ tel a) ctx args <- newArgsMetaCtx (El s tm `piApply` [arg]) tel ctx return $ arg : args _ -> return [] -- | Create a metavariable of record type. This is actually one metavariable -- for each field. newRecordMeta :: QName -> Args -> TCM Term newRecordMeta r pars = do args <- getContextArgs tel <- getContextTelescope newRecordMetaCtx r pars tel args newRecordMetaCtx :: QName -> Args -> Telescope -> Args -> TCM Term newRecordMetaCtx r pars tel ctx = do ftel <- flip apply pars <$> getRecordFieldTypes r fields <- newArgsMetaCtx (telePi_ ftel $ sort Prop) tel ctx con <- getRecordConstructor r return $ Con con fields newQuestionMark :: Type -> TCM Term newQuestionMark t = do m@(MetaV x _) <- newValueMeta' t ii <- fresh addInteractionPoint ii x return m -- | Construct a blocked constant if there are constraints. blockTerm :: Type -> TCM Term -> TCM Term blockTerm t blocker = do (pid, v) <- newProblem blocker blockTermOnProblem t v pid blockTermOnProblem :: Type -> Term -> ProblemId -> TCM Term blockTermOnProblem t v pid = ifM (isProblemSolved pid) (return v) $ do i <- createMetaInfo vs <- getContextArgs tel <- getContextTelescope x <- newMeta' (BlockedConst $ abstract tel v) i lowMetaPriority (idP $ size tel) (HasType () $ telePi_ tel t) -- we don't instantiate blocked terms escapeContext (size tel) $ addConstraint (Guarded (UnBlock x) pid) reportSDoc "tc.meta.blocked" 20 $ vcat [ text "blocked" <+> prettyTCM x <+> text ":=" <+> escapeContext (size tel) (prettyTCM $ abstract tel v) , text " by" <+> (prettyTCM =<< getConstraintsForProblem pid) ] inst <- isInstantiatedMeta x case inst of True -> instantiate (MetaV x vs) False -> do -- We don't return the blocked term instead create a fresh metavariable -- that we compare against the blocked term once it's unblocked. This way -- blocked terms can be instantiated before they are unblocked, thus making -- constraint solving a bit more robust against instantiation order. v <- newValueMeta t i <- liftTCM (fresh :: TCM Integer) -- This constraint is woken up when unblocking, so it doesn't need a problem id. cmp <- buildProblemConstraint 0 (ValueCmp CmpEq t v (MetaV x vs)) listenToMeta (CheckConstraint i cmp) x return v -- | @unblockedTester t@ returns @False@ if @t@ is a meta or a blocked term. -- -- Auxiliary function to create a postponed type checking problem. unblockedTester :: Type -> TCM Bool unblockedTester t = do t <- reduceB $ unEl t case t of Blocked{} -> return False NotBlocked MetaV{} -> return False _ -> return True -- | Create a postponed type checking problem @e : t@ that waits for type @t@ -- to unblock (become instantiated or its constraints resolved). postponeTypeCheckingProblem_ :: A.Expr -> Type -> TCM Term postponeTypeCheckingProblem_ e t = do postponeTypeCheckingProblem e t (unblockedTester t) -- | Create a postponed type checking problem @e : t@ that waits for conditon -- @unblock@. A new meta is created in the current context that has as -- instantiation the postponed type checking problem. An 'UnBlock' constraint -- is added for this meta, which links to this meta. postponeTypeCheckingProblem :: A.Expr -> Type -> TCM Bool -> TCM Term postponeTypeCheckingProblem e t unblock = do i <- createMetaInfo tel <- getContextTelescope cl <- buildClosure (e, t, unblock) m <- newMeta' (PostponedTypeCheckingProblem cl) i normalMetaPriority (idP (size tel)) $ HasType () $ telePi_ tel t -- Create the meta that we actually return v <- newValueMeta t i <- liftTCM (fresh :: TCM Integer) vs <- getContextArgs cmp <- buildProblemConstraint 0 (ValueCmp CmpEq t v (MetaV m vs)) listenToMeta (CheckConstraint i cmp) m addConstraint (UnBlock m) return v -- | Eta expand metavariables listening on the current meta. etaExpandListeners :: MetaId -> TCM () etaExpandListeners m = do ls <- getMetaListeners m clearMetaListeners m -- we don't really have to do this mapM_ wakeupListener ls -- | Wake up a meta listener and let it do its thing wakeupListener :: Listener -> TCM () -- Andreas 2010-10-15: do not expand record mvars, lazyness needed for irrelevance wakeupListener (EtaExpand x) = etaExpandMetaSafe x wakeupListener (CheckConstraint _ c) = do reportSDoc "tc.meta.blocked" 20 $ text "waking boxed constraint" <+> prettyTCM c addAwakeConstraints [c] solveAwakeConstraints -- | Do safe eta-expansions for meta (@SingletonRecords,Levels@). etaExpandMetaSafe :: MetaId -> TCM () etaExpandMetaSafe = etaExpandMeta [SingletonRecords,Levels] -- | Various kinds of metavariables. data MetaKind = Records -- ^ Meta variables of record type. | SingletonRecords -- ^ Meta variables of \"hereditarily singleton\" record type. | Levels -- ^ Meta variables of level type, if type-in-type is activated. deriving (Eq, Enum, Bounded) -- | All possible metavariable kinds. allMetaKinds :: [MetaKind] allMetaKinds = [minBound .. maxBound] -- | Eta expand a metavariable, if it is of the specified kind. -- Don't do anything if the metavariable is a blocked term. etaExpandMeta :: [MetaKind] -> MetaId -> TCM () etaExpandMeta kinds m = whenM (isEtaExpandable m) $ do verboseBracket "tc.meta.eta" 20 ("etaExpandMeta " ++ show m) $ do meta <- lookupMeta m let HasType _ a = mvJudgement meta TelV tel b <- telViewM a let args = [ Arg h r $ Var i [] | (i, Arg h r _) <- reverse $ zip [0..] $ reverse $ telToList tel ] bb <- reduceB b -- the target in the type @a@ of @m@ case unEl <$> bb of -- if the target type of @m@ is a meta variable @x@ itself -- (@NonBlocked (MetaV{})@), -- or it is blocked by a meta-variable @x@ (@Blocked@), we cannot -- eta expand now, we have to postpone this. Once @x@ is -- instantiated, we can continue eta-expanding m. This is realized -- by adding @m@ to the listeners of @x@. Blocked x _ -> listenToMeta (EtaExpand m) x NotBlocked (MetaV x _) -> listenToMeta (EtaExpand m) x NotBlocked lvl@(Def r ps) -> ifM (isEtaRecord r) (do let expand = do u <- withMetaInfo (mvInfo meta) $ newRecordMetaCtx r ps tel args inContext [] $ addCtxTel tel $ do verboseS "tc.meta.eta" 15 $ do du <- prettyTCM u liftIO $ LocIO.putStrLn $ "eta expanding: " ++ show m ++ " --> " ++ show du noConstraints $ assignV m args u -- should never produce any constraints if Records `elem` kinds then expand else if SingletonRecords `elem` kinds then do singleton <- isSingletonRecord r ps case singleton of Left x -> listenToMeta (EtaExpand m) x Right False -> return () Right True -> expand else return () ) $ when (Levels `elem` kinds) $ do mlvl <- getBuiltin' builtinLevel tt <- typeInType if tt && Just lvl == mlvl then do reportSLn "tc.meta.eta" 20 $ "Expanding level meta to 0 (type-in-type)" noConstraints $ assignV m args (Level $ Max []) else return () _ -> return () -- | Eta expand blocking metavariables of record type, and reduce the -- blocked thing. etaExpandBlocked :: Reduce t => Blocked t -> TCM (Blocked t) etaExpandBlocked t@NotBlocked{} = return t etaExpandBlocked (Blocked m t) = do etaExpandMeta [Records] m t <- reduceB t case t of Blocked m' _ | m /= m' -> etaExpandBlocked t _ -> return t -- * Solve constraint @x vs = v@. -- | Assign to an open metavar which may not be frozen. -- First check that metavar args are in pattern fragment. -- Then do extended occurs check on given thing. -- -- Assignment is aborted by throwing a @PatternErr@ via a call to -- @patternViolation@. This error is caught by @catchConstraint@ -- during equality checking (@compareAtom@) and leads to -- restoration of the original constraints. assignV :: MetaId -> Args -> Term -> TCM () assignV x args v = do reportSDoc "tc.meta.assign" 10 $ do text "term" <+> prettyTCM (MetaV x args) <+> text ":=" <+> prettyTCM v liftTCM $ nowSolvingConstraints (assign x args v) `finally` solveAwakeConstraints -- | @assign sort? x vs v@ assign :: MetaId -> Args -> Term -> TCM () assign x args v = do mvar <- lookupMeta x -- information associated with meta x -- Andreas, 2011-05-20 TODO! -- full normalization (which also happens during occurs check) -- is too expensive! (see Issue 415) -- need to do something cheaper, especially if -- we are dealing with a Miller pattern that can be solved -- immediately! -- Ulf, 2011-08-25 DONE! -- Just instantiating the top-level meta, which is cheaper. The occurs -- check will first try without unfolding any definitions (treating -- arguments to definitions as flexible), if that fails it tries again -- with full unfolding. v <- instantiate v reportSLn "tc.meta.assign" 50 $ "MetaVars.assign: assigning to " ++ show v case (v, mvJudgement mvar) of (Sort Inf, HasType{}) -> typeError $ GenericError "Setω is not a valid type." _ -> return () -- We don't instantiate frozen mvars when (mvFrozen mvar == Frozen) $ do reportSLn "tc.meta.assign" 25 $ "aborting: meta is frozen!" patternViolation -- We never get blocked terms here anymore. TODO: we actually do. why? whenM (isBlockedTerm x) patternViolation -- Andreas, 2010-10-15 I want to see whether rhs is blocked reportSLn "tc.meta.assign" 50 $ "MetaVars.assign: I want to see whether rhs is blocked" reportSDoc "tc.meta.assign" 25 $ do v0 <- reduceB v case v0 of Blocked m0 _ -> text "r.h.s. blocked on:" <+> prettyTCM m0 NotBlocked{} -> text "r.h.s. not blocked" -- Normalise and eta contract the arguments to the meta. These are -- usually small, and simplifying might let us instantiate more metas. args <- etaContract =<< normalise args -- Andreas, 2011-04-21 do the occurs check first -- e.g. _1 x (suc x) = suc (_2 x y) -- even though the lhs is not a pattern, we can prune the y from _2 let varsL = freeVars args let relVL = Set.toList $ relevantVars varsL -- Andreas, 2011-10-06 only irrelevant vars that are direct -- arguments to the meta, hence, can be abstracted over, may -- appear on the rhs. (test/fail/Issue483b) -- let irrVL = Set.toList $ irrelevantVars varsL let fromIrrVar (Arg h Irrelevant (Var i [])) = [i] fromIrrVar (Arg h Irrelevant (DontCare (Var i []))) = [i] fromIrrVar _ = [] let irrVL = concat $ map fromIrrVar args reportSDoc "tc.meta.assign" 20 $ let pr (Var n []) = text (show n) pr (Def c []) = prettyTCM c pr (DontCare v) = pr v pr _ = text ".." in vcat [ text "mvar args:" <+> sep (map (pr . unArg) args) , text "fvars lhs (rel):" <+> sep (map (text . show) relVL) , text "fvars lhs (irr):" <+> sep (map (text . show) irrVL) ] -- Check that the x doesn't occur in the right hand side. -- Prune mvars on rhs such that they can only depend on varsL. -- Herein, distinguish relevant and irrelevant vars, -- since when abstracting irrelevant lhs vars, they may only occur -- irrelevantly on rhs. v <- liftTCM $ occursCheck x (relVL, irrVL) v reportSLn "tc.meta.assign" 15 "passed occursCheck" verboseS "tc.meta.assign" 30 $ do let n = size v when (n > 200) $ do d <- sep [ text "size" <+> text (show n) -- , nest 2 $ text "type" <+> prettyTCM t , nest 2 $ text "term" <+> prettyTCM v ] liftIO $ LocIO.print d -- Check that the arguments are variables ids <- checkAllVars args -- Check linearity of @ids@ -- Andreas, 2010-09-24: Herein, ignore the variables which are not -- free in v -- Ulf, 2011-09-22: we need to respect irrelevant vars as well, otherwise -- we'll build solutions where the irrelevant terms are not valid let fvs = allVars $ freeVars v reportSDoc "tc.meta.assign" 20 $ text "fvars rhs:" <+> sep (map (text . show) $ Set.toList fvs) unless (distinct $ filter (`Set.member` fvs) ids) $ do -- non-linear lhs: we cannot solve, but prune killResult <- prune x args $ Set.toList fvs reportSDoc "tc.meta.assign" 10 $ text "pruning" <+> prettyTCM x <+> (text $ if killResult `elem` [PrunedSomething,PrunedEverything] then "succeeded" else "failed") patternViolation {- Andreas, 2011-04-21 this does not work if not (distinct $ filter (`Set.member` fvs) ids) then do -- non-linear lhs: we cannot solve, but prune ok <- prune x args $ Set.toList fvs if ok then return [] else patternViolation else do -} -- we are linear, so we can solve! reportSDoc "tc.meta.assign" 25 $ text "preparing to instantiate: " <+> prettyTCM v -- Rename the variables in v to make it suitable for abstraction over ids. v' <- do -- Basically, if -- Γ = a b c d e -- ids = d b e -- then -- v' = (λ a b c d e. v) _ 1 _ 2 0 tel <- getContextTelescope gamma <- map defaultArg <$> getContextTerms let iargs = reverse $ zipWith (rename ids) [0..] $ reverse gamma v' = raise (size args) (abstract tel v) `apply` iargs return v' -- Andreas, 2011-04-18 to work with irrelevant parameters DontCare -- we need to construct tel' from the type of the meta variable -- (no longer from ids which may not be the complete variable list -- any more) let t = jMetaType $ mvJudgement mvar reportSDoc "tc.meta.assign" 15 $ text "type of meta =" <+> prettyTCM t -- reportSDoc "tc.meta.assign" 30 $ text "type of meta =" <+> text (show t) TelV tel0 core0 <- telViewM t let n = length args reportSDoc "tc.meta.assign" 30 $ text "tel0 =" <+> prettyTCM tel0 reportSDoc "tc.meta.assign" 30 $ text "#args =" <+> text (show n) when (size tel0 < n) __IMPOSSIBLE__ let tel' = telFromList $ take n $ telToList tel0 reportSDoc "tc.meta.assign" 10 $ text "solving" <+> prettyTCM x <+> text ":=" <+> prettyTCM (abstract tel' v') -- Perform the assignment (and wake constraints). Metas -- are top-level so we do the assignment at top-level. n <- size <$> getContextTelescope escapeContext n $ assignTerm x $ killRange (abstract tel' v') return () where -- @ids@ are the lhs variables (metavar arguments) -- @i@ is the variable from the context Gamma rename :: [Nat] -> Nat -> Arg Term -> Arg Term rename ids i arg = case findIndex (==i) ids of Just j -> fmap (const $ Var (fromIntegral j) []) arg Nothing -> fmap (const __IMPOSSIBLE__) arg -- we will end up here, but never look at the result type FVs = Set.VarSet -- | Check that arguments to a metavar are in pattern fragment. -- Assumes all arguments already in whnf. -- Parameters are represented as @Var@s so @checkArgs@ really -- checks that all args are @Var@s and returns the -- list of corresponding indices for each arg. -- Linearity has to be checked separately. -- -- @reverse@ is necessary because we are directly abstracting over this list @ids@. checkAllVars :: Args -> TCM [Nat] checkAllVars args = case allVarOrIrrelevant args of Nothing -> do reportSDoc "tc.meta.assign" 15 $ vcat [ text "not all variables: " <+> prettyTCM args , text " aborting assignment" ] patternViolation Just is -> return $ map unArg is -- | filter out irrelevant args and check that all others are variables. -- Return the reversed list of variables. allVarOrIrrelevant :: Args -> Maybe [Arg Nat] allVarOrIrrelevant args = foldM isVarOrIrrelevant [] args where isVarOrIrrelevant vars arg = case arg of Arg h r (Var i []) -> return $ Arg h r i : removeIrr i vars Arg h Irrelevant (DontCare (Var i [])) -> return $ addIrrIfNotPresent h i vars -- Andreas, 2011-04-27 keep irrelevant variables Arg h Irrelevant _ -> return $ Arg h Irrelevant (-1) : vars -- any impossible deBruijn index will do (see Jason Reed, LFMTP 09 "_" or Nipkow "minus infinity") _ -> Nothing -- in case of non-linearity make sure not to count the irrelevant vars addIrrIfNotPresent h i vars | any (\ (Arg _ _ j) -> j == i) vars = Arg h Irrelevant (-1) : vars | otherwise = Arg h Irrelevant i : vars removeIrr i = map (\ a@(Arg h r j) -> if r == Irrelevant && i == j then Arg h Irrelevant (-1) else a) updateMeta :: MetaId -> Term -> TCM () updateMeta mI v = do mv <- lookupMeta mI withMetaInfo (getMetaInfo mv) $ do args <- getContextArgs noConstraints $ assignV mI args v