Skip to content

Commit f5c035c

Browse files
authored
Merge pull request #2559 from AlecsFerra/develop
Support for controlled negative occurrences
2 parents cfd99bd + e671efb commit f5c035c

File tree

18 files changed

+473
-60
lines changed

18 files changed

+473
-60
lines changed

docs/mkDocs/docs/specifications.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ of each here.
4747
* `{-@ <binding-signature-with-refinement-type> @-}` introduces a refinement type for the named Haskell definition.
4848
* For a function, the refinements become pre and post conditions for the functions use.
4949
* This is probably the most used Liquid Haskell annotation!
50+
* `{-@ stratified <data-type-declaration> @-}` the datatype is treated as a stratified type by the well-definedness checker.
51+
([Jump to: Stratified Types](http://ucsd-progsys.github.io/liquidhaskell/specifications/#stratified-types))
52+
5053

5154
The following sections detail more variety for the uses of the above annotations.
5255

@@ -1123,3 +1126,63 @@ safeDiv x y
11231126
```
11241127

11251128
In this example, the `lazyvar` annotation on `help` ensures that the check for `help` is deferred until it is used. Without this annotation, LiquidHaskell would incorrectly report an error like `Error: Liquid Type Mismatch`.
1129+
1130+
## Stratified Types
1131+
1132+
Liquid Haskell disallows arbitrary user-defined data types, since some
1133+
can be used to derive inconsistencies in the refinement logic.
1134+
1135+
```haskell
1136+
data Evil a = Very (Evil a -> a)
1137+
1138+
{-@ type Bot = {v:() | false} @-}
1139+
1140+
{-@ bad :: Evil Bot -> Bot @-}
1141+
bad :: Evil () -> ()
1142+
bad (Very f) = f (Very f)
1143+
1144+
{-@ worse :: Bot @-}
1145+
worse :: ()
1146+
worse = bad (Very bad)
1147+
```
1148+
1149+
This definition is rejected by Liquid Haskells [positivity
1150+
checker](https://ucsd-progsys.github.io/liquidhaskell/options/#positivity-check),
1151+
which enforces that recursive occurrences of a data type only appear
1152+
in **positive** positions (to the right of function arrows).
1153+
Positivity is a *sufficient* condition for logical consistency but not
1154+
a *necessary* one. Some definitions that fail the positivity check are
1155+
still well-founded and consistent. A class of those are **stratified
1156+
types**.
1157+
1158+
### Definition
1159+
1160+
A **stratified type** is a data type refined with `Prop` from
1161+
`Language.Haskell.Liquid.ProofCombinators`, where all recursive
1162+
occurrences of the type:
1163+
- appear under `Prop`, and
1164+
- are indexed by **strictly smaller** values than the index of the
1165+
constructor itself.
1166+
1167+
This controlled form of non positive types preserves consistency while
1168+
allowing more expressive data definitions.
1169+
1170+
### Example
1171+
1172+
The following defines values of the simply typed lambda calculus as a
1173+
stratified type:
1174+
1175+
```haskell
1176+
data Ty = TInt | TFun Ty Ty
1177+
1178+
{-@ stratified Val @-}
1179+
data Val where
1180+
{-@ VInt :: Int -> Prop (Val TInt) @-}
1181+
VInt :: Int -> Val
1182+
1183+
{-@ VFun :: t1:Ty -> t2:Ty
1184+
-> (Prop (Val t1) -> Prop (Val t2))
1185+
-> Prop (Val (TFun t1 t2)) @-}
1186+
VFun :: Ty -> Ty -> (Val -> Val) -> Val
1187+
data VAL = Val Ty
1188+
```

liquidhaskell-boot/src/Language/Haskell/Liquid/Bare.hs

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -93,17 +93,18 @@ makeTargetSpec :: Config
9393
-> TargetDependencies
9494
-> Ghc.TcRn (Either Diagnostics ([Warning], TargetSpec, LiftedSpec))
9595
makeTargetSpec cfg localVars lnameEnv lmap targetSrc bareSpec dependencies = do
96-
let targDiagnostics = Bare.checkTargetSrc cfg targetSrc
96+
let targDiagnostics = Bare.checkTargetSrc cfg bareSpec targetSrc
9797
let depsDiagnostics = mapM (Bare.checkBareSpec . snd) legacyDependencies
9898
let bareSpecDiagnostics = Bare.checkBareSpec bareSpec
99-
case targDiagnostics >> depsDiagnostics >> bareSpecDiagnostics of
100-
Left d | noErrors d -> secondPhase (allWarnings d)
99+
let stratDiagnostics = Bare.checkStratTys bareSpec targetSrc
100+
case targDiagnostics >> depsDiagnostics >> bareSpecDiagnostics >> stratDiagnostics of
101+
Left d | noErrors d -> secondPhase [] (allWarnings d)
101102
Left d -> return $ Left d
102-
Right () -> secondPhase mempty
103+
Right stratNames -> secondPhase stratNames mempty
103104
where
104-
secondPhase :: [Warning] -> Ghc.TcRn (Either Diagnostics ([Warning], TargetSpec, LiftedSpec))
105-
secondPhase phaseOneWarns = do
106-
diagOrSpec <- makeGhcSpec cfg lnameEnv localVars (fromTargetSrc targetSrc) lmap bareSpec legacyDependencies
105+
secondPhase :: [Ghc.Name] -> [Warning] -> Ghc.TcRn (Either Diagnostics ([Warning], TargetSpec, LiftedSpec))
106+
secondPhase stratNames phaseOneWarns = do
107+
diagOrSpec <- makeGhcSpec stratNames cfg lnameEnv localVars (fromTargetSrc targetSrc) lmap bareSpec legacyDependencies
107108
case diagOrSpec of
108109
Left d -> return $ Left d
109110
Right (warns, ghcSpec) -> do
@@ -144,7 +145,8 @@ makeTargetSpec cfg localVars lnameEnv lmap targetSrc bareSpec dependencies = do
144145
-- | @makeGhcSpec@ invokes @makeGhcSpec0@ to construct the @GhcSpec@ and then
145146
-- validates it using @checkGhcSpec@.
146147
-------------------------------------------------------------------------------------
147-
makeGhcSpec :: Config
148+
makeGhcSpec :: [Ghc.Name]
149+
-> Config
148150
-> LogicNameEnv
149151
-> Bare.LocalVars
150152
-> GhcSrc
@@ -153,11 +155,11 @@ makeGhcSpec :: Config
153155
-> [(ModName, Ms.BareSpec)]
154156
-> Ghc.TcRn (Either Diagnostics ([Warning], GhcSpec))
155157
-------------------------------------------------------------------------------------
156-
makeGhcSpec cfg lenv localVars src lmap bareSpec dependencySpecs = do
158+
makeGhcSpec stratNames cfg lenv localVars src lmap bareSpec dependencySpecs = do
157159
ghcTyLookupEnv <- Bare.makeGHCTyLookupEnv (_giCbs src)
158160
tcg <- Ghc.getGblEnv
159161
instEnvs <- Ghc.tcGetInstEnvs
160-
(dg0, sp) <- makeGhcSpec0 cfg ghcTyLookupEnv tcg instEnvs lenv localVars src lmap bareSpec dependencySpecs
162+
(dg0, sp) <- makeGhcSpec0 stratNames cfg ghcTyLookupEnv tcg instEnvs lenv localVars src lmap bareSpec dependencySpecs
161163
let diagnostics = Bare.checkTargetSpec (bareSpec : map snd dependencySpecs)
162164
(toTargetSrc src)
163165
(ghcSpecEnv sp)
@@ -195,7 +197,8 @@ ghcSpecEnv sp = F.notracepp "RENV" $ fromListSEnv binds
195197
-- lets us use aliases inside data-constructor definitions.
196198
-------------------------------------------------------------------------------------
197199
makeGhcSpec0
198-
:: Config
200+
:: [Ghc.Name]
201+
-> Config
199202
-> Bare.GHCTyLookupEnv
200203
-> Ghc.TcGblEnv
201204
-> Ghc.InstEnvs
@@ -206,7 +209,7 @@ makeGhcSpec0
206209
-> Ms.BareSpec
207210
-> [(ModName, Ms.BareSpec)]
208211
-> Ghc.TcRn (Diagnostics, GhcSpec)
209-
makeGhcSpec0 cfg ghcTyLookupEnv tcg instEnvs lenv localVars src lmap bareSpec dependencySpecs = do
212+
makeGhcSpec0 stratNames cfg ghcTyLookupEnv tcg instEnvs lenv localVars src lmap bareSpec dependencySpecs = do
210213
globalRdrEnv <- Ghc.tcg_rdr_env <$> Ghc.getGblEnv
211214
-- build up environments
212215
tycEnv <- makeTycEnv1 env (tycEnv0, datacons) coreToLg simplifier
@@ -223,7 +226,7 @@ makeGhcSpec0 cfg ghcTyLookupEnv tcg instEnvs lenv localVars src lmap bareSpec de
223226
-- NB: we first compute a measure environment w/o the opaque reflections, so that we can bootstrap
224227
-- the signature `sig`. Then we'll add the opaque reflections before we compute `sData` and al.
225228
let (dg1, measEnv0) = withDiagnostics $ makeMeasEnv env tycEnv sigEnv specs
226-
let (dg2, (specInstances, sig)) = withDiagnostics $ makeSpecSig cfg name mySpec iSpecs2 env sigEnv tycEnv measEnv0 (_giCbs src)
229+
let (dg2, (specInstances, sig)) = withDiagnostics $ makeSpecSig stratNames cfg name mySpec iSpecs2 env sigEnv tycEnv measEnv0 (_giCbs src)
227230
elaboratedSig <-
228231
if allowTC then Bare.makeClassAuxTypes (elaborateSpecType coreToLg simplifier) datacons instMethods
229232
>>= elaborateSig sig
@@ -862,10 +865,10 @@ makeAutoInst env spec = S.fromList <$> kvs
862865

863866

864867
----------------------------------------------------------------------------------------
865-
makeSpecSig :: Config -> ModName -> Ms.BareSpec -> Bare.ModSpecs -> Bare.Env -> Bare.SigEnv -> Bare.TycEnv -> Bare.MeasEnv -> [Ghc.CoreBind]
868+
makeSpecSig :: [Ghc.Name] -> Config -> ModName -> Ms.BareSpec -> Bare.ModSpecs -> Bare.Env -> Bare.SigEnv -> Bare.TycEnv -> Bare.MeasEnv -> [Ghc.CoreBind]
866869
-> Bare.Lookup ([RInstance LocBareType], GhcSpecSig)
867870
----------------------------------------------------------------------------------------
868-
makeSpecSig cfg name mySpec specs env sigEnv tycEnv measEnv cbs = do
871+
makeSpecSig stratNames cfg name mySpec specs env sigEnv tycEnv measEnv cbs = do
869872
mySigs <- makeTySigs env sigEnv name mySpec
870873
aSigs <- F.notracepp ("makeSpecSig aSigs " ++ F.showpp name) $ makeAsmSigs env sigEnv name allSpecs
871874
let asmSigs = Bare.tcSelVars tycEnv ++ aSigs
@@ -880,6 +883,7 @@ makeSpecSig cfg name mySpec specs env sigEnv tycEnv measEnv cbs = do
880883
asmRel <- makeRelation env name sigEnv (Ms.asmRel mySpec)
881884
return (instances, SpSig
882885
{ gsTySigs = tySigs
886+
, gsStratCtos = stratNames
883887
, gsAsmSigs = asmSigs
884888
, gsAsmReflects = bimap getVar getVar <$> concatMap (asmReflectSigs . snd) allSpecs
885889
, gsRefSigs = []

liquidhaskell-boot/src/Language/Haskell/Liquid/Bare/Check.hs

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@
33
{-# LANGUAGE TupleSections #-}
44
{-# LANGUAGE RecordWildCards #-}
55
{-# LANGUAGE OverloadedStrings #-}
6+
{-# LANGUAGE DeriveTraversable #-}
7+
68
{-# OPTIONS_GHC -Wno-x-partial #-}
79

810
module Language.Haskell.Liquid.Bare.Check
911
( checkTargetSpec
1012
, checkBareSpec
1113
, checkTargetSrc
14+
, checkStratTys
1215
, tyCompat
1316
) where
1417

15-
1618
import Language.Haskell.Liquid.Constraint.ToFixpoint
1719

1820
import Liquid.GHC.API as Ghc hiding ( Located
@@ -55,27 +57,101 @@ import qualified Language.Haskell.Liquid.Bare.Resolve as Bare
5557
import Language.Haskell.Liquid.UX.Config
5658
import Language.Fixpoint.Types.Config (ElabFlags (ElabFlags), solverFlags)
5759

58-
5960
----------------------------------------------------------------------------------------------
6061
-- | Checking TargetSrc ------------------------------------------------------------------------
6162
----------------------------------------------------------------------------------------------
62-
checkTargetSrc :: Config -> TargetSrc -> Either Diagnostics ()
63-
checkTargetSrc cfg spec
63+
checkTargetSrc :: Config -> BareSpec -> TargetSrc -> Either Diagnostics ()
64+
checkTargetSrc cfg bare spec
6465
| nopositivity cfg
6566
|| nopositives == emptyDiagnostics
6667
= Right ()
6768
| otherwise
6869
= Left nopositives
69-
where nopositives = checkPositives (gsTcs spec)
70+
where nopositives = checkPositives bare $ gsTcs spec
7071

72+
isStratifiedTyCon :: BareSpec -> TyCon -> Bool
73+
isStratifiedTyCon bs tc = Ghc.tyConName tc `elem` sn
74+
where sn = mapMaybe (getLHGHCName . F.val) $ S.toList $ stratified bs
7175

72-
checkPositives :: [TyCon] -> Diagnostics
73-
checkPositives tys = mkDiagnostics [] $ mkNonPosError (getNonPositivesTyCon tys)
76+
checkPositives :: BareSpec -> [TyCon] -> Diagnostics
77+
checkPositives bare tys = mkDiagnostics []
78+
$ mkNonPosError
79+
$ filter (not . isStratifiedTyCon bare . fst)
80+
$ getNonPositivesTyCon tys
7481

7582
mkNonPosError :: [(TyCon, [DataCon])] -> [Error]
7683
mkNonPosError tcs = [ ErrPosTyCon (getSrcSpan tc) (pprint tc) (pprint dc <+> ":" <+> pprint (dataConRepType dc))
7784
| (tc, dc:_) <- tcs]
7885

86+
--------------------------------------------------
87+
-- | Checking that stratified ctors are present --
88+
--------------------------------------------------
89+
90+
--- | Like 'Either' but the 'Semigroup' instance combines the failure
91+
--- | values.
92+
data Validation e a
93+
= Failure e
94+
| Success a
95+
deriving (Show, Eq, Functor, Foldable, Traversable)
96+
97+
instance (Semigroup e, Semigroup a) => Semigroup (Validation e a) where
98+
Failure e1 <> Failure e2 = Failure (e1 <> e2)
99+
Failure e <> _ = Failure e
100+
_ <> Failure e = Failure e
101+
Success x <> Success y = Success (x <> y)
102+
103+
instance (Semigroup e, Monoid a) => Monoid (Validation e a) where
104+
mempty = Success mempty
105+
mappend = (<>)
106+
107+
valToEither :: Validation e a -> Either e a
108+
valToEither (Failure e) = Left e
109+
valToEither (Success x) = Right x
110+
111+
-- | Check that all stratified types have their constructors
112+
-- defined with refinement type signatures in the BareSpec.
113+
--
114+
-- Yields the names of the data constructors of the stratified types.
115+
checkStratTys :: BareSpec -> TargetSrc -> Either Diagnostics [Name]
116+
checkStratTys bare spec =
117+
valToEither
118+
$ foldMap (checkStratTy bare)
119+
$ mapMaybe (traverse (findTyCon (gsTcs spec)))
120+
$ S.toList $ stratified bare
121+
122+
-- | Find the TyCon corresponding to the given LHName in the given list of TyCons
123+
findTyCon :: [TyCon] -> LHName -> Maybe TyCon
124+
findTyCon tcs nm = do
125+
c <- getLHGHCName nm
126+
L.find ((== c) . Ghc.tyConName) tcs
127+
128+
-- | Check that the given TyCon is an ADT and that all its constructors
129+
-- have refinements in the BareSpec.
130+
checkStratTy :: BareSpec -> Located TyCon -> Validation Diagnostics [Name]
131+
checkStratTy spec ltycon =
132+
case tyConDataCons_maybe (val ltycon) of
133+
Just ctors -> foldMap (checkStratCtor ltycon spec) ctors
134+
Nothing -> Failure $ mkDiagnostics mempty [ err ]
135+
where
136+
pos = GM.sourcePos2SrcSpan (loc ltycon) (locE ltycon)
137+
err = ErrStratNotAdt pos (pprint (Ghc.tyConName $ val ltycon))
138+
139+
-- | Check that the given DataCon has a refinement type signature in the BareSpec.
140+
--
141+
-- Yields the names of the data constructors that are stratified.
142+
checkStratCtor :: Located TyCon -> BareSpec -> DataCon -> Validation Diagnostics [Name]
143+
checkStratCtor ltycon spec datacon
144+
| hasRefinementTypeSignature datacon (map (val . fst) $ sigs spec)
145+
= Success [ dataConName datacon ]
146+
| otherwise = Failure $ mkDiagnostics mempty [ err ]
147+
where
148+
pos = GM.sourcePos2SrcSpan (loc ltycon) (locE ltycon)
149+
err = ErrStratNotRefCtor pos (pprint $ dataConName datacon) (pprint $ Ghc.tyConName $ val ltycon)
150+
hasRefinementTypeSignature :: DataCon -> [LHName] -> Bool
151+
hasRefinementTypeSignature dc lns =
152+
dataConName dc `elem` mapMaybe getLHGHCName lns
153+
154+
79155
----------------------------------------------------------------------------------------------
80156
-- | Checking BareSpec ------------------------------------------------------------------------
81157
----------------------------------------------------------------------------------------------
@@ -223,11 +299,7 @@ checkConstructorRefinement = mconcat . map checkOne
223299
validRef (F.Reft (_, F.PTrue))
224300
= True
225301
-- Prop foo from ProofCombinators
226-
validRef (F.Reft (v, F.PAtom F.Eq (F.EApp (F.EVar n) (F.EVar v')) _))
227-
| n == "Language.Haskell.Liquid.ProofCombinators.prop"
228-
, v == v'
229-
= True
230-
validRef _ = False
302+
validRef n = isJust $ getPropIndex n
231303

232304
isCtorName x = case idDetails x of
233305
DataConWorkId _ -> True

0 commit comments

Comments
 (0)