cryptol-2.6.0: src/Cryptol/TypeCheck/Instantiate.hs
-- |
-- Module : Cryptol.TypeCheck.Instantiate
-- Copyright : (c) 2013-2016 Galois, Inc.
-- License : BSD3
-- Maintainer : cryptol@galois.com
-- Stability : provisional
-- Portability : portable
{-# Language OverloadedStrings #-}
module Cryptol.TypeCheck.Instantiate (instantiateWith) where
import Cryptol.ModuleSystem.Name (nameIdent)
import Cryptol.TypeCheck.AST
import Cryptol.TypeCheck.Monad
import Cryptol.TypeCheck.Subst (listSubst,apSubst)
import Cryptol.TypeCheck.Error
import Cryptol.Parser.Position (Located(..))
import Cryptol.Utils.Ident (Ident)
import Cryptol.Utils.Panic(panic)
import Control.Monad(zipWithM)
import Data.Function (on)
import Data.List(sortBy, groupBy, find)
import Data.Maybe(mapMaybe,isJust)
import Data.Either(partitionEithers)
import qualified Data.Set as Set
instantiateWith :: Name -> Expr -> Schema -> [Located (Maybe Ident,Type)]
-> InferM (Expr,Type)
instantiateWith nm e s ts
| null named = instantiateWithPos nm e s positional
| null positional = instantiateWithNames nm e s named
| otherwise = do recordError CannotMixPositionalAndNamedTypeParams
instantiateWithNames nm e s named
where
(named,positional) = partitionEithers (map classify ts)
classify t = case thing t of
(Just n,ty) -> Left t { thing = (n,ty) }
(Nothing,ty) -> Right ty
instantiateWithPos :: Name -> Expr -> Schema -> [Type] -> InferM (Expr,Type)
instantiateWithPos nm e (Forall as ps t) ts =
do su <- makeSu (1::Int) [] as ts
doInst su e ps t
where
isNamed q = isJust (tpName q)
makeSu n su (q : qs) (ty : tys)
| not (isNamed q) = do r <- unnamed n q
makeSu (n+1) (r : su) qs (ty : tys)
| k1 == k2 = makeSu (n+1) ((q, ty) : su) qs tys
| otherwise = do recordError (KindMismatch k1 k2)
r <- unnamed n q
makeSu (n+1) (r : su) qs tys
where k1 = kindOf q
k2 = kindOf ty
makeSu _ su [] [] = return (reverse su)
makeSu n su (q : qs) [] = do r <- unnamed n q
makeSu (n+1) (r : su) qs []
makeSu _ su [] _ = do recordError TooManyPositionalTypeParams
return (reverse su)
unnamed n q = do ty <- newType src (kindOf q)
return (q, ty)
where
src = case drop (n-1) {- count from 1 -} as of
p:_ ->
case tpFlav p of
TPOther (Just a) -> TypeParamInstNamed nm (nameIdent a)
_ -> TypeParamInstPos nm n
_ -> panic "instantiateWithPos"
[ "Invalid parameter index", show n, show as ]
{- | Instantiate an expression of the given polymorphic type.
The arguments that are provided will be instantiated as requested,
the rest will be instantiated with fresh type variables.
EProofApp (ETApp e t)
where
- There will be one `ETApp t` for each insantiated type parameter;
- there will be one `EProofApp` for each constraint on the schema;
-}
instantiateWithNames :: Name -> Expr -> Schema -> [Located (Ident,Type)]
-> InferM (Expr,Type)
instantiateWithNames nm e (Forall as ps t) xs =
do sequence_ repeatedParams
mapM_ (recordError . UndefinedTypeParameter . fmap fst) undefParams
su' <- zipWithM paramInst [ 1.. ] as
doInst su' e ps t
where
-- Choose the type for type parameter `x`
paramInst n x =
do let k = tpKind x
-- We just use nameIdent for comparison here, as all parameter names
-- should have a NameInfo of Parameter.
lkp name = find (\th -> fst (thing th) == nameIdent name) xs
src = case tpName x of
Just na -> TypeParamInstNamed nm (nameIdent na)
Nothing -> TypeParamInstPos nm n
ty <- case lkp =<< tpName x of
Just lty
| k1 == k -> return ty
| otherwise -> inRange (srcRange lty) $
do recordError (KindMismatch k k1)
newType src k
where ty = snd (thing lty)
k1 = kindOf ty
Nothing -> newType src k
return (x, ty)
-- Errors from multiple values for the same parameter.
repeatedParams = mapMaybe isRepeated
$ groupBy ((==) `on` pName)
$ sortBy (compare `on` pName) xs
isRepeated ys@(a : _ : _) =
Just $ recordError (RepeatedTypeParameter (fst (thing a)) (map srcRange ys))
isRepeated _ = Nothing
paramIdents = [ nameIdent n | Just n <- map tpName as ]
-- Errors from parameters that are defined, but do not exist in the schema.
undefParams = [ x | x <- xs, pName x `notElem` paramIdents ]
pName = fst . thing
-- If the instantiation contains an assignment (v := t), and the type
-- contains a free unification variable ?x that could possibly depend
-- on v, then we must require that t = v (i.e. su must be an identity
-- substitution). Otherwise, this causes a problem: If ?x is
-- eventually instantiated to a type containing v, then the type
-- substitution will have computed the wrong result.
doInst :: [(TParam, Type)] -> Expr -> [Prop] -> Type -> InferM (Expr,Type)
doInst su' e ps t =
do let su = listSubst [ (tpVar tp, ty) | (tp, ty) <- su' ]
newGoals (CtInst e) (map (apSubst su) ps)
let t1 = apSubst su t
-- Possibly more goals due to unification
ps' <- concat <$> mapM checkInst su'
newGoals (CtInst e) ps'
return ( addProofParams (addTyParams (map snd su') e), t1 )
where
-- Add type parameters
addTyParams ts e1 = foldl ETApp e1 ts
-- Add proof parameters (the proofs are omitted but we mark where they'd go)
addProofParams e1 = foldl (\e2 _ -> EProofApp e2) e1 ps
-- free unification variables used in the schema
frees = Set.unions (map fvs (t : ps))
-- the bound variables from the scopes of any unification variables in the schema
bounds = Set.unions (map scope (Set.toList frees))
where
scope (TVFree _ _ vs _) = vs
scope (TVBound _) = Set.empty
-- if the tvar is in 'bounds', then make sure it is an identity substitution
checkInst :: (TParam, Type) -> InferM [Prop]
checkInst (tp, ty)
| Set.notMember tp bounds = return []
| otherwise = unify (TVar (tpVar tp)) ty