cryptol-3.0.0: src/Cryptol/TypeCheck/InferTypes.hs
-- |
-- Module : Cryptol.TypeCheck.InferTypes
-- Copyright : (c) 2013-2016 Galois, Inc.
-- License : BSD3
-- Maintainer : cryptol@galois.com
-- Stability : provisional
-- Portability : portable
--
-- This module contains types used during type inference.
{-# LANGUAGE Safe #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}
module Cryptol.TypeCheck.InferTypes where
import Control.Monad(guard)
import Cryptol.Parser.Position
import Cryptol.ModuleSystem.Name (asPrim,nameLoc)
import Cryptol.TypeCheck.AST
import Cryptol.TypeCheck.PP
import Cryptol.TypeCheck.Subst
import Cryptol.TypeCheck.TypePat
import Cryptol.TypeCheck.SimpType(tMax)
import Cryptol.Utils.Ident (PrimIdent(..), preludeName)
import Cryptol.Utils.Panic(panic)
import Cryptol.Utils.Misc(anyJust)
import Data.Set ( Set )
import qualified Data.Set as Set
import Data.Map ( Map )
import qualified Data.Map as Map
import GHC.Generics (Generic)
import Control.DeepSeq
data SolverConfig = SolverConfig
{ solverPath :: FilePath -- ^ The SMT solver to invoke
, solverArgs :: [String] -- ^ Additional arguments to pass to the solver
, solverVerbose :: Int -- ^ How verbose to be when type-checking
, solverPreludePath :: [FilePath]
-- ^ Look for the solver prelude in these locations.
} deriving (Show, Generic, NFData)
-- | A default configuration for using Z3, where
-- the solver prelude is expected to be found
-- in the given search path.
defaultSolverConfig :: [FilePath] -> SolverConfig
defaultSolverConfig searchPath =
SolverConfig
{ solverPath = "z3"
, solverArgs = [ "-smt2", "-in" ]
, solverVerbose = 0
, solverPreludePath = searchPath
}
-- | The types of variables in the environment.
data VarType = ExtVar Schema
-- ^ Known type
| CurSCC {- LAZY -} Expr Type
{- ^ Part of current SCC. The expression will replace the
variable, after we are done with the SCC. In this way a
variable that gets generalized is replaced with an appropriate
instantiation of itself. -}
data Goals = Goals
{ goalSet :: Set Goal
-- ^ A bunch of goals, not including the ones in 'literalGoals'.
, saturatedPropSet :: Set Prop
-- ^ The set of nonliteral goals, saturated by all superclass implications
, literalGoals :: Map TVar LitGoal
-- ^ An entry @(a,t)@ corresponds to @Literal t a@.
, literalLessThanGoals :: Map TVar LitGoal
-- ^ An entry @(a,t)@ corresponds to @LiteralLessThan t a@.
} deriving (Show)
-- | This abuses the type 'Goal' a bit. The 'goal' field contains
-- only the numeric part of the Literal constraint. For example,
-- @(a, Goal { goal = t })@ representats the goal for @Literal t a@
type LitGoal = Goal
litGoalToGoal :: (TVar,LitGoal) -> Goal
litGoalToGoal (a,g) = g { goal = pLiteral (goal g) (TVar a) }
goalToLitGoal :: Goal -> Maybe (TVar,LitGoal)
goalToLitGoal g =
do (tn,a) <- matchMaybe $ do (tn,b) <- aLiteral (goal g)
a <- aTVar b
return (tn,a)
return (a, g { goal = tn })
litLessThanGoalToGoal :: (TVar,LitGoal) -> Goal
litLessThanGoalToGoal (a,g) = g { goal = pLiteralLessThan (goal g) (TVar a) }
goalToLitLessThanGoal :: Goal -> Maybe (TVar,LitGoal)
goalToLitLessThanGoal g =
do (tn,a) <- matchMaybe $ do (tn,b) <- aLiteralLessThan (goal g)
a <- aTVar b
return (tn,a)
return (a, g { goal = tn })
emptyGoals :: Goals
emptyGoals =
Goals
{ goalSet = Set.empty
, saturatedPropSet = Set.empty
, literalGoals = Map.empty
, literalLessThanGoals = Map.empty
}
nullGoals :: Goals -> Bool
nullGoals gs =
Set.null (goalSet gs) &&
Map.null (literalGoals gs) &&
Map.null (literalLessThanGoals gs)
fromGoals :: Goals -> [Goal]
fromGoals gs = map litGoalToGoal (Map.toList (literalGoals gs)) ++
map litLessThanGoalToGoal (Map.toList (literalLessThanGoals gs)) ++
Set.toList (goalSet gs)
goalsFromList :: [Goal] -> Goals
goalsFromList = foldr insertGoal emptyGoals
insertGoal :: Goal -> Goals -> Goals
insertGoal g gls
| Just (a,newG) <- goalToLitGoal g =
-- XXX: here we are arbitrarily using the info of the first goal,
-- which could lead to a confusing location for a constraint.
let jn g1 g2 = g1 { goal = tMax (goal g1) (goal g2) } in
gls { literalGoals = Map.insertWith jn a newG (literalGoals gls)
, saturatedPropSet = Set.insert (pFin (TVar a)) (saturatedPropSet gls)
}
| Just (a,newG) <- goalToLitLessThanGoal g =
let jn g1 g2 = g1 { goal = tMax (goal g1) (goal g2) } in
gls { literalLessThanGoals = Map.insertWith jn a newG (literalLessThanGoals gls)
}
-- If the goal is already implied by some other goal, skip it
| Set.member (goal g) (saturatedPropSet gls) = gls
-- Otherwise, it is not already implied, add it and saturate
| otherwise =
gls { goalSet = gs', saturatedPropSet = sps' }
where
ips = superclassSet (goal g)
igs = Set.map (\p -> g{ goal = p}) ips
-- remove all the goals that are implied by ips
gs' = Set.insert g (Set.difference (goalSet gls) igs)
-- add the goal and all its implied toals to the saturated set
sps' = Set.insert (goal g) (Set.union (saturatedPropSet gls) ips)
-- | Something that we need to find evidence for.
data Goal = Goal
{ goalSource :: ConstraintSource -- ^ What it is about
, goalRange :: Range -- ^ Part of source code that caused goal
, goal :: Prop -- ^ What needs to be proved
} deriving (Show, Generic, NFData)
instance Eq Goal where
x == y = goal x == goal y
instance Ord Goal where
compare x y = compare (goal x) (goal y)
data HasGoal = HasGoal
{ hasName :: !Int -- ^ This is the "name" of the constraint,
-- used to find the solution for ellaboration.
, hasGoal :: Goal
} deriving Show
-- | A solution for a 'HasGoal'
data HasGoalSln = HasGoalSln
{ hasDoSelect :: Expr -> Expr
-- ^ Select a specific field from the input expsression.
, hasDoSet :: Expr -> Expr -> Expr
-- ^ Set a field of the first expression to the second expression
}
-- | Delayed implication constraints, arising from user-specified type sigs.
data DelayedCt = DelayedCt
{ dctSource :: Maybe Name -- ^ Signature that gave rise to this constraint
-- Nothing means module top-level
, dctForall :: [TParam]
, dctAsmps :: [Prop]
, dctGoals :: [Goal]
} deriving (Show, Generic, NFData)
-- | Information about how a constraint came to be, used in error reporting.
data ConstraintSource
= CtComprehension -- ^ Computing shape of list comprehension
| CtSplitPat -- ^ Use of a split pattern
| CtTypeSig -- ^ A type signature in a pattern or expression
| CtInst Expr -- ^ Instantiation of this expression
| CtSelector
| CtExactType
| CtEnumeration
| CtDefaulting -- ^ Just defaulting on the command line
| CtPartialTypeFun Name -- ^ Use of a partial type function.
| CtImprovement
| CtPattern TypeSource -- ^ Constraints arising from type-checking patterns
| CtModuleInstance Range -- ^ Instantiating a parametrized module
| CtPropGuardsExhaustive Name -- ^ Checking that a use of prop guards is exhastive
| CtFFI Name -- ^ Constraints on a foreign declaration required
-- by the FFI (e.g. sequences must be finite)
deriving (Show, Generic, NFData)
selSrc :: Selector -> TypeSource
selSrc l = case l of
RecordSel la _ -> TypeOfRecordField la
TupleSel n _ -> TypeOfTupleField n
ListSel _ _ -> TypeOfSeqElement
instance TVars ConstraintSource where
apSubst su src =
case src of
CtComprehension -> src
CtSplitPat -> src
CtTypeSig -> src
CtInst e -> CtInst (apSubst su e)
CtSelector -> src
CtExactType -> src
CtEnumeration -> src
CtDefaulting -> src
CtPartialTypeFun _ -> src
CtImprovement -> src
CtPattern _ -> src
CtModuleInstance _ -> src
CtPropGuardsExhaustive _ -> src
CtFFI _ -> src
instance FVS Goal where
fvs g = fvs (goal g)
instance FVS DelayedCt where
fvs d = fvs (dctAsmps d, dctGoals d) `Set.difference`
Set.fromList (map tpVar (dctForall d))
instance TVars Goals where
-- XXX: could be more efficient
apSubst su gs = case anyJust apG (fromGoals gs) of
Nothing -> gs
Just gs1 -> goalsFromList (concatMap norm gs1)
where
norm g = [ g { goal = p } | p <- pSplitAnd (goal g) ]
apG g = mk g <$> apSubstMaybe su (goal g)
mk g p = g { goal = p }
instance TVars Goal where
apSubst su g = Goal { goalSource = apSubst su (goalSource g)
, goalRange = goalRange g
, goal = apSubst su (goal g)
}
instance TVars HasGoal where
apSubst su h = h { hasGoal = apSubst su (hasGoal h) }
instance TVars DelayedCt where
apSubst su g
| Set.null captured =
DelayedCt { dctSource = dctSource g
, dctForall = dctForall g
, dctAsmps = apSubst su (dctAsmps g)
, dctGoals = apSubst su (dctGoals g)
}
| otherwise = panic "Cryptol.TypeCheck.Subst.apSubst (DelayedCt)"
[ "Captured quantified variables:"
, "Substitution: " ++ show su
, "Variables: " ++ show captured
, "Constraint: " ++ show g
]
where
captured = Set.fromList (map tpVar (dctForall g))
`Set.intersection`
subVars
subVars = Set.unions
$ map (fvs . applySubstToVar su)
$ Set.toList used
used = fvs (dctAsmps g, map goal (dctGoals g)) `Set.difference`
Set.fromList (map tpVar (dctForall g))
-- | For use in error messages
cppKind :: Kind -> Doc
cppKind ki =
case ki of
KNum -> text "a numeric type"
KType -> text "a value type"
KProp -> text "a constraint"
_ -> pp ki
addTVarsDescsAfter :: FVS t => NameMap -> t -> Doc -> Doc
addTVarsDescsAfter nm t d
| Set.null vs = d
-- TODO? use `hang` here instead to indent things after "where"
| otherwise = d $$ text "where" $$ vcat (map desc (Set.toList vs))
where
vs = fvs t
desc v = ppWithNames nm v <+> text "is" <+> pp (tvInfo v)
addTVarsDescsBefore :: FVS t => NameMap -> t -> Doc -> Doc
addTVarsDescsBefore nm t d = vcat (frontMsg ++ [d] ++ backMsg)
where
(vs1,vs2) = Set.partition isFreeTV (fvs t)
frontMsg | null vs1 = []
| otherwise = [hang "Failed to infer the following types:"
2 (vcat (map desc1 (Set.toList vs1)))]
desc1 v = "•" <+> ppWithNames nm v <.> comma <+> pp (tvInfo v)
backMsg | null vs2 = []
| otherwise = [hang "where"
2 (vcat (map desc2 (Set.toList vs2)))]
desc2 v = ppWithNames nm v <+> text "is" <+> pp (tvInfo v)
instance PP ConstraintSource where
ppPrec _ src =
case src of
CtComprehension -> "list comprehension"
CtSplitPat -> "split (#) pattern"
CtTypeSig -> "type signature"
CtInst e -> "use of" <+> ppUse e
CtSelector -> "use of selector"
CtExactType -> "matching types"
CtEnumeration -> "list enumeration"
CtDefaulting -> "defaulting"
CtPartialTypeFun f -> "use of partial type function" <+> pp f
CtImprovement -> "examination of collected goals"
CtPattern ad -> "checking a pattern:" <+> pp ad
CtModuleInstance r -> "module instantiation at" <+> pp r
CtPropGuardsExhaustive n -> "exhaustion check for prop guards used in defining" <+> pp n
CtFFI f -> "declaration of foreign function" <+> pp f
ppUse :: Expr -> Doc
ppUse expr =
case expr of
EVar (isPrelPrim -> Just prim)
| prim == "number" -> "literal or demoted expression"
| prim == "fraction" -> "fractional literal"
| prim == "infFrom" -> "infinite enumeration"
| prim == "infFromThen" -> "infinite enumeration (with step)"
| prim == "fromTo" -> "finite enumeration"
| prim == "fromThenTo" -> "finite enumeration"
_ -> "expression" <+> pp expr
where
isPrelPrim x = do PrimIdent p i <- asPrim x
guard (p == preludeName)
pure i
instance PP (WithNames Goal) where
ppPrec _ (WithNames g names) =
hang (ppWithNames names (goal g))
2 (text "arising from" $$
pp (goalSource g) $$
text "at" <+> pp (goalRange g))
instance PP (WithNames DelayedCt) where
ppPrec _ (WithNames d names) =
sig $$
hang "we need to show that"
2 (vcat ( vars ++ asmps ++
[ hang "the following constraints hold:"
2 (vcat
$ bullets
$ map (ppWithNames ns1)
$ dctGoals d )]))
where
bullets xs = [ "•" <+> x | x <- xs ]
sig = case name of
Just n -> "in the definition of" <+> quotes (pp n) <.>
comma <+> "at" <+> pp (nameLoc n) <.> comma
Nothing -> "when checking the module's parameters,"
name = dctSource d
vars = case dctForall d of
[] -> []
xs -> ["for any type" <+> commaSep (map (ppWithNames ns1) xs)]
asmps = case dctAsmps d of
[] -> []
xs -> [hang "assuming"
2 (vcat (bullets (map (ppWithNames ns1) xs)))]
ns1 = addTNames (dctForall d) names