{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE PartialTypeSignatures #-} module Language.Haskell.Liquid.Bare.Plugged ( makePluggedSig , makePluggedDataCon ) where import Prelude hiding (error) import Data.Generics.Aliases (mkT) import Data.Generics.Schemes (everywhere) import Text.PrettyPrint.HughesPJ import qualified Control.Exception as Ex import qualified Data.HashMap.Strict as M import qualified Data.HashSet as S import qualified Data.Maybe as Mb import qualified Data.List as L import qualified Language.Fixpoint.Types as F import qualified Language.Fixpoint.Types.Visitor as F import qualified Language.Haskell.Liquid.GHC.Misc as GM import qualified Language.Haskell.Liquid.GHC.API as Ghc import Language.Haskell.Liquid.GHC.Types (StableName, mkStableName) import Language.Haskell.Liquid.Types.RefType () import Language.Haskell.Liquid.Types import qualified Language.Haskell.Liquid.Misc as Misc import qualified Language.Haskell.Liquid.Bare.Types as Bare import qualified Language.Haskell.Liquid.Bare.Misc as Bare --------------------------------------------------------------------------------------- -- [NOTE: Plug-Holes-TyVars] We have _two_ versions of `plugHoles: -- * `HsTyVars` ensures that the returned signature uses the GHC type variables; -- We need this as these tyvars can appear in the SOURCE (as type annotations, or -- as the types of lambdas) and renaming them will cause problems; -- * `LqTyVars` ensures that the returned signatuer uses the LIQUID type variables; -- We need this e.g. for class specifications where we cannot change the tyvars -- used inside method signatures as that messes up the type for the data-constructor -- for the dictionary (as we need to use the same tyvars as are "bound" in the class -- definition). -- In short, use `HsTyVars` when we also have to analyze the binder's SOURCE; and -- otherwise, use `LqTyVars`. --------------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- | NOTE: Be *very* careful with the use functions from RType -> GHC.Type, -- e.g. toType, in this module as they cannot handle LH type holes. Since -- this module is responsible for plugging the holes we obviously cannot -- assume, as in e.g. L.H.L.Constraint.* that they do not appear. -------------------------------------------------------------------------------- makePluggedSig :: ModName -> F.TCEmb Ghc.TyCon -> TyConMap -> S.HashSet StableName -> Bare.PlugTV Ghc.Var -> LocSpecType -> LocSpecType makePluggedSig name embs tyi exports kx t | Just x <- kxv = mkPlug x | otherwise = t where kxv = Bare.plugSrc kx mkPlug x = plugHoles kx embs tyi r τ t where τ = Ghc.expandTypeSynonyms (Ghc.varType x) r = maybeTrue x name exports -- x = case kx of { Bare.HsTV x -> x ; Bare.LqTV x -> x } -- makePluggedDataCon = makePluggedDataCon_old -- plugHoles = plugHoles_old -- makePluggedDataCon = makePluggedDataCon_new -- plugHoles _ = plugHoles_old plugHoles :: (Ghc.NamedThing a, PPrint a, Show a) => Bare.PlugTV a -> F.TCEmb Ghc.TyCon -> Bare.TyConMap -> (SpecType -> RReft -> RReft) -> Ghc.Type -> LocSpecType -> LocSpecType plugHoles (Bare.HsTV x) a b = plugHoles_old a b x plugHoles (Bare.LqTV x) a b = plugHoles_new a b x plugHoles _ _ _ = \_ _ t -> t makePluggedDataCon :: F.TCEmb Ghc.TyCon -> Bare.TyConMap -> Located DataConP -> Located DataConP makePluggedDataCon embs tyi ldcp | mismatchFlds = Ex.throw (err "fields") | mismatchTyVars = Ex.throw (err "type variables") | otherwise = F.atLoc ldcp $ F.notracepp "makePluggedDataCon" $ dcp { dcpFreeTyVars = dcVars , dcpTyArgs = reverse tArgs , dcpTyRes = tRes } where (tArgs, tRes) = plugMany embs tyi ldcp (das, dts, dt) (dcVars, dcArgs, dcpTyRes dcp) (das, _, dts, dt) = {- F.notracepp ("makePluggedDC: " ++ F.showpp dc) $ -} Ghc.dataConSig dc dcArgs = filter (not . isClassType . snd) $ reverse (dcpTyArgs dcp) dcVars = if isGADT then padGADVars $ L.nub (dcpFreeTyVars dcp ++ (concatMap (map ty_var_value . freeTyVars) (dcpTyRes dcp:(snd <$> dcArgs)))) else dcpFreeTyVars dcp dc = dcpCon dcp dcp = val ldcp isGADT = Ghc.isGadtSyntaxTyCon $ Ghc.dataConTyCon dc -- hack to match LH and GHC GADT vars, since it is unclear how ghc generates free vars padGADVars vs = (RTV <$> take (length das - length vs) das) ++ vs mismatchFlds = length dts /= length dcArgs mismatchTyVars = length das /= length dcVars err things = ErrBadData (GM.fSrcSpan dcp) (pprint dc) ("GHC and Liquid specifications have different numbers of" <+> things) :: UserError -- | @plugMany@ is used to "simultaneously" plug several different types, -- e.g. as arise in the fields of a data constructor. To do so we create -- a single "function type" that is then passed into @plugHoles@. -- We also pass in the type parameters as dummy arguments, because, e.g. -- we want @plugMany@ on the two types -- -- forall a. a -> a -> Bool -- forall b. _ -> _ -> _ -- -- to return something like -- -- forall b. b -> b -> Bool -- -- and not, forall b. a -> a -> Bool. plugMany :: F.TCEmb Ghc.TyCon -> Bare.TyConMap -> Located DataConP -> ([Ghc.Var], [Ghc.Type], Ghc.Type) -- ^ hs type -> ([RTyVar] , [(F.Symbol, SpecType)], SpecType) -- ^ lq type -> ([(F.Symbol, SpecType)], SpecType) -- ^ plugged lq type plugMany embs tyi ldcp (hsAs, hsArgs, hsRes) (lqAs, lqArgs, lqRes) = F.notracepp msg (drop nTyVars (zip xs ts), t) where (_,(xs,ts,_), t) = bkArrow (val pT) pT = plugHoles (Bare.LqTV dcName) embs tyi (const killHoles) hsT (F.atLoc ldcp lqT) hsT = foldr (Ghc.mkFunTy Ghc.VisArg) hsRes hsArgs' lqT = foldr (uncurry rFun) lqRes lqArgs' hsArgs' = [ Ghc.mkTyVarTy a | a <- hsAs] ++ hsArgs lqArgs' = [(F.dummySymbol, RVar a mempty) | a <- lqAs] ++ lqArgs nTyVars = length hsAs -- == length lqAs dcName = Ghc.dataConName . dcpCon . val $ ldcp msg = "plugMany: " ++ F.showpp (dcName, hsT, lqT) plugHoles_old, plugHoles_new :: (Ghc.NamedThing a, PPrint a, Show a) => F.TCEmb Ghc.TyCon -> Bare.TyConMap -> a -> (SpecType -> RReft -> RReft) -> Ghc.Type -> LocSpecType -> LocSpecType -- NOTE: this use of toType is safe as rt' is derived from t. plugHoles_old tce tyi x f t0 zz@(Loc l l' st0) = Loc l l' . mkArrow (zip (updateRTVar <$> αs') rs) ps' [] [] . makeCls cs' . goPlug tce tyi err f (subts su rt) . mapExprReft (\_ -> F.applyCoSub coSub) . subts su $ st where tyvsmap = case Bare.runMapTyVars (toType rt) st err of Left e -> Ex.throw e Right s -> Bare.vmap s su = [(y, rTyVar x) | (x, y) <- tyvsmap] su' = [(y, RVar (rTyVar x) ()) | (x, y) <- tyvsmap] :: [(RTyVar, RSort)] coSub = M.fromList [(F.symbol y, F.FObj (F.symbol x)) | (y, x) <- su] ps' = fmap (subts su') <$> ps cs' = [(F.dummySymbol, RApp c ts [] mempty) | (c, ts) <- cs ] (αs', rs) = unzip αs (αs,_,cs,rt) = bkUnivClass (F.notracepp "hs-spec" $ ofType (Ghc.expandTypeSynonyms t0) :: SpecType) (_,ps,_ ,st) = bkUnivClass (F.notracepp "lq-spec" st0) makeCls cs t = foldr (uncurry rFun) t cs err hsT lqT = ErrMismatch (GM.fSrcSpan zz) (pprint x) (text "Plugged Init types old") (pprint $ Ghc.expandTypeSynonyms t0) (pprint $ toRSort st0) (Just (hsT, lqT)) (Ghc.getSrcSpan x) plugHoles_new tce tyi x f t0 zz@(Loc l l' st0) = Loc l l' . mkArrow (zip (updateRTVar <$> as'') rs) ps [] [] . makeCls cs' . goPlug tce tyi err f rt' $ st where rt' = tx rt as'' = subRTVar su <$> as' (as',rs) = unzip as cs' = [ (F.dummySymbol, ct) | (c, t) <- cs, let ct = tx (RApp c t [] mempty) ] tx = subts su su = case Bare.runMapTyVars (toType rt) st err of Left e -> Ex.throw e Right s -> [ (rTyVar x, y) | (x, y) <- Bare.vmap s] (as,_,cs,rt) = bkUnivClass (ofType (Ghc.expandTypeSynonyms t0) :: SpecType) (_,ps,_ ,st) = bkUnivClass st0 makeCls cs t = foldr (uncurry rFun) t cs err hsT lqT = ErrMismatch (GM.fSrcSpan zz) (pprint x) (text "Plugged Init types new") (pprint $ Ghc.expandTypeSynonyms t0) (pprint $ toRSort st0) (Just (hsT, lqT)) (Ghc.getSrcSpan x) subRTVar :: [(RTyVar, RTyVar)] -> SpecRTVar -> SpecRTVar subRTVar su a@(RTVar v i) = Mb.maybe a (`RTVar` i) (lookup v su) goPlug :: F.TCEmb Ghc.TyCon -> Bare.TyConMap -> (Doc -> Doc -> Error) -> (SpecType -> RReft -> RReft) -> SpecType -> SpecType -> SpecType goPlug tce tyi err f = go where go t (RHole r) = (addHoles t') { rt_reft = f t r } where t' = everywhere (mkT $ addRefs tce tyi) t addHoles = everywhere (mkT $ addHole) -- NOTE: make sure we only add holes to RVar and RApp (NOT RFun) addHole :: SpecType -> SpecType addHole t@(RVar v _) = RVar v (f t (uReft ("v", hole))) addHole t@(RApp c ts ps _) = RApp c ts ps (f t (uReft ("v", hole))) addHole t = t go (RVar _ _) v@(RVar _ _) = v go t' (RImpF x i o r) = RImpF x i (go t' o) r go (RFun _ i o _) (RFun x i' o' r) = RFun x (go i i') (go o o') r go (RAllT _ t _) (RAllT a t' r) = RAllT a (go t t') r go (RAllT a t r) t' = RAllT a (go t t') r go t (RAllP p t') = RAllP p (go t t') go t (RAllE b a t') = RAllE b a (go t t') go t (REx b x t') = REx b x (go t t') go t (RRTy e r o t') = RRTy e r o (go t t') go (RAppTy t1 t2 _) (RAppTy t1' t2' r) = RAppTy (go t1 t1') (go t2 t2') r -- zipWithDefM: if ts and ts' have different length then the liquid and haskell types are different. -- keep different types for now, as a pretty error message will be created at Bare.Check go (RApp _ ts _ _) (RApp c ts' p r) | length ts == length ts' = RApp c (Misc.zipWithDef go ts $ Bare.matchKindArgs ts ts') p r go hsT lqT = Ex.throw (err (F.pprint hsT) (F.pprint lqT)) -- otherwise = Ex.throw err -- If we reach the default case, there's probably an error, but we defer -- throwing it as checkGhcSpec does a much better job of reporting the -- problem to the user. -- go st _ = st addRefs :: F.TCEmb Ghc.TyCon -> TyConMap -> SpecType -> SpecType addRefs tce tyi (RApp c ts _ r) = RApp c' ts ps r where RApp c' _ ps _ = addTyConInfo tce tyi (RApp c ts [] r) addRefs _ _ t = t maybeTrue :: Ghc.NamedThing a => a -> ModName -> S.HashSet StableName -> SpecType -> RReft -> RReft maybeTrue x target exports t r | not (isFunTy t) && (Ghc.isInternalName name || inTarget && notExported) = r | otherwise = killHoles r where inTarget = Ghc.moduleName (Ghc.nameModule name) == getModName target name = Ghc.getName x notExported = not (mkStableName (Ghc.getName x) `S.member` exports) -- killHoles r@(U (Reft (v, rs)) _ _) = r { ur_reft = Reft (v, filter (not . isHole) rs) } killHoles :: RReft -> RReft killHoles ur = ur { ur_reft = tx $ ur_reft ur } where tx r = {- traceFix ("killholes: r = " ++ showFix r) $ -} F.mapPredReft dropHoles r dropHoles = F.pAnd . filter (not . isHole) . F.conjuncts