Improve printing of provenance information, add more info

This commit is contained in:
John Wiegley 2018-04-23 10:06:49 -07:00
parent 3cf02e3902
commit 4ff0c483bf
4 changed files with 170 additions and 98 deletions

View file

@ -48,6 +48,7 @@ import Nix.Utils
class (Show v, Monad m) => MonadEval v m | v -> m where
freeVariable :: Text -> m v
evaledSym :: Text -> v -> m v
evalCurPos :: m v
evalConstant :: NAtom -> m v
@ -64,6 +65,22 @@ class (Show v, Monad m) => MonadEval v m | v -> m where
evalApp :: v -> m v -> m v
evalAbs :: Params Void -> (m v -> m v) -> m v
{-
evalSelect :: v -> NonEmpty Text -> Maybe (m v) -> m v
evalHasAttr :: v -> NonEmpty Text -> m v
-- | This and the following methods are intended to allow things like
-- adding provenance information.
evalListElem :: [m v] -> Int -> m v -> m v
evalList :: [t] -> m v
evalSetElem :: AttrSet (m v) -> Text -> m v -> m v
evalSet :: AttrSet t -> AttrSet SourcePos -> m v
evalRecSetElem :: AttrSet (m v) -> Text -> m v -> m v
evalRecSet :: AttrSet t -> AttrSet SourcePos -> m v
evalLetElem :: Text -> m v -> m v
evalLet :: m v -> m v
-}
evalError :: String -> m a
type MonadNixEval e v t m =
@ -87,7 +104,7 @@ eval :: forall e v t m. MonadNixEval e v t m => NExprF (m v) -> m v
eval (NSym "__curPos") = evalCurPos
eval (NSym var) =
maybe (freeVariable var) (force ?? pure) =<< lookupVar var
lookupVar var >>= maybe (freeVariable var) (force ?? evaledSym var)
eval (NConstant x) = evalConstant x
eval (NStr str) = uncurry evalString =<< assembleString str
@ -144,7 +161,7 @@ eval (NIf cond t f) = cond >>= \v -> evalIf v t f
eval (NWith scope body) = evalWith scope body
eval (NAssert cond body) = cond >>= \v -> evalAssert v body
eval (NAssert cond body) = cond >>= evalAssert ?? body
eval e@(NAbs params body) = do
-- It is the environment at the definition site, not the call site, that

View file

