Skip to content

Commit e557f2f

Browse files
authored
Merge pull request #1419 from ucsd-progsys/class-laws
Class laws definition and checking
2 parents ea8217b + c3a2f92 commit e557f2f

22 files changed

+791
-17
lines changed

README.md

+5
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,11 @@ and compelling example.
10021002
- Value parameters are specified in **upper**case: `X`, `Y`, `Z` etc.
10031003

10041004

1005+
#### Class Laws
1006+
1007+
Class laws can be defined and checked using the `class laws` and `instance laws` keywords.
1008+
For an example, see [class-laws/pos/SemiGroup.hs](https://github.com/ucsd-progsys/liquidhaskell/blob/06d22aa070933d9ea833e30d84ed91de2a28eaee/tests/class-laws/pos/SemiGroup.hs)
1009+
10051010
#### Type Aliases
10061011

10071012

liquidhaskell.cabal

+3
Original file line numberDiff line numberDiff line change
@@ -212,12 +212,14 @@ Library
212212
Language.Haskell.Liquid.Types.Literals,
213213
Language.Haskell.Liquid.Types.Strata,
214214
Language.Haskell.Liquid.Types.Fresh,
215+
Language.Haskell.Liquid.Types.Equality,
215216
Language.Haskell.Liquid.Constraint.Fresh,
216217
Language.Haskell.Liquid.Types.Visitors,
217218
Language.Haskell.Liquid.WiredIn,
218219
Language.Haskell.Liquid.Types.Names,
219220
Language.Haskell.Liquid.Liquid,
220221
Language.Haskell.Liquid.Desugar.HscMain,
222+
Language.Haskell.Liquid.LawInstances,
221223

222224
-- NOTE: these need to be exposed so GHC generates .dyn_o files for them..
223225
Language.Haskell.Liquid.Desugar.Check,
@@ -251,6 +253,7 @@ Library
251253
Language.Haskell.Liquid.Bare.Axiom,
252254
Language.Haskell.Liquid.Bare.ToBare,
253255
Language.Haskell.Liquid.Bare.Class,
256+
Language.Haskell.Liquid.Bare.Laws,
254257
Language.Haskell.Liquid.Bare.Check,
255258
Language.Haskell.Liquid.Interactive.Types,
256259
Language.Haskell.Liquid.Interactive.Handler,

src/Language/Haskell/Liquid/Bare.hs

+15
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ import qualified Language.Haskell.Liquid.Bare.Axiom as Bare
5151
import qualified Language.Haskell.Liquid.Bare.ToBare as Bare
5252
import qualified Language.Haskell.Liquid.Bare.Class as Bare
5353
import qualified Language.Haskell.Liquid.Bare.Check as Bare
54+
import qualified Language.Haskell.Liquid.Bare.Laws as Bare
5455
import qualified Language.Haskell.Liquid.Transforms.CoreToLogic as CoreToLogic
56+
import Control.Arrow (second)
5557

5658
--------------------------------------------------------------------------------
5759
-- | De/Serializing Spec files -------------------------------------------------
@@ -138,6 +140,7 @@ makeGhcSpec0 cfg src lmap mspecs = SP
138140
, gsImps = makeImports mspecs
139141
, gsSig = addReflSigs refl sig
140142
, gsRefl = refl
143+
, gsLaws = laws
141144
, gsData = sData
142145
, gsQual = qual
143146
, gsName = makeSpecName env tycEnv measEnv name
@@ -155,6 +158,7 @@ makeGhcSpec0 cfg src lmap mspecs = SP
155158
qual = makeSpecQual cfg env tycEnv measEnv rtEnv specs
156159
sData = makeSpecData src env sigEnv measEnv sig specs
157160
refl = makeSpecRefl src measEnv specs env name sig tycEnv
161+
laws = makeSpecLaws env sigEnv (gsTySigs sig ++ gsAsmSigs sig) measEnv specs
158162
sig = makeSpecSig name specs env sigEnv tycEnv measEnv
159163
measEnv = makeMeasEnv env tycEnv sigEnv specs
160164
-- build up environments
@@ -406,6 +410,17 @@ makeSize env name spec =
406410
| otherwise
407411
= Nothing
408412

413+
414+
415+
------------------------------------------------------------------------------------------
416+
makeSpecLaws :: Bare.Env -> Bare.SigEnv -> [(Ghc.Var,LocSpecType)] -> Bare.MeasEnv -> Bare.ModSpecs
417+
-> GhcSpecLaws
418+
------------------------------------------------------------------------------------------
419+
makeSpecLaws env sigEnv sigs menv specs = SpLaws
420+
{ gsLawDefs = second (map (\(_,x,y) -> (x,y))) <$> Bare.meCLaws menv
421+
, gsLawInst = Bare.makeInstanceLaws env sigEnv sigs specs
422+
}
423+
409424
------------------------------------------------------------------------------------------
410425
makeSpecRefl :: GhcSrc -> Bare.MeasEnv -> Bare.ModSpecs -> Bare.Env -> ModName -> GhcSpecSig -> Bare.TycEnv
411426
-> GhcSpecRefl

src/Language/Haskell/Liquid/Bare/Check.hs

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import Language.Haskell.Liquid.Types.PrettyPrint (pprintSymbol)
3131
import Language.Haskell.Liquid.Types.RefType (classBinds, ofType, rTypeSort, rTypeSortedReft, subsTyVars_meet, toType)
3232
import Language.Haskell.Liquid.Types
3333
import Language.Haskell.Liquid.WiredIn
34+
import Language.Haskell.Liquid.LawInstances (checkLawInstances)
3435

3536
import qualified Language.Haskell.Liquid.Measure as Ms
3637
import qualified Language.Haskell.Liquid.Bare.Types as Bare
@@ -128,6 +129,7 @@ checkGhcSpec specs env cbs sp = Misc.applyNonNull (Right sp) Left errors
128129
-- but make sure that all the specs are checked.
129130
-- ++ checkRefinedClasses rClasses rInsts
130131
++ checkSizeFun emb env (gsTconsP (gsName sp))
132+
++ checkLawInstances (gsLaws sp)
131133
_rClasses = concatMap (Ms.classes . snd) specs
132134
_rInsts = concatMap (Ms.rinstance . snd) specs
133135
tAliases = concat [Ms.aliases sp | (_, sp) <- specs]
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
module Language.Haskell.Liquid.Bare.Laws ( makeInstanceLaws ) where
2+
3+
import qualified Data.Maybe as Mb
4+
import qualified Data.List as L
5+
import qualified Data.HashMap.Strict as M
6+
import Control.Monad (join)
7+
8+
import qualified Language.Haskell.Liquid.Measure as Ms
9+
import qualified Language.Fixpoint.Types as F
10+
import qualified Language.Haskell.Liquid.GHC.Misc as GM
11+
import Language.Haskell.Liquid.Bare.Types as Bare
12+
import Language.Haskell.Liquid.Bare.Resolve as Bare
13+
import Language.Haskell.Liquid.Bare.Expand as Bare
14+
import Language.Haskell.Liquid.Types
15+
import Language.Haskell.Liquid.GHC.API
16+
17+
18+
19+
makeInstanceLaws :: Bare.Env -> Bare.SigEnv -> [(Var,LocSpecType)]
20+
-> Bare.ModSpecs -> [LawInstance]
21+
makeInstanceLaws env sigEnv sigs specs
22+
= [makeInstanceLaw env sigEnv sigs name rilaw
23+
| (name, spec) <- M.toList specs
24+
, rilaw <- Ms.ilaws spec ]
25+
26+
27+
makeInstanceLaw :: Bare.Env -> Bare.SigEnv -> [(Var,LocSpecType)] -> ModName
28+
-> RILaws LocBareType -> LawInstance
29+
makeInstanceLaw env sigEnv sigs name rilaw = LawInstance
30+
{ lilName = Mb.fromMaybe errmsg tc
31+
, liSupers = mkTy <$> rilSupers rilaw
32+
, lilTyArgs = mkTy <$> rilTyArgs rilaw
33+
, lilEqus = [(mkVar l, mkTypedVar r) | (l,r)<- rilEqus rilaw ]
34+
, lilPos = GM.sourcePosSrcSpan $ loc $ rilPos rilaw
35+
}
36+
where
37+
tc :: Maybe Class
38+
tc = classTc (rilName rilaw)
39+
errmsg = error ("Not a type class: " ++ F.showpp tc)
40+
41+
classTc = join . fmap tyConClass_maybe . Bare.maybeResolveSym env name "makeClass" . btc_tc
42+
43+
mkTy :: LocBareType -> LocSpecType
44+
mkTy = Bare.cookSpecType env sigEnv name Bare.GenTV
45+
mkVar :: LocSymbol -> VarOrLocSymbol
46+
mkVar x = case Bare.maybeResolveSym env name "makeInstanceLaw" x of
47+
Just v -> Left v
48+
_ -> Right x
49+
50+
mkTypedVar :: LocSymbol -> (VarOrLocSymbol, Maybe LocSpecType)
51+
mkTypedVar l = case mkVar l of
52+
Left x -> (Left x, Just $ Mb.fromMaybe (dummyLoc $ ofType $ varType x) (L.lookup x sigs))
53+
Right x -> (Right x, Nothing)
54+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
{-# LANGUAGE FlexibleContexts #-}
2+
module Language.Haskell.Liquid.LawInstances ( checkLawInstances ) where
3+
4+
import qualified Data.List as L
5+
import qualified Data.Maybe as Mb
6+
import Text.PrettyPrint.HughesPJ
7+
8+
import Language.Haskell.Liquid.Types
9+
import Language.Haskell.Liquid.Types.Equality
10+
import Language.Haskell.Liquid.GHC.API
11+
import qualified Language.Fixpoint.Types as F
12+
13+
checkLawInstances :: GhcSpecLaws -> [Error]
14+
checkLawInstances speclaws = concatMap go (gsLawInst speclaws)
15+
where go l = checkOneInstance (lilName l) (Mb.fromMaybe [] $ L.lookup (lilName l) (gsLawDefs speclaws)) l
16+
17+
checkOneInstance :: Class -> [(Var, LocSpecType)] -> LawInstance -> [Error]
18+
checkOneInstance c laws li
19+
= checkExtra c li ((fst <$> laws) ++ classMethods c) (lilEqus li) ++ concatMap (\l -> checkOneLaw c l li) laws
20+
21+
checkExtra :: Class -> LawInstance -> [Var] -> [(VarOrLocSymbol, (VarOrLocSymbol, Maybe LocSpecType))] -> [Error]
22+
checkExtra c li laws insts = mkError <$> ({- (msgExtra <$> extra) ++ -} (msgUnfoundLaw <$> unfoundLaws) ++ (msgUnfoundInstance <$> unfoundInstances))
23+
where
24+
25+
unfoundInstances = [ x | (_, (Right x,_)) <- insts]
26+
unfoundLaws = [ x | (Right x, _) <- insts]
27+
extra = [] -- this breaks on extra super requirements [ (x,i) | (Left x, (Left i, _)) <- insts, not (x `L.elem` laws)]
28+
mkError = ErrILaw (lilPos li) (pprint c) (pprint $ lilTyArgs li)
29+
msgExtra (x,_) = pprint x <+> text "is not a defined law."
30+
msgUnfoundLaw i = pprint i <+> text "is not a defined law."
31+
msgUnfoundInstance i = pprint i <+> text "is not a defined instance."
32+
33+
checkOneLaw :: Class -> (Var, LocSpecType) -> LawInstance -> [Error]
34+
checkOneLaw c (x, t) li
35+
| Just (Left _, Just ti) <- lix
36+
= unify mkError c li t ti
37+
| Just (Right l, _) <- lix
38+
= [mkError (text "is not found.")]
39+
| otherwise
40+
= [mkError (text "is not defined.")]
41+
where
42+
lix = L.lookup (Left x) (lilEqus li)
43+
mkError txt = ErrILaw (lilPos li) (pprint c) (pprintXs $ lilTyArgs li)
44+
(text "The instance for the law" <+> pprint x <+> txt)
45+
pprintXs [l] = pprint l
46+
pprintXs xs = pprint xs
47+
48+
unify :: (Doc -> Error) -> Class -> LawInstance -> LocSpecType -> LocSpecType -> [Error]
49+
unify mkError c li t1 t2
50+
= if t11 =*= t22 then [] else err
51+
where
52+
err = [mkError (text "is invalid:\nType" <+> pprint t1 <+> text "\nis different than\n" <+> pprint t2
53+
-- <+> text "\nesubt1 = " <+> pprint esubst1
54+
-- <+> text "\nesubt = " <+> pprint esubst
55+
-- <+> text "\ncompared\n" <+> pprint t11 <+> text "\nWITH\n" <+> pprint t22
56+
)]
57+
58+
t22 = fromRTypeRep (trep2 {ty_vars = [], ty_binds = fst <$> args2, ty_args = snd <$> args2, ty_refts = drop (length tc2) (ty_refts trep2)})
59+
t11 = fromRTypeRep (trep1 { ty_vars = []
60+
, ty_binds = fst <$> args2
61+
, ty_args = (tx . snd) <$> args1
62+
, ty_refts = F.subst esubst <$> drop (length tc1) (ty_refts trep1)
63+
, ty_res = tx $ ty_res trep1})
64+
tx = subtsSpec tsubst . F.subst esubst
65+
subtsSpec = subts :: ([(TyVar, Type)] -> SpecType -> SpecType)
66+
67+
trep1 = toRTypeRep $ val t1
68+
trep2 = toRTypeRep $ val t2
69+
(tc1, args1) = splitTypeConstraints $ zip (ty_binds trep1) (ty_args trep1)
70+
(tc2, args2) = splitTypeConstraints $ zip (ty_binds trep2) (ty_args trep2)
71+
esubst = F.mkSubst (esubst1
72+
++ [(F.symbol x, F.EVar (F.symbol y)) | (Left x, (Left y, _)) <- lilEqus li]
73+
)
74+
esubst1 = zip (fst <$> args1) ((F.EVar . fst) <$> args2)
75+
76+
tsubst = reverse $ zip ((\(RTV v) -> v) <$> (findTyVars tc1 ++ (ty_var_value <$> concat argVars)))
77+
(toType <$> (argBds ++ (((`RVar` mempty) . ty_var_value) <$>ty_vars trep2)))
78+
79+
(argVars, argBds) = unzip (splitForall [] . val <$> lilTyArgs li)
80+
81+
splitForall vs (RAllT v t) = splitForall (v:vs) t
82+
splitForall vs t = (vs, t)
83+
84+
findTyVars ((b@(x,RApp cc as _ _):ts)) | rtc_tc cc == classTyCon c
85+
= [v | RVar v _ <- as ]
86+
findTyVars (_:ts) = findTyVars ts
87+
findTyVars [] = []
88+
89+
90+
splitTypeConstraints :: [(F.Symbol, SpecType)] -> ([(F.Symbol, SpecType)], [(F.Symbol, SpecType)])
91+
splitTypeConstraints = go []
92+
where
93+
go cs (b@(x,RApp c _ _ _):ts)
94+
| isClass c
95+
= go (b:cs) ts
96+
go cs r = (reverse cs, map (\(x, t) -> (x, shiftVV t x)) r)

src/Language/Haskell/Liquid/Measure.hs

+2
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ instance Semigroup (Spec ty bndr) where
107107
, claws = claws s1 ++ claws s2
108108
, termexprs = termexprs s1 ++ termexprs s2
109109
, rinstance = rinstance s1 ++ rinstance s2
110+
, ilaws = ilaws s1 ++ ilaws s2
110111
, dvariance = dvariance s1 ++ dvariance s2
111112
, axeqs = axeqs s1 ++ axeqs s2
112113
, embeds = mappend (embeds s1) (embeds s2)
@@ -162,6 +163,7 @@ instance Monoid (Spec ty bndr) where
162163
, claws = []
163164
, termexprs = []
164165
, rinstance = []
166+
, ilaws = []
165167
, dvariance = []
166168
, axeqs = []
167169
, bounds = M.empty

src/Language/Haskell/Liquid/Parse.hs

+29-1
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,7 @@ data Pspec ty ctor
854854
| NTDecl DataDecl -- ^ refined 'newtype' declaration
855855
| Class (RClass ty) -- ^ refined 'class' definition
856856
| CLaws (RClass ty) -- ^ 'class laws' definition
857+
| ILaws (RILaws ty)
857858
| RInst (RInstance ty) -- ^ refined 'instance' definition
858859
| Incl FilePath -- ^ 'include' a path -- TODO: deprecate
859860
| Invt ty -- ^ 'invariant' specification
@@ -1048,6 +1049,7 @@ mkSpec name xs = (name,) $ qualifySpec (symbol name) Measure.Spec
10481049
, Measure.claws = [c | CLaws c <- xs]
10491050
, Measure.dvariance = [v | Varia v <- xs]
10501051
, Measure.rinstance = [i | RInst i <- xs]
1052+
, Measure.ilaws = [i | ILaws i <- xs]
10511053
, Measure.termexprs = [(y, es) | Asrts (ys, (_, Just es)) <- xs, y <- ys]
10521054
, Measure.lazy = S.fromList [s | Lazy s <- xs]
10531055
, Measure.bounds = M.fromList [(bname i, i) | PBound i <- xs]
@@ -1090,7 +1092,8 @@ specP
10901092
<|> liftM Class classP ))
10911093
<|> (reserved "instance"
10921094
>> ((reserved "measure" >> liftM IMeas iMeasureP )
1093-
<|> liftM RInst instanceP ))
1095+
<|> (reserved "laws" >> liftM ILaws instanceLawP)
1096+
<|> liftM RInst instanceP ))
10941097

