Skip to content

Commit 8ce52fc

Browse files
committed
Implement stratification
1 parent 1671fd4 commit 8ce52fc

File tree

12 files changed

+336
-32
lines changed

12 files changed

+336
-32
lines changed

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

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,15 @@ makeTargetSpec cfg localVars lnameEnv lmap targetSrc bareSpec dependencies = do
9696
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: 69 additions & 10 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,8 +57,6 @@ 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-
-- import Debug.Trace
59-
6060
----------------------------------------------------------------------------------------------
6161
-- | Checking TargetSrc ------------------------------------------------------------------------
6262
----------------------------------------------------------------------------------------------
@@ -67,21 +67,80 @@ checkTargetSrc cfg bare spec
6767
= Right ()
6868
| otherwise
6969
= Left nopositives
70-
where nopositives = checkPositives $ filter (not . isStratifiedTyCon bare) $ gsTcs spec
70+
where nopositives = checkPositives bare $ gsTcs spec
7171

7272
isStratifiedTyCon :: BareSpec -> TyCon -> Bool
73-
isStratifiedTyCon bs tc = (Ghc.tyConName tc) `elem` sn
74-
where sn = mapMaybe ctorName $ S.toList $ stratified bs
75-
ctorName (F.Loc _ _ (LHNResolved (LHRGHC c) _)) = Just c
76-
ctorName _ = Nothing
73+
isStratifiedTyCon bs tc = Ghc.tyConName tc `elem` sn
74+
where
75+
-- (Alecs): For some reason it can't see that they are the same
76+
-- GHC name, probably is due to some worker wrapper shenannigans
77+
sn = mapMaybe ctorName $ S.toList $ stratified bs
78+
ctorName (F.Loc _ _ (LHNResolved (LHRGHC c) _)) = Just c
79+
ctorName _ = Nothing
7780

78-
checkPositives :: [TyCon] -> Diagnostics
79-
checkPositives tys = mkDiagnostics [] $ mkNonPosError (getNonPositivesTyCon tys)
81+
checkPositives :: BareSpec -> [TyCon] -> Diagnostics
82+
checkPositives bare tys = mkDiagnostics []
83+
$ mkNonPosError
84+
$ filter (not . isStratifiedTyCon bare . fst)
85+
$ getNonPositivesTyCon tys
8086

8187
mkNonPosError :: [(TyCon, [DataCon])] -> [Error]
8288
mkNonPosError tcs = [ ErrPosTyCon (getSrcSpan tc) (pprint tc) (pprint dc <+> ":" <+> pprint (dataConRepType dc))
8389
| (tc, dc:_) <- tcs]
8490

91+
--------------------------------------------------
92+
-- | Checking that stratified ctors are present --
93+
--------------------------------------------------
94+
95+
data Validation e a
96+
= Failure e
97+
| Success a
98+
deriving (Show, Eq, Functor, Foldable, Traversable)
99+
100+
instance (Semigroup e, Semigroup a) => Semigroup (Validation e a) where
101+
Failure e1 <> Failure e2 = Failure (e1 <> e2)
102+
Failure e <> _ = Failure e
103+
_ <> Failure e = Failure e
104+
Success x <> Success y = Success (x <> y)
105+
106+
instance (Semigroup e, Monoid a) => Monoid (Validation e a) where
107+
mempty = Success mempty
108+
mappend = (<>)
109+
110+
valToEither :: Validation e a -> Either e a
111+
valToEither (Failure e) = Left e
112+
valToEither (Success x) = Right x
113+
114+
checkStratTys :: BareSpec -> TargetSrc -> Either Diagnostics [Name]
115+
checkStratTys bare spec = valToEither
116+
$ foldMap (uncurry $ checkStratTy bare)
117+
$ mapMaybe (locateStratTcs bare) (gsTcs spec)
118+
119+
locateStratTcs :: BareSpec -> TyCon -> Maybe (SrcSpan, TyCon)
120+
locateStratTcs bs tc = listToMaybe $ mapMaybe ctorName $ S.toList $ stratified bs
121+
where
122+
ctorName (F.Loc s e (LHNResolved (LHRGHC c) _))
123+
| c == Ghc.tyConName tc = Just (GM.sourcePos2SrcSpan s e, tc)
124+
ctorName _ = Nothing
125+
126+
checkStratTy :: BareSpec -> SrcSpan -> TyCon -> Validation Diagnostics [Name]
127+
checkStratTy spec pos tycon =
128+
case tyConDataCons_maybe tycon of
129+
Just ctors -> foldMap (checkStratCtor tycon spec pos) ctors
130+
Nothing -> Failure $ mkDiagnostics mempty [ err ]
131+
where err = ErrStratNotAdt pos (pprint (Ghc.tyConName tycon))
132+
133+
checkStratCtor :: TyCon -> BareSpec -> SrcSpan -> DataCon -> Validation Diagnostics [Name]
134+
checkStratCtor tcon spec pos datacon
135+
| Just nm <- listToMaybe $ mapMaybe (isThisDataCon . val . fst) $ sigs spec
136+
= Success [ nm ]
137+
| otherwise = Failure $ mkDiagnostics mempty [ err ]
138+
where err = ErrStratNotRefCtor pos (pprint $ dataConName datacon) (pprint $ Ghc.tyConName tcon)
139+
isThisDataCon (LHNResolved (LHRGHC c) _)
140+
| c == dataConName datacon = Just c
141+
isThisDataCon _ = Nothing
142+
143+
85144
----------------------------------------------------------------------------------------------
86145
-- | Checking BareSpec ------------------------------------------------------------------------
87146
----------------------------------------------------------------------------------------------

