From 01bcb86d3f60ee7e7f2cb9c4f2e20f29536cf7df Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 24 Apr 2018 11:12:20 -0700 Subject: [PATCH] Further improvements to error rendering, but not all done yet --- hnix.cabal | 3 +- main/Main.hs | 15 +++--- src/Nix.hs | 9 ++-- src/Nix/Convert.hs | 8 ++-- src/Nix/Eval.hs | 19 ++++---- src/Nix/Exec.hs | 15 ++++-- src/Nix/Frames.hs | 11 +++-- src/Nix/Normal.hs | 8 ++-- src/Nix/Pretty.hs | 2 + src/Nix/Render/Frame.hs | 98 +++++++++++++++++++++++++++++++++++++++ src/Nix/Thunk.hs | 7 ++- src/Nix/Utils.hs | 4 ++ src/Nix/Value.hs | 35 ++++++++------ tests/Main.hs | 6 ++- tests/NixLanguageTests.hs | 23 +++++---- 15 files changed, 200 insertions(+), 63 deletions(-) create mode 100644 src/Nix/Render/Frame.hs diff --git a/hnix.cabal b/hnix.cabal index e161d23..e4c8b60 100644 --- a/hnix.cabal +++ b/hnix.cabal @@ -2,7 +2,7 @@ -- -- see: https://github.com/sol/hpack -- --- hash: 469fd512cc13754d0cd6b72f0c771fc90d48e89283e3308e564335e6b31e5e39 +-- hash: 138c46bc62e1d1c2dd934c6ef0f57939f8d7926c09ea5e99d4591175823b5295 name: hnix version: 0.5.0 @@ -60,6 +60,7 @@ library Nix.Pretty Nix.Reduce Nix.Render + Nix.Render.Frame Nix.Scope Nix.Strings Nix.TH diff --git a/main/Main.hs b/main/Main.hs index 203d9e8..8ba5530 100644 --- a/main/Main.hs +++ b/main/Main.hs @@ -11,7 +11,8 @@ import qualified Control.Exception as Exc import Control.Monad import Control.Monad.Catch import Control.Monad.IO.Class --- import Control.Monad.ST +import Control.Monad.ST +import Control.Monad.Trans.Reader import qualified Data.Aeson.Encoding as A import qualified Data.Aeson.Text as A import Data.Functor.Compose @@ -21,7 +22,7 @@ import qualified Data.Text.Lazy.IO as TL import Nix import Nix.Convert import qualified Nix.Eval as Eval --- import Nix.Lint +import Nix.Lint import Nix.Utils import Options.Applicative hiding (ParserResult(..)) import qualified Repl @@ -62,12 +63,14 @@ main = do else errorWithoutStackTrace) $ "Parse failed: " ++ show err Success expr -> Exc.catch (process opts mpath expr) $ \case - NixException msg -> errorWithoutStackTrace "error" -- jww (2018-04-24): NYI msg + NixException frames -> + errorWithoutStackTrace . show + =<< runReaderT (renderFrames frames) opts process opts mpath expr = do - -- when (check opts) $ - -- putStrLn $ runST $ - -- runLintM opts . renderSymbolic =<< lint opts expr + when (check opts) $ + putStrLn $ runST $ + runLintM opts . renderSymbolic =<< lint opts expr let printer :: (MonadNix e m, MonadIO m) => NValue m -> m () printer | xml opts = diff --git a/src/Nix.hs b/src/Nix.hs index b96f91f..d12fd98 100644 --- a/src/Nix.hs +++ b/src/Nix.hs @@ -8,6 +8,7 @@ module Nix (module Nix.Cache, module Nix.Exec, module Nix.Expr, module Nix.Frames, + module Nix.Render.Frame, module Nix.Normal, module Nix.Options, module Nix.Parser, @@ -23,11 +24,9 @@ module Nix (module Nix.Cache, import Control.Applicative import Control.Arrow (second) import Control.Monad.Reader --- import Control.Monad.Trans.Class import Data.Fix import Data.Functor.Compose import qualified Data.HashMap.Lazy as M --- import Data.Monoid import qualified Data.Text as Text import qualified Data.Text.Read as Text import Nix.Builtins @@ -35,17 +34,15 @@ import Nix.Cache import qualified Nix.Eval as Eval import Nix.Exec import Nix.Expr --- import Nix.Expr.Shorthands --- import Nix.Expr.Types --- import Nix.Expr.Types.Annotated +import Nix.Frames import Nix.Normal import Nix.Options import Nix.Parser import Nix.Parser.Library (Result(..)) import Nix.Pretty import Nix.Reduce +import Nix.Render.Frame import Nix.Scope -import Nix.Frames import Nix.Thunk import Nix.Utils import Nix.Value diff --git a/src/Nix/Convert.hs b/src/Nix/Convert.hs index 9bb662a..3ce064f 100644 --- a/src/Nix/Convert.hs +++ b/src/Nix/Convert.hs @@ -441,8 +441,10 @@ instance Applicative m => ToValue Bool m (NExprF r) where instance Applicative m => ToValue () m (NExprF r) where toValue _ = pure . NConstant $ NNull -whileForcingThunk :: (Framed e m, Frame s) => s -> m r -> m r -whileForcingThunk frame = withFrame Debug ForcingThunk . withFrame Debug frame +whileForcingThunk :: forall s e m r. (Framed e m, Frame s, Typeable m) + => s -> m r -> m r +whileForcingThunk frame = + withFrame Debug (ForcingThunk @m) . withFrame Debug frame instance (Convertible e m, MonadThunk (NValue m) (NThunk m) m) => ToValue A.Value m (NValue m) where @@ -450,7 +452,7 @@ instance (Convertible e m, MonadThunk (NValue m) (NThunk m) m) A.Object m -> flip nvSet M.empty <$> traverse (thunk . toValue @_ @_ @(NValue m)) m A.Array l -> nvList <$> - traverse (\x -> thunk . whileForcingThunk (CoercionFromJson x) + traverse (\x -> thunk . whileForcingThunk (CoercionFromJson @m x) . toValue $ x) (V.toList l) A.String s -> pure $ nvStr s mempty A.Number n -> pure $ nvConstant $ case floatingOrInteger n of diff --git a/src/Nix/Eval.hs b/src/Nix/Eval.hs index fd900c4..510227b 100644 --- a/src/Nix/Eval.hs +++ b/src/Nix/Eval.hs @@ -81,7 +81,7 @@ class (Show v, Monad m) => MonadEval v m | v -> m where evalLet :: m v -> m v -} - evalError :: String -> m a + evalError :: Frame s => s -> m a type MonadNixEval e v t m = (MonadEval v m, Scoped e t m, MonadThunk v t m, MonadFix m, @@ -92,11 +92,12 @@ type MonadNixEval e v t m = ToValue (AttrSet t, AttrSet SourcePos) m v, FromValue (AttrSet t, AttrSet SourcePos) m v) -newtype ExprContext = ExprContext NExpr -newtype EvaluatingExpr = EvaluatingExpr NExprLoc +data EvalFrame + = ExprContext NExpr + | EvaluatingExpr NExprLoc + deriving (Show, Typeable) -instance Frame ExprContext -instance Frame EvaluatingExpr +instance Frame EvalFrame wrapExpr :: NExprF (m v) -> NExpr wrapExpr x = Fix (Fix (NSym "") <$ x) @@ -203,7 +204,8 @@ attrSetAlter :: forall e v t m. MonadNixEval e v t m -> AttrSet (m v) -> m v -> m (AttrSet (m v)) -attrSetAlter [] _ _ = evalError @v "invalid selector with no components" +attrSetAlter [] _ _ = + evalError @v ("invalid selector with no components" :: String) attrSetAlter (p:ps) m val = case M.lookup p m of Nothing | null ps -> go @@ -359,14 +361,15 @@ evalKeyNameStatic :: forall v m. MonadEval v m evalKeyNameStatic = \case StaticKey k p -> pure (k, p) DynamicKey _ -> - evalError @v "dynamic attribute not allowed in this context" + evalError @v ("dynamic attribute not allowed in this context" :: String) evalKeyNameDynamicNotNull :: forall v m. (MonadEval v m, FromValue (Text, DList Text) m v) => NKeyName (m v) -> m (Text, Maybe SourcePos) evalKeyNameDynamicNotNull = evalKeyNameDynamicNullable >=> \case (Nothing, _) -> - evalError @v "value is null while a string was expected" + -- jww (2018-04-24): This should be of Coercion ValueFrame type + evalError @v $ ("value is null while a string was expected" :: String) (Just k, p) -> pure (k, p) -- | Evaluate a component of an attribute path in a context where we are diff --git a/src/Nix/Exec.hs b/src/Nix/Exec.hs index c0539dd..fd53ac6 100644 --- a/src/Nix/Exec.hs +++ b/src/Nix/Exec.hs @@ -75,7 +75,12 @@ type MonadNix e m = (Scoped e (NThunk m) m, Framed e m, Typeable m, MonadVar m, MonadEffects m, MonadFix m, MonadCatch m, Alternative m) -nverr :: forall e m a. MonadNix e m => String -> m a +data ExecFrame m = Assertion (NValue m) + deriving (Show, Typeable) + +instance Typeable m => Frame (ExecFrame m) + +nverr :: forall s e m a. (MonadNix e m, Frame s) => s -> m a nverr = evalError @(NValue m) instance MonadNix e m => MonadThunk (NValue m) (NThunk m) m where @@ -162,7 +167,7 @@ instance MonadNix e m => MonadEval (NValue m) m where span <- currentPos changeProvenance scope (\b -> NAssert_ span (Just c) (Just b)) <$> body - else nverr $ "assertion failed: " ++ show c + else nverr $ Assertion c evalApp f x = do span <- currentPos @@ -258,8 +263,10 @@ execBinaryOp scope span op lval rarg = do (NLte, l, r) -> toBool $ l <= r (NGt, l, r) -> toBool $ l > r (NGte, l, r) -> toBool $ l >= r - (NAnd, _, _) -> nverr "should be impossible: && is handled above" - (NOr, _, _) -> nverr "should be impossible: || is handled above" + (NAnd, _, _) -> + nverr @String "should be impossible: && is handled above" + (NOr, _, _) -> + nverr @String "should be impossible: || is handled above" (NPlus, l, r) -> numBinOp bin (+) l r (NMinus, l, r) -> numBinOp bin (-) l r (NMult, l, r) -> numBinOp bin (*) l r diff --git a/src/Nix/Frames.hs b/src/Nix/Frames.hs index a9f5c04..e18a122 100644 --- a/src/Nix/Frames.hs +++ b/src/Nix/Frames.hs @@ -5,7 +5,7 @@ {-# LANGUAGE ScopedTypeVariables #-} module Nix.Frames (NixLevel(..), Frames, Framed, Frame(..), NixFrame(..), - NixException(..), withFrame, throwError, + NixException(..), SomeFrame(..), withFrame, throwError, module Data.Typeable, module Control.Exception) where @@ -21,13 +21,17 @@ data NixLevel = Fatal | Error | Warning | Info | Debug data SomeFrame = forall e. Frame e => SomeFrame e -class Typeable e => Frame e where +instance Show SomeFrame where + show (SomeFrame f) = show f + +class (Typeable e, Show e) => Frame e where toFrame :: e -> SomeFrame fromFrame :: SomeFrame -> Maybe e toFrame = SomeFrame fromFrame (SomeFrame e) = cast e +-- jww (2018-04-24): These two are temporary instance for now. instance Frame [Char] instance Frame Doc @@ -37,7 +41,8 @@ data NixFrame = NixFrame } instance Show NixFrame where - show (NixFrame level _) = "Nix frame at level " ++ show level + show (NixFrame level f) = + "Nix frame at level " ++ show level ++ ": "++ show f type Frames = [NixFrame] diff --git a/src/Nix/Normal.hs b/src/Nix/Normal.hs index 4a36274..444e144 100644 --- a/src/Nix/Normal.hs +++ b/src/Nix/Normal.hs @@ -25,6 +25,7 @@ import Nix.Utils import Nix.Value newtype NormalLoop m = NormalLoop (NValue m) + deriving Show instance Typeable m => Frame (NormalLoop m) @@ -70,7 +71,7 @@ embed (Fix x) = case x of NVPathF fp -> return $ nvPath fp NVBuiltinF name f -> return $ nvBuiltin name f -valueText :: forall e m. (Framed e m, MonadEffects m) +valueText :: forall e m. (Framed e m, MonadEffects m, Typeable m) => Bool -> NValueNF m -> m (Text, DList Text) valueText addPathsToStore = cata phi where @@ -94,7 +95,8 @@ valueText addPathsToStore = cata phi phi v@(NVBuiltinF _ _) = coercionFailed v coercionFailed v = - throwError $ Coercion (valueType v) TString + throwError $ Coercion @m (valueType v) TString -valueTextNoContext :: (Framed e m, MonadEffects m) => Bool -> NValueNF m -> m Text +valueTextNoContext :: (Framed e m, MonadEffects m, Typeable m) + => Bool -> NValueNF m -> m Text valueTextNoContext addPathsToStore = fmap fst . valueText addPathsToStore diff --git a/src/Nix/Pretty.hs b/src/Nix/Pretty.hs index 644466d..3d5a125 100644 --- a/src/Nix/Pretty.hs +++ b/src/Nix/Pretty.hs @@ -260,6 +260,7 @@ removeEffectsIO = fmap Fix . traverse dethunk . baseValue Computed v -> removeEffectsIO v _ -> pure $ Fix $ NVStrF "" mempty +{- instance Functor m => Show (NValueF m (NThunk m)) where show = show . prettyNixValue . removeEffects . NValue Nothing @@ -272,3 +273,4 @@ instance Functor m => Show (NValue m) where instance Functor m => Show (NThunk m) where show (NThunk (Value v)) = show v show (NThunk _) = "" +-} diff --git a/src/Nix/Render/Frame.hs b/src/Nix/Render/Frame.hs new file mode 100644 index 0000000..a5c80fc --- /dev/null +++ b/src/Nix/Render/Frame.hs @@ -0,0 +1,98 @@ +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} + +module Nix.Render.Frame where + +import Control.Monad.Reader +import Data.Fix +import Data.Functor.Compose +import Data.List (intercalate) +import Data.Typeable +import Nix.Eval +import Nix.Exec +import Nix.Expr +import Nix.Frames +import Nix.Normal +import Nix.Options +import Nix.Parser.Library +import Nix.Pretty +import Nix.Render +import Nix.Thunk +import Nix.Utils +import Nix.Value +import Text.PrettyPrint.ANSI.Leijen as P + +renderFrames :: (MonadReader e m, Has e Options, MonadFile m, Typeable m) + => Frames -> m Doc +renderFrames [] = pure mempty +renderFrames xs = fmap (foldr1 (P.<$>)) $ mapM renderFrame $ reverse xs + +renderFrame :: forall e m. (MonadReader e m, Has e Options, MonadFile m, Typeable m) + => NixFrame -> m Doc +renderFrame (NixFrame level f) + | Just (e :: EvalFrame) <- fromFrame f = renderEvalFrame level e + | Just (e :: ThunkLoop) <- fromFrame f = renderThunkLoop level e + | Just (e :: ValueFrame m) <- fromFrame f = renderValueFrame level e + | Just (_ :: NormalLoop m) <- fromFrame f = + pure $ text "<>" + | Just (e :: ExecFrame m) <- fromFrame f = renderExecFrame level e + | Just (e :: [Char]) <- fromFrame f = pure $ text e + | Just (e :: Doc) <- fromFrame f = pure e + | otherwise = error $ "Unrecognized frame: " ++ show f + +renderEvalFrame :: (MonadReader e m, Has e Options, MonadFile m) + => NixLevel -> EvalFrame -> m Doc +renderEvalFrame _level = \case + ExprContext e -> + pure $ text "While forcing thunk for: " prettyNix e + + EvaluatingExpr e@(Fix (Compose (Ann ann x))) -> do + opts :: Options <- asks (view hasLens) + let rendered = show $ prettyNix $ + if verbose opts >= Chatty + then stripAnnotation e + else Fix (Fix (NSym "") <$ x) + msg = if verbose opts >= Chatty + then "While evaluating:\n>>>>>>>>\n" + ++ intercalate " \n" (lines rendered) + ++ "\n<<<<<<<<" + else "Expression: " ++ rendered + renderLocation ann (text msg) + +renderValueFrame :: (MonadReader e m, Has e Options, MonadFile m) + => NixLevel -> ValueFrame m -> m Doc +renderValueFrame level = \case + ForcingThunk -> pure $ text "ForcingThunk" + ConcerningValue _v -> pure $ text "ConcerningValue" + + Coercion x y -> + pure $ text desc <> text (describeValue x) + <> text " to " <> text (describeValue y) + where + desc | level <= Error = "Cannot coerce " + | otherwise = "While coercing " + + CoercionToJsonNF _v -> pure $ text "CoercionToJsonNF" + CoercionFromJson _j -> pure $ text "CoercionFromJson" + ExpectationNF _t _v -> pure $ text "ExpectationNF" + Expectation _t _v -> pure $ text "Expectation" + +renderExecFrame :: (MonadReader e m, Has e Options, MonadFile m) + => NixLevel -> ExecFrame m -> m Doc +renderExecFrame _level = \case + Assertion v -> + -- jww (2018-04-24): Render values nicely based on the verbosity. + pure $ text $ "Assertion failed: " ++ show v + +renderThunkLoop :: (MonadReader e m, Has e Options, MonadFile m) + => NixLevel -> ThunkLoop -> m Doc +renderThunkLoop _level = \case + ThunkLoop Nothing -> pure $ text "<>" + ThunkLoop (Just n) -> + pure $ text $ "<>" diff --git a/src/Nix/Thunk.hs b/src/Nix/Thunk.hs index 6021390..8f649ce 100644 --- a/src/Nix/Thunk.hs +++ b/src/Nix/Thunk.hs @@ -27,9 +27,7 @@ counter = unsafePerformIO $ newIORef 0 {-# NOINLINE counter #-} #endif -data Deferred m v - = Deferred (m v) - | Computed v +data Deferred m v = Deferred (m v) | Computed v deriving (Functor, Foldable, Traversable) class Monad m => MonadVar m where @@ -39,7 +37,7 @@ class Monad m => MonadVar m where writeVar :: Var m a -> a -> m () atomicModifyVar :: Var m a -> (a -> (a, b)) -> m b -class Monad m => MonadThunk v t m | v -> m, v -> t, t -> v where +class Monad m => MonadThunk v t m | v -> m, v -> t, t -> m, t -> v where thunk :: m v -> m t force :: t -> (v -> m r) -> m r value :: v -> t @@ -53,6 +51,7 @@ data Thunk m v (Var m Bool) (Var m (Deferred m v)) newtype ThunkLoop = ThunkLoop (Maybe Int) + deriving (Show, Typeable) instance Frame ThunkLoop diff --git a/src/Nix/Utils.hs b/src/Nix/Utils.hs index a72e403..0b3c5b1 100644 --- a/src/Nix/Utils.hs +++ b/src/Nix/Utils.hs @@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} @@ -104,6 +105,9 @@ over l f = runIdentity . l (Identity . f) class Has a b where hasLens :: MonoLens a b +instance Has a a where + hasLens f = f + toEncodingSorted :: A.Value -> A.Encoding toEncodingSorted = \case A.Object m -> diff --git a/src/Nix/Value.hs b/src/Nix/Value.hs index c48a123..21420e0 100644 --- a/src/Nix/Value.hs +++ b/src/Nix/Value.hs @@ -279,18 +279,25 @@ describeValue = \case TPath -> "a path" TBuiltin -> "a builtin function" -data ForcingThunk = ForcingThunk -data ConcerningValue m = ConcerningValue (NValue m) -data Coercion = Coercion ValueType ValueType -data CoercionToJsonNF m = CoercionToJsonNF (NValueNF m) -data CoercionFromJson = CoercionFromJson A.Value -data ExpectationNF m = ExpectationNF ValueType (NValueNF m) -data Expectation m = Expectation ValueType (NValue m) +instance Show (NValueF m (NThunk m)) where + show = show . describeValue . valueType -instance Frame ForcingThunk -instance Typeable m => Frame (ConcerningValue m) -instance Frame Coercion -instance Typeable m => Frame (CoercionToJsonNF m) -instance Frame CoercionFromJson -instance Typeable m => Frame (ExpectationNF m) -instance Typeable m => Frame (Expectation m) +instance Show (NValue m) where + show (NValue Nothing v) = show v + show (NValue (Just _) v) = show v + +instance Show (NThunk m) where + show (NThunk (Value v)) = show v + show (NThunk _) = "" + +data ValueFrame m + = ForcingThunk + | ConcerningValue (NValue m) + | Coercion ValueType ValueType + | CoercionToJsonNF (NValueNF m) + | CoercionFromJson A.Value + | ExpectationNF ValueType (NValueNF m) + | Expectation ValueType (NValue m) + deriving (Show, Typeable) + +instance Typeable m => Frame (ValueFrame m) diff --git a/tests/Main.hs b/tests/Main.hs index e57ee2e..459ecc1 100644 --- a/tests/Main.hs +++ b/tests/Main.hs @@ -7,6 +7,7 @@ module Main where import Control.DeepSeq import qualified Control.Exception as Exc import Control.Monad +import Control.Monad.Trans.Reader import Data.Fix import Data.List (isInfixOf) import Data.Maybe (isJust) @@ -19,6 +20,7 @@ import Nix.Expr.Types import Nix.Frames import Nix.Options import Nix.Parser +import Nix.Render.Frame import Nix.Value import qualified NixLanguageTests import qualified ParserTests @@ -73,7 +75,9 @@ ensureNixpkgsCanParse = Failure err -> errorWithoutStackTrace $ "Parsing " ++ path ++ " failed: " ++ show err Success expr -> Exc.catch (k expr) $ \case - NixException msg -> errorWithoutStackTrace "error! NYI!" -- jww (2018-04-24): msg + NixException frames -> + errorWithoutStackTrace . show + =<< runReaderT (renderFrames frames) defaultOptions main :: IO () main = do diff --git a/tests/NixLanguageTests.hs b/tests/NixLanguageTests.hs index d78227b..39e019d 100644 --- a/tests/NixLanguageTests.hs +++ b/tests/NixLanguageTests.hs @@ -8,6 +8,7 @@ import Control.Arrow ((&&&)) import Control.Exception import Control.Monad import Control.Monad.ST +import Control.Monad.Trans.Reader import Data.List (delete, sort) import Data.List.Split (splitOn) import Data.Map (Map) @@ -20,6 +21,7 @@ import Nix.Lint import Nix.Options import Nix.Parser import Nix.Pretty +import Nix.Render.Frame import Nix.Utils import Nix.XML import qualified Options.Applicative as Opts @@ -70,8 +72,8 @@ genTests = do testCase (takeFileName basename) $ case kind of ["parse", "okay"] -> assertParse defaultOptions $ the files ["parse", "fail"] -> assertParseFail defaultOptions $ the files - ["eval", "okay"] -> assertEval files - ["eval", "fail"] -> assertEvalFail $ the files + ["eval", "okay"] -> assertEval defaultOptions files + ["eval", "fail"] -> assertEvalFail $ the files _ -> error $ "Unexpected: " ++ show kind assertParse :: Options -> FilePath -> Assertion @@ -104,10 +106,11 @@ assertLangOkXml opts file = do expected <- Text.readFile $ file ++ ".exp.xml" assertEqual "" expected $ Text.pack actual -assertEval :: [FilePath] -> Assertion -assertEval files = catch go $ \case - NixException frames -> error $ "Evaluation error: NYI rendering NYI" - -- NixException frames -> error $ "Evaluation error: " ++ str +assertEval :: Options -> [FilePath] -> Assertion +assertEval opts files = catch go $ \case + NixException frames -> do + msg <- runReaderT (renderFrames frames) opts + error $ "Evaluation error: " ++ show msg where go = case delete ".nix" $ sort $ map takeExtensions files of [] -> assertLangOkXml defaultOptions name @@ -123,11 +126,11 @@ assertEval files = catch go $ \case Opts.Failure err -> errorWithoutStackTrace $ "Error parsing flags from " ++ name ++ ".flags: " ++ show err - Opts.Success opts -> + Opts.Success opts' -> assertLangOk - (opts { include = include opts ++ - [ "nix=../../../../data/nix/corepkgs" - , "lang/dir4" ] }) + (opts' { include = include opts' ++ + [ "nix=../../../../data/nix/corepkgs" + , "lang/dir4" ] }) name Opts.CompletionInvoked _ -> error "unused" _ -> assertFailure $ "Unknown test type " ++ show files