10951098
<|> (reserved "import" >> liftM Impt symbolP )
10961099

@@ -1190,6 +1193,7 @@ tyBindsP = do
11901193
tyBindNoLocP :: Parser (LocSymbol, BareType)
11911194
tyBindNoLocP = second val <$> tyBindP
11921195

1196+
11931197
tyBindP :: Parser (LocSymbol, Located BareType)
11941198
tyBindP = xyP xP dcolon tP
11951199
where
@@ -1299,6 +1303,30 @@ oneClassArg
12991303
<|> ((:) <$> (fmap bTyVar <$> locLowerIdP) <*> classParams)
13001304
sing x = [x]
13011305

1306+
instanceLawP :: Parser (RILaws (Located BareType))
1307+
instanceLawP
1308+
= do l1 <- getPosition
1309+
sups <- supersP
1310+
c <- classBTyConP
1311+
spaces
1312+
tvs <- manyTill (locParserP bareTypeP) (try $ reserved "where")
1313+
spaces
1314+
ms <- grabs eqBinderP
1315+
spaces
1316+
l2 <- getPosition
1317+
return $ RIL c sups tvs ms (Loc l1 l2 ())
1318+
where
1319+
superP = locParserP (toRCls <$> bareAtomBindP)
1320+
supersP = try (((parens (superP `sepBy1` comma)) <|> fmap pure superP)
1321+
<* reservedOp "=>")
1322+
<|> return []
1323+
toRCls x = x
1324+
1325+
eqBinderP = xyP xP (spaces >> string "=" <* spaces) (xP <* spaces)
1326+
1327+
xP = locParserP binderP
1328+
1329+
13021330
instanceP :: Parser (RInstance (Located BareType))
13031331
instanceP
13041332
= do _ <- supersP

0 commit comments

Comments
 (0)