liquidhaskell-boot/src/Language/Haskell/Liquid/Constraint/Generate.hs

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
{-# LANGUAGE MultiParamTypeClasses #-}
88
{-# LANGUAGE OverloadedStrings #-}
99
{-# LANGUAGE ImplicitParams #-}
10+
{-# LANGUAGE NamedFieldPuns #-}
1011

1112
{-# OPTIONS_GHC -Wno-orphans #-}
1213
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
@@ -28,8 +29,9 @@ import qualified Language.Haskell.Liquid.GHC.Resugar as Rs
2829
import qualified Language.Haskell.Liquid.GHC.SpanStack as Sp
2930
import qualified Language.Haskell.Liquid.GHC.Misc as GM -- ( isInternal, collectArguments, tickSrcSpan, showPpr )
3031
import Text.PrettyPrint.HughesPJ ( text )
31-
import Control.Monad ( foldM, forM, forM_, when, void )
32+
import Control.Monad ( foldM, forM, forM_, when, void, unless)
3233
import Control.Monad.State
34+
import Data.Bifunctor (first)
3335
import Data.Maybe (fromMaybe, isJust, mapMaybe)
3436
import Data.Either.Extra (eitherToMaybe)
3537
import qualified Data.HashMap.Strict as M
@@ -97,12 +99,110 @@ consAct γ cfg info = do
9799
hws <- gets hsWfs
98100
fcs <- concat <$> mapM (splitC (typeclass (getConfig info))) hcs
99101
fws <- concat <$> mapM splitW hws
102+
checkStratCtors γ sSpc
100103
modify $ \st -> st { fEnv = fEnv st `mappend` feEnv (fenv γ)
101104
, cgLits = litEnv γ
102105
, cgConsts = cgConsts st `mappend` constEnv γ
103106
, fixCs = fcs
104107
, fixWfs = fws }
105108

109+
110+
---------------------------------
111+
-- | Checking stratified ctors --
112+
---------------------------------
113+
type FExpr = F.ExprV F.Symbol
114+
type FRef = F.ReftV F.Symbol
115+
type Ctors = S.HashSet F.Symbol
116+
117+
checkStratCtors :: CGEnv -> GhcSpecSig -> CG ()
118+
checkStratCtors env sSpc = do
119+
let ctors = S.fromList $ M.keys $ F.seBinds $ constEnv env
120+
let ctorRefinements = filter (isStrat . fst) $ gsTySigs sSpc
121+
forM_ ctorRefinements $ uncurry $ checkCtor ctors
122+
where
123+
-- (Alecs): For some reason it can't see that they are the same
124+
-- GHC name, probably is due to some worker wrapper shenannigans
125+
hack = occNameString . nameOccName
126+
isStrat nm = hack (varName nm) `elem` map hack (gsStratCtos sSpc)
127+
128+
uncurryPi :: SpecType -> ([SpecType], SpecType)
129+
uncurryPi (RFun _ _ dom cod _) = first (dom :) $ uncurryPi cod
130+
uncurryPi rest = ([], rest)
131+
132+
getIndex :: FRef -> Maybe FExpr
133+
getIndex (F.Reft (v, F.PAtom F.Eq (F.EApp (F.EVar n) (F.EVar v')) to))
134+
| n == "Language.Haskell.Liquid.ProofCombinators.prop"
135+
, v == v'
136+
= Just to
137+
getIndex _ = Nothing
138+
139+
getTyConName :: SpecType -> Name
140+
getTyConName a = Ghc.tyConName $ rtc_tc $ rt_tycon a
141+
142+
checkCtor :: Ctors -> Var -> LocSpecType -> CG ()
143+
checkCtor ctors name typ = do
144+
let loc = GM.fSrcSpan $ F.loc typ
145+
let (args, ret) = uncurryPi $ val typ
146+
-- The constuctor that we want not to appear negatrive
147+
let tyName = getTyConName ret
148+
-- Its index information
149+
retIdx <- case getIndex $ ur_reft $ rt_reft ret of
150+
Just idx -> pure idx
151+
Nothing -> uError $ ErrStratNotPropRet loc (pprint tyName) (pprint name) (F.pprint ret)
152+
-- For every argument of the constructor we check that in negative
153+
-- position all the self-refernce are refined by a "smaller" `prop`
154+
-- annotation
155+
forM_ args $ checkNg ctors loc name tyName retIdx
156+
157+
checkNg :: Ctors -> SrcSpan -> Var -> Name -> FExpr -> SpecType -> CG ()
158+
checkNg ctors loc ctorName tyName retIdx = go
159+
where
160+
go :: SpecType -> CG ()
161+
go RVar {} = pure ()
162+
go RAllT { rt_ty } = go rt_ty
163+
go RAllP { rt_ty } = go rt_ty
164+
go RFun { rt_in, rt_out } = do
165+
go rt_in
166+
go rt_out
167+
go r@RApp { rt_tycon = RTyCon { rtc_tc }, rt_args, rt_reft } = do
168+
if Ghc.tyConName rtc_tc == tyName then do
169+
case getIndex $ ur_reft rt_reft of
170+
(Just arg) -> do
171+
-- We compare index information
172+
-- The engativer occurrence is safe iff the index of the
173+
-- return type is strictly bigger than the one in negative
174+
-- position
175+
unless (isStructurallySmaller ctors arg retIdx) $ do
176+
uError $ ErrStratIdxNotSmall loc
177+
(pprint tyName) (pprint ctorName) (F.pprint retIdx) (F.pprint arg)
178+
-- We don't have index information for both so we bail
179+
_ -> uError $ ErrStratOccProp loc (pprint tyName) (pprint ctorName) (F.pprint r)
180+
else do
181+
forM_ rt_args go
182+
go _ = lift $ impossible (Just loc) "checkNg unexpected type"
183+
184+
185+
isStructurallySmaller :: Ctors -> FExpr -> FExpr -> Bool
186+
isStructurallySmaller ctors l r
187+
-- Congruence rule
188+
| (F.EVar nl, argsl) <- F.splitEAppThroughECst l
189+
, (F.EVar nr, argsr) <- F.splitEAppThroughECst r
190+
, nl == nr
191+
, length argsl == length argsr
192+
, nl `elem` ctors
193+
= any (uncurry $ isStructurallySmaller ctors) $ zip argsl argsr
194+
| otherwise = isSubterm ctors l r && l /= r
195+
196+
isSubterm :: Ctors -> FExpr -> FExpr -> Bool
197+
isSubterm ctors l r | l == r
198+
= True
199+
-- Congruence rule
200+
| (F.EVar nm, args) <- F.splitEAppThroughECst r
201+
, nm `elem` ctors
202+
= any (isSubterm ctors l) args
203+
| otherwise
204+
= False
205+
106206
--------------------------------------------------------------------------------
107207
-- | Ensure that the instance type is a subtype of the class type --------------
108208
--------------------------------------------------------------------------------

0 commit comments

Comments
 (0)