@ -44,6 +44,7 @@ import Data.List.Split
import Data.Maybe (mapMaybe)
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Void
import Nix.Atoms
import Nix.Context
import Nix.Convert
@ -92,30 +93,38 @@ instance MonadNix e m => MonadEval (NValue m) m where
nverr $ "Undefined variable '" ++ Text.unpack var ++ "'"
evalCurPos = do
SrcSpan delta _ <- currentPos
toValue delta
evalConstant c = do
scope <- currentScopes
span <- currentPos
pure $ nvConstantP (Provenance scope (NConstant_ span c)) c
SrcSpan delta _ <- currentPos
changeProvenance scope (\_ -> NSym_ span "__curPos") <$> toValue delta
evaledSym name val = do
span <- currentPos
pure $ provenanceContext (NSym_ span name) val
evalConstant c = do
scope <- currentScopes
span <- currentPos
pure $ nvConstantP (Provenance scope (NConstant_ span c) Nothing) c
evalString s d = do
scope <- currentScopes
span <- currentPos
-- jww (2018-04-22): Determine full provenance for the string?
pure $ nvStrP (Provenance scope (NStr_ span (DoubleQuoted [Plain s]))) s d
pure $ nvStrP (Provenance scope (NStr_ span (DoubleQuoted [Plain s]))
Nothing) s d
evalLiteralPath p = do
scope <- currentScopes
span <- currentPos
fmap (nvPathP (Provenance scope (NLiteralPath_ span p)))
fmap (nvPathP (Provenance scope (NLiteralPath_ span p) Nothing))
(makeAbsolutePath p)
evalEnvPath p = do
scope <- currentScopes
span <- currentPos
fmap (nvPathP (Provenance scope (NEnvPath_ span p))) (findEnvPath p)
fmap (nvPathP (Provenance scope (NEnvPath_ span p) Nothing))
(findEnvPath p)
evalUnary op arg = do
scope <- currentScopes
@ -128,31 +137,41 @@ instance MonadNix e m => MonadEval (NValue m) m where
execBinaryOp scope span op larg rarg
evalWith c b = do
_scope <- currentScopes @_ @(NThunk m)
_span <- currentPos
-- jww (2018-04-22): This one needs more work.
-- addProvenance scope (\b -> NWith_ span (Just c) (Just (pure b))) <$>
evalWithAttrSet c b
span <- currentPos
-- jww (2018-04-23): What about the arguments to with? All this
-- preserves right now is the location.
provenanceContext (NWith_ span Nothing Nothing)
<$> evalWithAttrSet c b
evalIf c t f = do
scope <- currentScopes
span <- currentPos
fromValue c >>= \b ->
if b
then addProvenance scope (\t -> NIf_ span (Just c) (Just t) Nothing) <$> t
else addProvenance scope (\f -> NIf_ span (Just c) Nothing (Just f)) <$> f
then changeProvenance scope
(\t -> NIf_ span (Just c) (Just t) Nothing) <$> t
else changeProvenance scope
(\f -> NIf_ span (Just c) Nothing (Just f)) <$> f
evalAssert c body = do
evalAssert c body = fromValue c >>= \b ->
if b
then do
scope <- currentScopes
span <- currentPos
changeProvenance scope
(\b -> NAssert_ span (Just c) (Just b)) <$> body
else nverr $ "assertion failed: " ++ show c
evalApp f x = do
span <- currentPos
provenanceContext (NBinary_ span NApp (Just f) Nothing)
<$> callFunc f x
evalAbs p b = do
scope <- currentScopes
span <- currentPos
fromValue c >>= \b ->
if b
then addProvenance scope (\b -> NAssert_ span (Just c) (Just b)) <$> body
else nverr $ "assertion failed, value provenance: "
++ show (provenance c)
evalApp = callFunc
evalAbs = (pure .) . nvClosure -- jww (2018-04-22): NYI
pure $ nvClosureP (Provenance scope (NAbs_ span (fmap absurd p) Nothing)
Nothing) p b
evalError = throwError
@ -168,9 +187,7 @@ callFunc fun arg = case fun of
s@(NVSet m _) | Just f <- M.lookup "__functor" m -> do
traceM "callFunc:__functor"
force f $ (`callFunc` pure s) >=> (`callFunc` arg)
x -> arg >>= \arg' ->
throwError $ "Attempt to call non-function '" ++ show x
++ "' with arg: " ++ show arg'
x -> throwError $ "Attempt to call non-function: " ++ show x
execUnaryOp :: (Framed e m, MonadVar m, MonadFile m)
=> Scopes m (NThunk m) -> SrcSpan -> NUnaryOp -> NValue m
@ -193,7 +210,8 @@ execUnaryOp scope span op arg = do
throwError $ "argument to unary operator"
++ " must evaluate to an atomic type: " ++ show x
where
unaryOp = pure . nvConstantP (Provenance scope (NUnary_ span op (Just arg)))
unaryOp = pure . nvConstantP
(Provenance scope (NUnary_ span op (Just arg)) Nothing)
execBinaryOp
:: forall e m. (MonadNix e m, MonadEval (NValue m) m)
@ -210,7 +228,8 @@ execBinaryOp scope span NOr larg rarg = fromNix larg >>= \l ->
else rarg >>= \rval -> fromNix @Bool rval >>= orOp (Just rval)
where
orOp r b = pure $
nvConstantP (Provenance scope (NBinary_ span NOr (Just larg) r)) (NBool b)
nvConstantP (Provenance scope (NBinary_ span NOr (Just larg) r) Nothing)
(NBool b)
execBinaryOp scope span NAnd larg rarg = fromNix larg >>= \l ->
if l
@ -218,14 +237,16 @@ execBinaryOp scope span NAnd larg rarg = fromNix larg >>= \l ->
else andOp Nothing False
where
andOp r b = pure $
nvConstantP (Provenance scope (NBinary_ span NAnd (Just larg) r)) (NBool b)
nvConstantP (Provenance scope (NBinary_ span NAnd (Just larg) r) Nothing)
(NBool b)
-- jww (2018-04-08): Refactor so that eval (NBinary ..) *always* dispatches
-- based on operator first
execBinaryOp scope span op lval rarg = do
rval <- rarg
let bin :: (Provenance m -> a) -> a
bin f = f (Provenance scope (NBinary_ span op (Just lval) (Just rval)))
bin f = f (Provenance scope (NBinary_ span op (Just lval) (Just rval))
Nothing)
toBool = pure . bin nvConstantP . NBool
case (lval, rval) of
(NVConstant lc, NVConstant rc) -> case (op, lc, rc) of

View file

