409 lines
16 KiB
Idris
409 lines
16 KiB
Idris
module Lib.Check
|
|
|
|
import Control.Monad.Error.Either
|
|
import Control.Monad.Error.Interface
|
|
import Control.Monad.State
|
|
import Control.Monad.Identity
|
|
import Lib.Parser.Impl
|
|
import Lib.Prettier
|
|
import Data.List
|
|
import Data.Vect
|
|
import Data.String
|
|
import Lib.Types
|
|
import Lib.TT
|
|
import Lib.TopContext
|
|
import Lib.Syntax
|
|
|
|
-- renaming
|
|
-- dom gamma ren
|
|
data Pden = PR Nat Nat (List Nat)
|
|
|
|
-- IORef for metas needs IO
|
|
|
|
forceMeta : Val -> M Val
|
|
forceMeta (VMeta fc ix sp) = case !(lookupMeta ix) of
|
|
(Unsolved pos k xs) => pure (VMeta fc ix sp)
|
|
(Solved k t) => vappSpine t sp
|
|
forceMeta x = pure x
|
|
|
|
-- Lennart needed more forcing for recursive nat,
|
|
forceType : Val -> M Val
|
|
forceType (VRef fc nm def sp) =
|
|
case lookup nm !(get) of
|
|
(Just (MkEntry name type (Fn t))) => vappSpine !(eval [] CBN t) sp
|
|
_ => pure (VRef fc nm def sp)
|
|
forceType (VMeta fc ix sp) = case !(lookupMeta ix) of
|
|
(Unsolved x k xs) => pure (VMeta fc ix sp)
|
|
(Solved k t) => vappSpine t sp >>= forceType
|
|
forceType x = pure x
|
|
|
|
|
|
parameters (ctx: Context)
|
|
-- return renaming, the position is the new VVar
|
|
invert : Nat -> SnocList Val -> M (List Nat)
|
|
invert lvl sp = go sp []
|
|
where
|
|
go : SnocList Val -> List Nat -> M (List Nat)
|
|
go [<] acc = pure $ reverse acc
|
|
go (xs :< VVar emptyFC k [<]) acc = do
|
|
if elem k acc
|
|
then error emptyFC "non-linear pattern"
|
|
else go xs (k :: acc)
|
|
go _ _ = error emptyFC "non-variable in pattern"
|
|
|
|
-- we have to "lift" the renaming when we go under a lambda
|
|
-- I think that essentially means our domain ix are one bigger, since we're looking at lvl
|
|
-- in the codomain, so maybe we can just keep that value
|
|
rename : Nat -> List Nat -> Nat -> Val -> M Tm
|
|
rename meta ren lvl v = go ren lvl v
|
|
where
|
|
go : List Nat -> Nat -> Val -> M Tm
|
|
goSpine : List Nat -> Nat -> Tm -> SnocList Val -> M Tm
|
|
goSpine ren lvl tm [<] = pure tm
|
|
goSpine ren lvl tm (xs :< x) = do
|
|
xtm <- go ren lvl x
|
|
goSpine ren lvl (App emptyFC tm xtm) xs
|
|
|
|
go ren lvl (VVar fc k sp) = case findIndex (== k) ren of
|
|
Nothing => error emptyFC "scope/skolem thinger"
|
|
Just x => goSpine ren lvl (Bnd fc $ cast x) sp
|
|
go ren lvl (VRef fc nm def sp) = goSpine ren lvl (Ref fc nm def) sp
|
|
go ren lvl (VMeta fc ix sp) = if ix == meta
|
|
-- REVIEW is this the right fc?
|
|
then error fc "meta occurs check"
|
|
else goSpine ren lvl (Meta fc ix) sp
|
|
go ren lvl (VLam fc n t) = pure (Lam fc n !(go (lvl :: ren) (S lvl) !(t $$ VVar emptyFC lvl [<])))
|
|
go ren lvl (VPi fc n icit ty tm) = pure (Pi fc n icit !(go ren lvl ty) !(go (lvl :: ren) (S lvl) !(tm $$ VVar emptyFC lvl [<])))
|
|
go ren lvl (VU fc) = pure (U fc)
|
|
go ren lvl (VCase fc sc alts) = error fc "Case in solution"
|
|
-- This seems dicey, for VLam we eval and then process the levels.
|
|
-- Here we have raw Tm so we haven't even done occurs check. I'm thinking
|
|
-- we don't allow solutions with Case in them
|
|
-- pure (Case fc !(go ren lvl sc) alts)
|
|
go ren lvl (VLit fc lit) = pure (Lit fc lit)
|
|
|
|
lams : Nat -> Tm -> Tm
|
|
lams 0 tm = tm
|
|
-- REVIEW can I get better names in here?
|
|
lams (S k) tm = Lam emptyFC "arg_\{show k}" (lams k tm)
|
|
|
|
|
|
solve : Nat -> Nat -> SnocList Val -> Val -> M ()
|
|
solve l m sp t = do
|
|
ren <- invert l sp
|
|
tm <- rename m ren l t
|
|
let tm = lams (length sp) tm
|
|
top <- get
|
|
soln <- eval [] CBN tm
|
|
solveMeta top m soln
|
|
pure ()
|
|
|
|
unify : (l : Nat) -> Val -> Val -> M ()
|
|
|
|
unifySpine : Nat -> Bool -> SnocList Val -> SnocList Val -> M ()
|
|
unifySpine l False _ _ = error emptyFC "unify failed at head" -- unreachable now
|
|
unifySpine l True [<] [<] = pure ()
|
|
unifySpine l True (xs :< x) (ys :< y) = unify l x y >> unifySpine l True xs ys
|
|
unifySpine l True _ _ = error emptyFC "meta spine length mismatch"
|
|
|
|
unify l t u = do
|
|
debug "Unify \{show ctx.lvl}"
|
|
debug " \{show l} \{show t}"
|
|
debug " =?= \{show u}"
|
|
t' <- forceMeta t
|
|
u' <- forceMeta u
|
|
case (t',u') of
|
|
(VLam _ _ t, VLam _ _ t') => unify (l + 1) !(t $$ VVar emptyFC l [<]) !(t' $$ VVar emptyFC l [<])
|
|
(t, VLam fc' _ t') => unify (l + 1) !(t `vapp` VVar emptyFC l [<]) !(t' $$ VVar emptyFC l [<])
|
|
(VLam fc _ t, t' ) => unify (l + 1) !(t $$ VVar emptyFC l [<]) !(t' `vapp` VVar emptyFC l [<])
|
|
(VPi fc _ _ a b, VPi fc' _ _ a' b') => unify l a a' >> unify (S l) !(b $$ VVar emptyFC l [<]) !(b' $$ VVar emptyFC l [<])
|
|
(VVar fc k sp, VVar fc' k' sp' ) =>
|
|
if k == k' then unifySpine l (k == k') sp sp'
|
|
else error emptyFC "vvar mismatch \{show k} \{show k'}"
|
|
(VRef fc k def sp, VRef fc' k' def' sp' ) =>
|
|
if k == k' then unifySpine l (k == k') sp sp'
|
|
-- REVIEW - consider forcing?
|
|
else error emptyFC "vref mismatch \{show k} \{show k'}"
|
|
(VMeta fc k sp, VMeta fc' k' sp' ) =>
|
|
if k == k' then unifySpine l (k == k') sp sp'
|
|
else solve l k sp (VMeta fc' k' sp')
|
|
(t, VMeta fc' i' sp') => solve l i' sp' t
|
|
(VMeta fc i sp, t' ) => solve l i sp t'
|
|
(VU _, VU _) => pure ()
|
|
-- Lennart.newt cursed type references itself
|
|
-- We _could_ look up the ref, eval against [] and vappSpine...
|
|
(t, VRef fc' k' def sp') => do
|
|
debug "expand \{show t} =?= %ref \{k'}"
|
|
case lookup k' !(get) of
|
|
Just (MkEntry name ty (Fn tm)) => unify l t !(vappSpine !(eval [] CBN tm) sp')
|
|
_ => error emptyFC "unify failed \{show t'} =?= \{show u'} [no Fn]\n env is \{show ctx.env} \{show $ map fst ctx.types}"
|
|
-- REVIEW I'd like to quote this back, but we have l that aren't in the environment.
|
|
_ => error emptyFC "unify failed \{show t'} =?= \{show u'} \n env is \{show ctx.env} \{show $ map fst ctx.types}"
|
|
|
|
unifyCatch : FC -> Context -> Val -> Val -> M ()
|
|
unifyCatch fc ctx ty' ty = catchError (unify ctx ctx.lvl ty' ty) $ \(E x str) => do
|
|
let names = toList $ map fst ctx.types
|
|
a <- quote ctx.lvl ty'
|
|
b <- quote ctx.lvl ty
|
|
let msg = "\n failed to unify \{pprint names a}\n with \{pprint names b}\n " <+> str
|
|
throwError (E fc msg)
|
|
|
|
insert : (ctx : Context) -> Tm -> Val -> M (Tm, Val)
|
|
insert ctx tm ty = do
|
|
case !(forceMeta ty) of
|
|
VPi fc x Implicit a b => do
|
|
m <- freshMeta ctx fc
|
|
mv <- eval ctx.env CBN m
|
|
insert ctx (App emptyFC tm m) !(b $$ mv)
|
|
va => pure (tm, va)
|
|
|
|
lookupName : Context -> Raw -> M (Maybe (Tm, Val))
|
|
lookupName ctx (RVar fc nm) = go 0 ctx.types
|
|
where
|
|
go : Nat -> Vect n (String, Val) -> M (Maybe (Tm, Val))
|
|
go i [] = case lookup nm !(get) of
|
|
Just (MkEntry name ty def) => pure $ Just (Ref fc nm def, !(eval [] CBN ty))
|
|
Nothing => pure Nothing
|
|
go i ((x, ty) :: xs) = if x == nm then pure $ Just (Bnd fc i, ty)
|
|
else go (i + 1) xs
|
|
lookupName ctx _ = pure Nothing
|
|
|
|
|
|
primType : FC -> String -> M Val
|
|
primType fc nm = case lookup nm !(get) of
|
|
Just (MkEntry name ty PrimTCon) => pure $ VRef fc name PrimTCon [<]
|
|
_ => error fc "Primitive type \{show nm} not in scope"
|
|
|
|
|
|
export
|
|
infer : Context -> Raw -> M (Tm, Val)
|
|
|
|
export
|
|
check : Context -> Raw -> Val -> M Tm
|
|
|
|
-- FIXME we need to switch to FC
|
|
|
|
checkAlt : Val -> Context -> Val -> RCaseAlt -> M CaseAlt
|
|
checkAlt scty ctx ty (MkAlt ptm body) = do
|
|
-- we have a pattern term and a body
|
|
(con, args) <- getArgs ptm []
|
|
debug "ALT con \{con} args \{show args}"
|
|
let Just (MkEntry _ dcty (DCon arity _)) = lookup con !(get)
|
|
| Nothing => do
|
|
-- check body with con bound at scty against ty
|
|
let ctx' = extend ctx con scty
|
|
body' <- check ctx' body ty
|
|
pure $ CaseDefault body'
|
|
| _ => error emptyFC "expected datacon, got \{con}"
|
|
|
|
-- arity is wrong, but we actually need the type anyway
|
|
-- in fact arity is for later (eval?) and we need to do implicits first
|
|
debug "arity is \{show arity} dcty \{pprint [] dcty} con \{show con} pats \{show args}"
|
|
-- and then we run the names against the type
|
|
-- get all that into a context and run the body
|
|
|
|
-- So, how does this work?
|
|
-- The constructor has arguments
|
|
-- they may or may not be bound to names.
|
|
-- their types may depend on nameless arguments
|
|
-- the RHS is a function of some or all of this
|
|
|
|
-- I suspect I'll rewrite this a few times
|
|
vdcty <- eval [] CBN dcty
|
|
debug "go \{show vdcty} \{show ptm}"
|
|
alttm <- go vdcty args ctx
|
|
debug "GOT \{pprint (names ctx) alttm}"
|
|
|
|
-- package up the results into something.
|
|
-- REVIEW args, also probably want the tag not the name.
|
|
pure $ CaseCons con (map (snd . snd) args) alttm
|
|
|
|
where
|
|
argsFC : List (FC, Icit, String) -> FC
|
|
argsFC [] = emptyFC
|
|
argsFC ((fc,_) :: xs) = fc
|
|
|
|
go : Val -> List (FC, Icit, String) -> Context -> M Tm
|
|
-- FIXME icit
|
|
-- backwards?
|
|
go (VPi fc str Explicit a b) ((fc', Explicit, nm) :: rest) ctx = do
|
|
debug "*** \{nm} : \{show a} Explicit \{str}"
|
|
let var = VVar fc' (length ctx.env) [<]
|
|
let ctx' = extend ctx nm a
|
|
Lam fc' nm <$> go !(b $$ var) rest ctx'
|
|
go (VPi fc str Implicit a b) ((fc', Implicit, nm) :: rest) ctx = do
|
|
debug "*** \{nm} : \{show a} Implicit \{str}"
|
|
let var = VVar emptyFC (length ctx.env) [<]
|
|
let ctx' = extend ctx nm a
|
|
Lam emptyFC nm <$> go !(b $$ var) rest ctx'
|
|
|
|
go (VPi _ str Implicit a b) args ctx = do
|
|
debug "*** insert \{str}"
|
|
let fc' = argsFC args
|
|
let var = VVar fc' (length ctx.env) [<]
|
|
let ctx' = extend ctx "_" a
|
|
Lam fc' "_" <$> go !(b $$ var) args ctx'
|
|
-- same deal with _ for name
|
|
go (VPi fc str Explicit a b) ((fc', Implicit, nm) :: rest) ctx = do
|
|
error fc' "Implicit/Explicit mismatch \{show str} at \{show nm}"
|
|
go (VPi fc str icit x y) [] ctx = error emptyFC "Not enough arguments"
|
|
|
|
-- nameless variable
|
|
go ctype [] ctx = do
|
|
debug "*** end \{show ctype}"
|
|
-- FIXME FIXME - I think I should be unifying ctype against scty and learning stuff from it
|
|
-- like n = S k.
|
|
-- debug "Unifying constructor"
|
|
-- unifyCatch emptyFC ctx ctype scty
|
|
-- my first example has issues with Vect Z 0 =?=
|
|
|
|
check ctx body ty
|
|
-- This happens if we run out of runway (more args and no pi)
|
|
-- go ctype tm ctx = error (getF "unhandled in checkAlt.go type: \{show ctype} term: \{show tm}"
|
|
go ctype args ctx = error (argsFC args) "Extra args"
|
|
getArgs : Raw -> List (FC,Icit, String) -> M (String, List (FC,Icit, String))
|
|
getArgs (RVar _ nm) acc = pure (nm, acc)
|
|
-- TODO implicits
|
|
getArgs (RApp _ t (RVar fc nm) icit) acc = getArgs t ((fc,icit,nm) :: acc)
|
|
getArgs (RApp _ t (RHole fc) icit) acc = getArgs t ((fc,icit,"_") :: acc)
|
|
getArgs tm _ = error (getFC tm) "Patterns must be constructor and vars, got \{show tm}"
|
|
|
|
|
|
check ctx tm ty = case (tm, !(forceType ty)) of
|
|
(RCase fc rsc alts, ty) => do
|
|
(sc, scty) <- infer ctx rsc
|
|
let (VRef fc nm (TCon cnames) sp) = scty
|
|
| _ => error fc "expected TCon for scrutinee type, got: \{show scty}"
|
|
debug "constructor names \{show cnames}"
|
|
alts' <- for alts $ checkAlt scty ctx ty
|
|
pure $ Case emptyFC sc alts'
|
|
-- Document a hole, pretend it's implemented
|
|
(RHole fc, ty) => do
|
|
ty' <- quote ctx.lvl ty
|
|
let names = (toList $ map fst ctx.types)
|
|
env <- for ctx.types $ \(n,ty) => pure " \{n} : \{pprint names !(quote ctx.lvl ty)}"
|
|
let msg = unlines (toList $ reverse env) ++ " -----------\n" ++ " goal \{pprint names ty'}"
|
|
debug "INFO at \{show fc}: "
|
|
debug msg
|
|
-- let context = unlines foo
|
|
-- need to print 'warning' with position
|
|
-- fixme - just put a name on it like idris and stuff it into top.
|
|
-- error [DS "hole:\n\{msg}"]
|
|
pure $ Ref emptyFC "?" Axiom -- TODO - probably want hole opt on Def
|
|
(t@(RLam fc nm icit tm), ty@(VPi fc' nm' icit' a b)) => do
|
|
debug "icits \{nm} \{show icit} \{nm'} \{show icit'}"
|
|
if icit == icit' then do
|
|
let var = VVar fc (length ctx.env) [<]
|
|
let ctx' = extend ctx nm a
|
|
tm' <- check ctx' tm !(b $$ var)
|
|
pure $ Lam emptyFC nm tm'
|
|
else if icit' == Implicit then do
|
|
let var = VVar fc (length ctx.env) [<]
|
|
ty' <- b $$ var
|
|
-- use nm' here if we want them automatically in scope
|
|
sc <- check (extend ctx nm' a) t ty'
|
|
pure $ Lam fc nm' sc
|
|
else
|
|
error fc "Icity issue checking \{show t} at \{show ty}"
|
|
(t@(RLam fc nm icit tm), ty) =>
|
|
error fc "Expected pi type, got \{!(prvalCtx ty)}"
|
|
|
|
(tm, ty@(VPi fc nm' Implicit a b)) => do
|
|
let names = toList $ map fst ctx.types
|
|
debug "XXX edge add implicit lambda to \{show tm}"
|
|
let var = VVar fc (length ctx.env) [<]
|
|
ty' <- b $$ var
|
|
debug "XXX ty' is \{!(prvalCtx {ctx=(extend ctx nm' a)} ty')}"
|
|
sc <- check (extend ctx nm' a) tm ty'
|
|
pure $ Lam (getFC tm) nm' sc
|
|
|
|
(tm,ty) => do
|
|
-- We need to insert if tm is not an Implicit Lam
|
|
-- assuming all of the implicit ty have been handled above
|
|
let names = toList $ map fst ctx.types
|
|
(tm', ty') <- case !(infer ctx tm) of
|
|
-- Kovacs doesn't insert on tm = Implicit Lam, we don't have Plicity there
|
|
-- so I'll check the inferred type for an implicit pi
|
|
(tm'@(Lam{}), ty'@(VPi _ _ Implicit _ _)) => do debug "Lambda"; pure (tm', ty')
|
|
(tm', ty') => do
|
|
debug "RUN INSERT ON \{pprint names tm'} at \{show ty'}"
|
|
insert ctx tm' ty'
|
|
|
|
debug "INFER \{show tm} to (\{pprint names tm'} : \{show ty'}) expect \{show ty}"
|
|
unifyCatch (getFC tm) ctx ty' ty
|
|
pure tm'
|
|
|
|
infer ctx (RVar fc nm) = go 0 ctx.types
|
|
where
|
|
go : Nat -> Vect n (String, Val) -> M (Tm, Val)
|
|
go i [] = case lookup nm !(get) of
|
|
Just (MkEntry name ty def) => do
|
|
debug "lookup \{name} as \{show def}"
|
|
pure (Ref fc nm def, !(eval [] CBN ty))
|
|
Nothing => error fc "\{show nm} not in scope"
|
|
go i ((x, ty) :: xs) = if x == nm then pure $ (Bnd fc i, ty)
|
|
else go (i + 1) xs
|
|
-- need environment of name -> type..
|
|
infer ctx (RApp fc t u icit) = do
|
|
(icit, t, tty) <- case the Icit icit of
|
|
Explicit => do
|
|
(t, tty) <- infer ctx t
|
|
(t, tty) <- insert ctx t tty
|
|
pure (Explicit, t, tty)
|
|
Implicit => do
|
|
(t, tty) <- infer ctx t
|
|
pure (Implicit, t, tty)
|
|
|
|
(a,b) <- case !(forceMeta tty) of
|
|
(VPi fc str icit' a b) => if icit' == icit then pure (a,b)
|
|
else error fc "IcitMismatch \{show icit} \{show icit'}"
|
|
|
|
-- If it's not a VPi, try to unify it with a VPi
|
|
-- TODO test case to cover this.
|
|
tty => do
|
|
debug "unify PI for \{show tty}"
|
|
a <- eval ctx.env CBN !(freshMeta ctx fc)
|
|
b <- MkClosure ctx.env <$> freshMeta (extend ctx ":ins" a) fc
|
|
unify ctx 0 tty (VPi fc ":ins" icit a b)
|
|
pure (a,b)
|
|
|
|
u <- check ctx u a
|
|
pure (App fc t u, !(b $$ !(eval ctx.env CBN u)))
|
|
|
|
infer ctx (RU fc) = pure (U fc, VU fc) -- YOLO
|
|
infer ctx (RPi fc nm icit ty ty2) = do
|
|
ty' <- check ctx ty (VU fc)
|
|
vty' <- eval ctx.env CBN ty'
|
|
let nm := fromMaybe "_" nm
|
|
ty2' <- check (extend ctx nm vty') ty2 (VU fc)
|
|
pure (Pi fc nm icit ty' ty2', (VU fc))
|
|
infer ctx (RLet fc str tm tm1 tm2) = error fc "implement RLet"
|
|
infer ctx (RAnn fc tm rty) = do
|
|
ty <- check ctx rty (VU fc)
|
|
vty <- eval ctx.env CBN ty
|
|
tm <- check ctx tm vty
|
|
pure (tm, vty)
|
|
|
|
infer ctx (RLam fc nm icit tm) = do
|
|
a <- freshMeta ctx fc >>= eval ctx.env CBN
|
|
let ctx' = extend ctx nm a
|
|
(tm', b) <- infer ctx' tm
|
|
debug "make lam for \{show nm} scope \{pprint (names ctx) tm'} : \{show b}"
|
|
pure $ (Lam fc nm tm', VPi fc nm icit a $ MkClosure ctx.env !(quote (S ctx.lvl) b))
|
|
-- error {ctx} [DS "can't infer lambda"]
|
|
|
|
infer ctx (RImplicit fc) = do
|
|
ty <- freshMeta ctx fc
|
|
vty <- eval ctx.env CBN ty
|
|
tm <- freshMeta ctx fc
|
|
pure (tm, vty)
|
|
|
|
infer ctx (RLit fc (LString str)) = pure (Lit fc (LString str), !(primType fc "String"))
|
|
infer ctx (RLit fc (LInt i)) = pure (Lit fc (LInt i), !(primType fc "Int"))
|
|
|
|
infer ctx tm = error (getFC tm) "Implement infer \{show tm}"
|
|
|
|
-- The idea here is to insert a hole for a parse error
|
|
-- but the parser doesn't emit this yet.
|
|
-- infer ctx (RParseError str) = ?todo_insert_meta
|