@ -1,5 +1,6 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeSynonymInstances #-}
@ -142,66 +143,76 @@ prettyAtom :: NAtom -> NixDoc
prettyAtom atom = simpleExpr $ text $ unpack $ atomText atom
prettyNix :: NExpr -> Doc
prettyNix = withoutParens . cata phi where
phi :: NExprF NixDoc -> NixDoc
phi (NConstant atom) = prettyAtom atom
phi (NStr str) = simpleExpr $ prettyString str
phi (NList []) = simpleExpr $ lbracket <> rbracket
phi (NList xs) = simpleExpr $ group $
nest 2 (vsep $ lbracket : map (wrapParens appOpNonAssoc) xs) <$> rbracket
phi (NSet []) = simpleExpr $ lbrace <> rbrace
phi (NSet xs) = simpleExpr $ group $
nest 2 (vsep $ lbrace : map prettyBind xs) <$> rbrace
phi (NRecSet []) = simpleExpr $ recPrefix <> lbrace <> rbrace
phi (NRecSet xs) = simpleExpr $ group $
nest 2 (vsep $ recPrefix <> lbrace : map prettyBind xs) <$> rbrace
phi (NAbs args body) = leastPrecedence $
nest 2 ((prettyParams args <> colon) <$> withoutParens body)
phi (NBinary NApp fun arg)
= NixDoc (wrapParens appOp fun <+> wrapParens appOpNonAssoc arg) appOp
phi (NBinary op r1 r2) = flip NixDoc opInfo $ hsep
[ wrapParens (f NAssocLeft) r1
, text $ unpack $ operatorName opInfo
, wrapParens (f NAssocRight) r2
]
where
opInfo = getBinaryOperator op
f x
| associativity opInfo /= x = opInfo { associativity = NAssocNone }
| otherwise = opInfo
phi (NUnary op r1) =
NixDoc (text (unpack (operatorName opInfo)) <> wrapParens opInfo r1) opInfo
where opInfo = getUnaryOperator op
phi (NSelect r attr o) =
prettyNix = withoutParens . cata exprFNixDoc
prettyOriginExpr :: NExprLocF (Maybe (NValue m)) -> Doc
prettyOriginExpr = withoutParens . go
where
go = exprFNixDoc . annotated . getCompose . fmap render
render Nothing = simpleExpr $ text "_"
render (Just (NValue Nothing _)) = simpleExpr $ text "?"
render (Just (NValue (Just expr) _)) = go (originExpr expr)
exprFNixDoc :: NExprF NixDoc -> NixDoc
exprFNixDoc = \case
NConstant atom -> prettyAtom atom
NStr str -> simpleExpr $ prettyString str
NList [] -> simpleExpr $ lbracket <> rbracket
NList xs -> simpleExpr $ group $
nest 2 (vsep $ lbracket : map (wrapParens appOpNonAssoc) xs) <$> rbracket
NSet [] -> simpleExpr $ lbrace <> rbrace
NSet xs -> simpleExpr $ group $
nest 2 (vsep $ lbrace : map prettyBind xs) <$> rbrace
NRecSet [] -> simpleExpr $ recPrefix <> lbrace <> rbrace
NRecSet xs -> simpleExpr $ group $
nest 2 (vsep $ recPrefix <> lbrace : map prettyBind xs) <$> rbrace
NAbs args body -> leastPrecedence $
nest 2 ((prettyParams args <> colon) <$> withoutParens body)
NBinary NApp fun arg ->
NixDoc (wrapParens appOp fun <+> wrapParens appOpNonAssoc arg) appOp
NBinary op r1 r2 -> flip NixDoc opInfo $ hsep
[ wrapParens (f NAssocLeft) r1
, text $ unpack $ operatorName opInfo
, wrapParens (f NAssocRight) r2
]
where
opInfo = getBinaryOperator op
f x | associativity opInfo /= x = opInfo { associativity = NAssocNone }
| otherwise = opInfo
NUnary op r1 ->
NixDoc (text (unpack (operatorName opInfo)) <> wrapParens opInfo r1) opInfo
where opInfo = getUnaryOperator op
NSelect r attr o ->
(if isJust o then leastPrecedence else flip NixDoc selectOp) $
wrapParens selectOp r <> dot <> prettySelector attr <> ordoc
where ordoc = maybe empty (((space <> text "or") <+>) . wrapParens selectOp) o
phi (NHasAttr r attr)
= NixDoc (wrapParens hasAttrOp r <+> text "?" <+> prettySelector attr) hasAttrOp
phi (NEnvPath p) = simpleExpr $ text ("<" ++ p ++ ">")
phi (NLiteralPath p) = simpleExpr $ text $ case p of
"./" -> "./."
"../" -> "../."
".." -> "../."
txt | "/" `isPrefixOf` txt -> txt
| "~/" `isPrefixOf` txt -> txt
| "./" `isPrefixOf` txt -> txt
| "../" `isPrefixOf` txt -> txt
| otherwise -> "./" ++ txt
phi (NSym name) = simpleExpr $ text (unpack name)
phi (NLet binds body) = leastPrecedence $ group $ text "let" <$> indent 2 (
where ordoc = maybe empty (((space <> text "or") <+>) . wrapParens selectOp) o
NHasAttr r attr ->
NixDoc (wrapParens hasAttrOp r <+> text "?" <+> prettySelector attr) hasAttrOp
NEnvPath p -> simpleExpr $ text ("<" ++ p ++ ">")
NLiteralPath p -> simpleExpr $ text $ case p of
"./" -> "./."
"../" -> "../."
".." -> "../."
txt | "/" `isPrefixOf` txt -> txt
| "~/" `isPrefixOf` txt -> txt
| "./" `isPrefixOf` txt -> txt
| "../" `isPrefixOf` txt -> txt
| otherwise -> "./" ++ txt
NSym name -> simpleExpr $ text (unpack name)
NLet binds body -> leastPrecedence $ group $ text "let" <$> indent 2 (
vsep (map prettyBind binds)) <$> text "in" <+> withoutParens body
phi (NIf cond trueBody falseBody) = leastPrecedence $
group $ nest 2 $ (text "if" <+> withoutParens cond) <$>
( align (text "then" <+> withoutParens trueBody)
<$> align (text "else" <+> withoutParens falseBody)
)
phi (NWith scope body) = leastPrecedence $
text "with" <+> withoutParens scope <> semi <$> align (withoutParens body)
phi (NAssert cond body) = leastPrecedence $
text "assert" <+> withoutParens cond <> semi <$> align (withoutParens body)
recPrefix = text "rec" <> space
NIf cond trueBody falseBody -> leastPrecedence $
group $ nest 2 $ (text "if" <+> withoutParens cond) <$>
( align (text "then" <+> withoutParens trueBody)
<$> align (text "else" <+> withoutParens falseBody)
)
NWith scope body -> leastPrecedence $
text "with" <+> withoutParens scope <> semi <$> align (withoutParens body)
NAssert cond body -> leastPrecedence $
text "assert" <+> withoutParens cond <> semi <$> align (withoutParens body)
where
recPrefix = text "rec" <> space
prettyNixValue :: Functor m => NValueNF m -> Doc
prettyNixValue = prettyNix . valueToExpr
@ -237,15 +248,27 @@ removeEffects = Fix . fmap dethunk . baseValue
dethunk (NThunk (Value v)) = removeEffects v
dethunk (NThunk _) = Fix $ NVStrF "<thunk>" mempty
removeEffectsIO :: MonadVar m => NValue m -> m (NValueNF m)
removeEffectsIO = fmap Fix . traverse dethunk . baseValue
where
dethunk (NThunk (Value v)) = removeEffectsIO v
dethunk (NThunk (Thunk
#if ENABLE_TRACING
_
#endif
_ t)) = readVar t >>= \case
Computed v -> removeEffectsIO v
_ -> pure $ Fix $ NVStrF "<thunk>" mempty
instance Functor m => Show (NValueF m (NThunk m)) where
show = show . prettyNixValue . removeEffects . NValue Nothing
instance Functor m => Show (NValue m) where
show (NValue p v) = "(" ++ show v ++ " from " ++ show p ++ ")"
show (NValue Nothing v) = show v
show (NValue (Just p) v) =
-- jww (2018-04-23): Need to display the contextExpr as well.
show v ++ " (from: " ++ show (prettyOriginExpr (originExpr p)) ++ ")"
instance Functor m => Show (NThunk m) where
show (NThunk (Value v)) = show v
show (NThunk _) = "<thunk>"
instance Functor m => Show (Provenance m) where
show (Provenance _ (Compose (Ann _ expr))) = show expr

View file

@ -81,6 +81,11 @@ type ValueSet m = AttrSet (NThunk m)
data Provenance m = Provenance
{ lexicalScope :: Scopes m (NThunk m)
, originExpr :: NExprLocF (Maybe (NValue m))
, contextExpr :: Maybe (NExprLocF (Maybe (NValue m)))
-- ^ When calling the function x: x + 2 with argument x = 3, the
-- 'originExpr' for the resulting value will be 3 + 2, while the
-- 'contextExpr' will be @(x: x + 2) 3@, preserving not only the
-- result of the call, but what was called and with what arguments.
}
-- jww (2018-04-22): Tracking value provenance may need to be a compile-time
@ -90,10 +95,16 @@ data NValue m = NValue
, baseValue :: NValueF m (NThunk m)
}
addProvenance :: Scopes m (NThunk m)
-> (NValue m -> NExprLocF (Maybe (NValue m)))
-> NValue m -> NValue m
addProvenance s f l@(NValue _ v) = NValue (Just (Provenance s (f l))) v
changeProvenance :: Scopes m (NThunk m)
-> (NValue m -> NExprLocF (Maybe (NValue m)))
-> NValue m -> NValue m
changeProvenance s f l@(NValue _ v) =
NValue (Just (Provenance s (f l) Nothing)) v
provenanceContext :: NExprLocF (Maybe (NValue m))
-> NValue m -> NValue m
provenanceContext c (NValue p v) =
NValue (fmap (\x -> x { contextExpr = Just c }) p) v
pattern NVConstant x <- NValue _ (NVConstantF x)