about summary refs log tree commit diff
path: root/src/libexpr
diff options
context:
space:
mode:
authorEelco Dolstra <e.dolstra@tudelft.nl>2004-04-05T22·27+0000
committerEelco Dolstra <e.dolstra@tudelft.nl>2004-04-05T22·27+0000
commit59b94ee18ac0cba5c7b261ee72550a4d3db0acb5 (patch)
tree9dbe6721699439cda3ce68ac86acbe38e9839e66 /src/libexpr
parenta520b1cbc3327dfb8e3c6f503dfd0bd41e0a6d55 (diff)
* When something goes wrong in the evaluation of a Nix expression,
  print a nice backtrace of the stack, rather than vomiting a gigantic
  (and useless) aterm on the screen.  Example:

    error: while evaluating file `.../pkgs/system/test.nix':
    while evaluating attribute `subversion' at `.../pkgs/system/all-packages-generic.nix', line 533:
    while evaluating function at `.../pkgs/applications/version-management/subversion/default.nix', line 1:
    assertion failed at `.../pkgs/applications/version-management/subversion/default.nix', line 13

  Since the Nix expression language is lazy, the trace may be
  misleading.  The purpose is to provide a hint as to the location of
  the problem.    
  

Diffstat (limited to 'src/libexpr')
-rw-r--r--src/libexpr/eval.cc84
-rw-r--r--src/libexpr/nixexpr.cc74
-rw-r--r--src/libexpr/nixexpr.hh6
-rw-r--r--src/libexpr/parser.cc27
-rw-r--r--src/libexpr/parser.y19
-rw-r--r--src/libexpr/primops.cc38
6 files changed, 170 insertions, 78 deletions
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 5ae4d6de8edc..af0ab913cae1 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -77,16 +77,16 @@ static Expr substArgs(Expr body, ATermList formals, Expr arg)
         Expr key = *i;
         Expr cur = subs.get(key);
         if (!cur)
-            throw badTerm(format("function has no formal argument `%1%'")
-                % aterm2String(key), arg);
+            throw Error(format("unexpected function argument `%1%'")
+                % aterm2String(key));
         subs.set(key, args.get(key));
     }
 
     /* Check that all arguments are defined. */
     for (ATermIterator i(subs.keys()); i; ++i)
         if (subs.get(*i) == undefined)
-            throw badTerm(format("formal argument `%1%' missing")
-                % aterm2String(*i), arg);
+            throw Error(format("required function argument `%1%' missing")
+                % aterm2String(*i));
     
     return substitute(subs, body);
 }
@@ -119,16 +119,18 @@ ATerm expandRec(ATerm e, ATermList rbnds, ATermList nrbnds)
     /* Create the non-recursive set. */
     ATermMap as;
     for (ATermIterator i(rbnds); i; ++i) {
-        if (!(atMatch(m, *i) >> "Bind" >> name >> e2))
+        ATerm pos;
+        if (!(atMatch(m, *i) >> "Bind" >> name >> e2 >> pos))
             abort(); /* can't happen */
-        as.set(name, substitute(subs, e2));
+        as.set(name, ATmake("(<term>, <term>)", substitute(subs, e2), pos));
     }
 
     /* Copy the non-recursive bindings.  !!! inefficient */
     for (ATermIterator i(nrbnds); i; ++i) {
-        if (!(atMatch(m, *i) >> "Bind" >> name >> e2))
+        ATerm pos;
+        if (!(atMatch(m, *i) >> "Bind" >> name >> e2 >> pos))
             abort(); /* can't happen */
-        as.set(name, e2);
+        as.set(name, ATmake("(<term>, <term>)", e2, pos));
     }
 
     return makeAttrs(as);
@@ -140,8 +142,8 @@ static Expr updateAttrs(Expr e1, Expr e2)
     /* Note: e1 and e2 should be in normal form. */
 
     ATermMap attrs;
-    queryAllAttrs(e1, attrs);
-    queryAllAttrs(e2, attrs);
+    queryAllAttrs(e1, attrs, true);
+    queryAllAttrs(e2, attrs, true);
 
     return makeAttrs(attrs);
 }
@@ -153,7 +155,7 @@ string evalString(EvalState & state, Expr e)
     ATMatcher m;
     string s;
     if (!(atMatch(m, e) >> "Str" >> s))
-        throw badTerm("string expected", e);
+        throw Error("string expected");
     return s;
 }
 
@@ -164,7 +166,7 @@ Path evalPath(EvalState & state, Expr e)
     ATMatcher m;
     string s;
     if (!(atMatch(m, e) >> "Path" >> s))
-        throw badTerm("path expected", e);
+        throw Error("path expected");
     return s;
 }
 
@@ -175,7 +177,7 @@ bool evalBool(EvalState & state, Expr e)
     ATMatcher m;
     if (atMatch(m, e) >> "Bool" >> "True") return true;
     else if (atMatch(m, e) >> "Bool" >> "False") return false;
-    else throw badTerm("expecting a boolean", e);
+    else throw Error("boolean expected");
 }
 
 
@@ -183,7 +185,7 @@ Expr evalExpr2(EvalState & state, Expr e)
 {
     ATMatcher m;
     Expr e1, e2, e3, e4;
-    ATerm name;
+    ATerm name, pos;
 
     /* Normal forms. */
     string cons;
@@ -219,6 +221,7 @@ Expr evalExpr2(EvalState & state, Expr e)
     if (atMatch(m, e) >> "Call" >> e1 >> e2) {
 
         ATermList formals;
+        ATerm pos;
         
         /* Evaluate the left-hand side. */
         e1 = evalExpr(state, e1);
@@ -229,25 +232,42 @@ Expr evalExpr2(EvalState & state, Expr e)
             if (primOp) return primOp(state, e2); else abort();
         }
 
-        else if (atMatch(m, e1) >> "Function" >> formals >> e4)
-            return evalExpr(state, 
-                substArgs(e4, formals, evalExpr(state, e2)));
+        else if (atMatch(m, e1) >> "Function" >> formals >> e4 >> pos) {
+            e2 = evalExpr(state, e2);
+            try {
+                return evalExpr(state, substArgs(e4, formals, e2));
+            } catch (Error & e) {
+                throw Error(format("while evaluating function at %1%:\n%2%")
+                    % showPos(pos) % e.msg());
+            }
+        }
         
-        else if (atMatch(m, e1) >> "Function1" >> name >> e4) {
-            ATermMap subs;
-            subs.set(name, e2);
-            return evalExpr(state, substitute(subs, e4));
+        else if (atMatch(m, e1) >> "Function1" >> name >> e4 >> pos) {
+            try {
+                ATermMap subs;
+                subs.set(name, e2);
+                return evalExpr(state, substitute(subs, e4));
+            } catch (Error & e) {
+                throw Error(format("while evaluating function at %1%:\n%2%")
+                    % showPos(pos) % e.msg());
+            }
         }
         
-        else throw badTerm("expecting a function or primop", e1);
+        else throw Error("function or primop expected in function call");
     }
 
     /* Attribute selection. */
     string s1;
     if (atMatch(m, e) >> "Select" >> e1 >> s1) {
-        Expr a = queryAttr(evalExpr(state, e1), s1);
-        if (!a) throw badTerm(format("missing attribute `%1%'") % s1, e);
-        return evalExpr(state, a);
+        ATerm pos;
+        Expr a = queryAttr(evalExpr(state, e1), s1, pos);
+        if (!a) throw Error(format("attribute `%1%' missing") % s1);
+        try {
+            return evalExpr(state, a);
+        } catch (Error & e) {
+            throw Error(format("while evaluating attribute `%1%' at %2%:\n%3%")
+                % s1 % showPos(pos) % e.msg());
+        }
     }
 
     /* Mutually recursive sets. */
@@ -264,8 +284,9 @@ Expr evalExpr2(EvalState & state, Expr e)
     }
 
     /* Assertions. */
-    if (atMatch(m, e) >> "Assert" >> e1 >> e2) {
-        if (!evalBool(state, e1)) throw badTerm("guard failed", e);
+    if (atMatch(m, e) >> "Assert" >> e1 >> e2 >> pos) {
+        if (!evalBool(state, e1))
+            throw Error(format("assertion failed at %1%") % showPos(pos));
         return evalExpr(state, e2);
     }
 
@@ -323,7 +344,7 @@ Expr evalExpr(EvalState & state, Expr e)
     Expr nf = state.normalForms.get(e);
     if (nf) {
         if (nf == state.blackHole)
-            throw badTerm("infinite recursion", e);
+            throw Error("infinite recursion encountered");
         state.nrCached++;
         return nf;
     }
@@ -340,7 +361,12 @@ Expr evalFile(EvalState & state, const Path & path)
 {
     startNest(nest, lvlTalkative, format("evaluating file `%1%'") % path);
     Expr e = parseExprFromFile(state, path);
-    return evalExpr(state, e);
+    try {
+        return evalExpr(state, e);
+    } catch (Error & e) {
+        throw Error(format("while evaluating file `%1%':\n%2%")
+            % path % e.msg());
+    }
 }
 
 
diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc
index 2736daf32301..dec734e46647 100644
--- a/src/libexpr/nixexpr.cc
+++ b/src/libexpr/nixexpr.cc
@@ -104,6 +104,19 @@ string aterm2String(ATerm t)
 {
     return ATgetName(ATgetAFun(t));
 }
+
+
+string showPos(ATerm pos)
+{
+    ATMatcher m;
+    Path path;
+    int line, column;
+    if (atMatch(m, pos) >> "NoPos")
+        return "undefined position";
+    if (!(atMatch(m, pos) >> "Pos" >> path >> line >> column))
+        throw badTerm("position expected", pos);
+    return (format("`%1%', line %2%") % path % line).str();
+}
     
 
 ATerm bottomupRewrite(TermFun & f, ATerm e)
@@ -135,37 +148,66 @@ ATerm bottomupRewrite(TermFun & f, ATerm e)
 }
 
 
-void queryAllAttrs(Expr e, ATermMap & attrs)
+void queryAllAttrs(Expr e, ATermMap & attrs, bool withPos)
 {
     ATMatcher m;
     ATermList bnds;
     if (!(atMatch(m, e) >> "Attrs" >> bnds))
-        throw badTerm("expected attribute set", e);
+        throw Error("attribute set expected");
 
     for (ATermIterator i(bnds); i; ++i) {
         string s;
         Expr e;
-        if (!(atMatch(m, *i) >> "Bind" >> s >> e))
+        ATerm pos;
+        if (!(atMatch(m, *i) >> "Bind" >> s >> e >> pos))
             abort(); /* can't happen */
-        attrs.set(s, e);
+        attrs.set(s, withPos ? ATmake("(<term>, <term>)", e, pos) : e);
     }
 }
 
 
 Expr queryAttr(Expr e, const string & name)
 {
-    ATermMap attrs;
-    queryAllAttrs(e, attrs);
-    return attrs.get(name);
+    ATerm dummy;
+    return queryAttr(e, name, dummy);
+}
+
+
+Expr queryAttr(Expr e, const string & name, ATerm & pos)
+{
+    ATMatcher m;
+    ATermList bnds;
+    if (!(atMatch(m, e) >> "Attrs" >> bnds))
+        throw Error("attribute set expected");
+
+    for (ATermIterator i(bnds); i; ++i) {
+        string s;
+        Expr e;
+        ATerm pos2;
+        if (!(atMatch(m, *i) >> "Bind" >> s >> e >> pos2))
+            abort(); /* can't happen */
+        if (s == name) {
+            pos = pos2;
+            return e;
+        }
+    }
+
+    return 0;
 }
 
 
 Expr makeAttrs(const ATermMap & attrs)
 {
+    ATMatcher m;
     ATermList bnds = ATempty;
-    for (ATermIterator i(attrs.keys()); i; ++i)
+    for (ATermIterator i(attrs.keys()); i; ++i) {
+        Expr e;
+        ATerm pos;
+        if (!(atMatch(m, attrs.get(*i)) >> "" >> e >> pos))
+            abort(); /* can't happen */
         bnds = ATinsert(bnds, 
-            ATmake("Bind(<term>, <term>)", *i, attrs.get(*i)));
+            ATmake("Bind(<term>, <term>, <term>)", *i, e, pos));
+    }
     return ATmake("Attrs(<term>)", ATreverse(bnds));
 }
 
@@ -175,7 +217,7 @@ Expr substitute(const ATermMap & subs, Expr e)
     checkInterrupt();
 
     ATMatcher m;
-    ATerm name;
+    ATerm name, pos;
 
     /* As an optimisation, don't substitute in subterms known to be
        closed. */
@@ -190,7 +232,7 @@ Expr substitute(const ATermMap & subs, Expr e)
        function. */
     ATermList formals;
     ATerm body;
-    if (atMatch(m, e) >> "Function" >> formals >> body) {
+    if (atMatch(m, e) >> "Function" >> formals >> body >> pos) {
         ATermMap subs2(subs);
         for (ATermIterator i(formals); i; ++i) {
             if (!(atMatch(m, *i) >> "NoDefFormal" >> name) &&
@@ -198,16 +240,16 @@ Expr substitute(const ATermMap & subs, Expr e)
                 abort();
             subs2.remove(name);
         }
-        return ATmake("Function(<term>, <term>)",
+        return ATmake("Function(<term>, <term>, <term>)",
             substitute(subs, (ATerm) formals),
-            substitute(subs2, body));
+            substitute(subs2, body), pos);
     }
 
-    if (atMatch(m, e) >> "Function1" >> name >> body) {
+    if (atMatch(m, e) >> "Function1" >> name >> body >> pos) {
         ATermMap subs2(subs);
         subs2.remove(name);
-        return ATmake("Function1(<term>, <term>)", name,
-            substitute(subs2, body));
+        return ATmake("Function1(<term>, <term>, <term>)", name,
+            substitute(subs2, body), pos);
     }
         
     /* Idem for a mutually recursive attribute set. */
diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh
index 924f8b912f77..ecd2534088f6 100644
--- a/src/libexpr/nixexpr.hh
+++ b/src/libexpr/nixexpr.hh
@@ -52,6 +52,9 @@ private:
 ATerm string2ATerm(const string & s);
 string aterm2String(ATerm t);
 
+/* Show a position. */
+string showPos(ATerm pos);
+
 /* Generic bottomup traversal over ATerms.  The traversal first
    recursively descends into subterms, and then applies the given term
    function to the resulting term. */
@@ -63,11 +66,12 @@ ATerm bottomupRewrite(TermFun & f, ATerm e);
 
 /* Query all attributes in an attribute set expression.  The
    expression must be in normal form. */
-void queryAllAttrs(Expr e, ATermMap & attrs);
+void queryAllAttrs(Expr e, ATermMap & attrs, bool withPos = false);
 
 /* Query a specific attribute from an attribute set expression.  The
    expression must be in normal form. */
 Expr queryAttr(Expr e, const string & name);
+Expr queryAttr(Expr e, const string & name, ATerm & pos);
 
 /* Create an attribute set expression from an Attrs value. */
 Expr makeAttrs(const ATermMap & attrs);
diff --git a/src/libexpr/parser.cc b/src/libexpr/parser.cc
index c300a0d07bc4..2e561c95ea29 100644
--- a/src/libexpr/parser.cc
+++ b/src/libexpr/parser.cc
@@ -12,8 +12,8 @@
 struct ParseData 
 {
     Expr result;
-    string basePath;
-    string location;
+    Path basePath;
+    Path path;
     string error;
 };
 
@@ -39,8 +39,8 @@ ATerm absParsedPath(ParseData * data, ATerm t)
     
 void parseError(ParseData * data, char * error, int line, int column)
 {
-    data->error = (format("%1%, at line %2%, column %3%, of %4%")
-        % error % line % column % data->location).str();
+    data->error = (format("%1%, at `%2%':%3%:%4%")
+        % error % data->path % line % column).str();
 }
         
 ATerm fixAttrs(int recursive, ATermList as)
@@ -51,13 +51,15 @@ ATerm fixAttrs(int recursive, ATermList as)
     for (ATermIterator i(as); i; ++i) {
         ATermList names;
         Expr src;
-        if (atMatch(m, *i) >> "Inherit" >> src >> names) {
+        ATerm pos;
+        if (atMatch(m, *i) >> "Inherit" >> src >> names >> pos) {
             bool fromScope = atMatch(m, src) >> "Scope";
             for (ATermIterator j(names); j; ++j) {
                 Expr rhs = fromScope
                     ? ATmake("Var(<term>)", *j)
                     : ATmake("Select(<term>, <term>)", src, *j);
-                *is = ATinsert(*is, ATmake("Bind(<term>, <term>)", *j, rhs));
+                *is = ATinsert(*is, ATmake("Bind(<term>, <term>, <term>)",
+                                   *j, rhs, pos));
             }
         } else bs = ATinsert(bs, *i);
     }
@@ -67,18 +69,23 @@ ATerm fixAttrs(int recursive, ATermList as)
         return ATmake("Attrs(<term>)", bs);
 }
 
+const char * getPath(ParseData * data)
+{
+    return data->path.c_str();
+}
+
 int yyparse(yyscan_t scanner, ParseData * data);
 }
 
 
 static Expr parse(EvalState & state,
-    const char * text, const string & location,
+    const char * text, const Path & path,
     const Path & basePath)
 {
     yyscan_t scanner;
     ParseData data;
     data.basePath = basePath;
-    data.location = location;
+    data.path = path;
 
     yylex_init(&scanner);
     yy_scan_string(text, scanner);
@@ -90,7 +97,7 @@ static Expr parse(EvalState & state,
     try {
         checkVarDefs(state.primOpsAll, data.result);
     } catch (Error & e) {
-        throw Error(format("%1%, in %2%") % e.msg() % location);
+        throw Error(format("%1%, in `%2%'") % e.msg() % path);
     }
 
     return data.result;
@@ -133,7 +140,7 @@ Expr parseExprFromFile(EvalState & state, Path path)
     readFull(fd, (unsigned char *) text, st.st_size);
     text[st.st_size] = 0;
 
-    return parse(state, text, "`" + path + "'", dirOf(path));
+    return parse(state, text, path, dirOf(path));
 }
 
 
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index cfcbc589f61c..347516f69239 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -19,11 +19,20 @@ void setParseResult(void * data, ATerm t);
 void parseError(void * data, char * error, int line, int column);
 ATerm absParsedPath(void * data, ATerm t);
 ATerm fixAttrs(int recursive, ATermList as);
+const char * getPath(void * data);
 
 void yyerror(YYLTYPE * loc, yyscan_t scanner, void * data, char * s)
 {
     parseError(data, s, loc->first_line, loc->first_column);
 }
+
+ATerm makePos(YYLTYPE * loc, void * data)
+{
+    return ATmake("Pos(<str>, <int>, <int>)",
+        getPath(data), loc->first_line, loc->first_column);
+}
+
+#define CUR_POS makePos(yylocp, data)
  
 %}
 
@@ -55,15 +64,15 @@ expr: expr_function;
 
 expr_function
   : '{' formals '}' ':' expr_function
-    { $$ = ATmake("Function(<term>, <term>)", $2, $5); }
+    { $$ = ATmake("Function(<term>, <term>, <term>)", $2, $5, CUR_POS); }
   | ID ':' expr_function
-    { $$ = ATmake("Function1(<term>, <term>)", $1, $3); }
+    { $$ = ATmake("Function1(<term>, <term>, <term>)", $1, $3, CUR_POS); }
   | expr_assert
   ;
 
 expr_assert
   : ASSERT expr ';' expr_assert
-    { $$ = ATmake("Assert(<term>, <term>)", $2, $4); }
+    { $$ = ATmake("Assert(<term>, <term>, <term>)", $2, $4, CUR_POS); }
   | expr_if
   ;
 
@@ -123,9 +132,9 @@ binds
 
 bind
   : ID '=' expr ';'
-    { $$ = ATmake("Bind(<term>, <term>)", $1, $3); }
+    { $$ = ATmake("Bind(<term>, <term>, <term>)", $1, $3, CUR_POS); }
   | INHERIT inheritsrc ids ';'
-    { $$ = ATmake("Inherit(<term>, <term>)", $2, $3); }
+    { $$ = ATmake("Inherit(<term>, <term>, <term>)", $2, $3, CUR_POS); }
   ;
 
 inheritsrc
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 835a3217f3b0..b0a0a276152f 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -8,7 +8,7 @@ Expr primImport(EvalState & state, Expr arg)
     ATMatcher m;
     string path;
     if (!(atMatch(m, arg) >> "Path" >> path))
-        throw badTerm("path expected", arg);
+        throw Error("path expected");
     return evalFile(state, path);
 }
 
@@ -102,18 +102,18 @@ static void processBinding(EvalState & state, Expr e, StoreExpr & ne,
         Expr a = queryAttr(e, "type");
         if (a && evalString(state, a) == "derivation") {
             a = queryAttr(e, "drvPath");
-            if (!a) throw badTerm("derivation name missing", e);
+            if (!a) throw Error("derivation name missing");
             Path drvPath = evalPath(state, a);
 
             a = queryAttr(e, "drvHash");
-            if (!a) throw badTerm("derivation hash missing", e);
+            if (!a) throw Error("derivation hash missing");
             Hash drvHash = parseHash(evalString(state, a));
 
             state.drvHashes[drvPath] = drvHash;
             
             ss.push_back(addInput(state, drvPath, ne));
         } else
-            throw badTerm("invalid derivation binding", e);
+            throw Error("invalid derivation attribute");
     }
 
     else if (atMatch(m, e) >> "Path" >> s) {
@@ -142,7 +142,7 @@ static void processBinding(EvalState & state, Expr e, StoreExpr & ne,
         ss.push_back(canonPath(ss2.front() + "/" + s));
     }
     
-    else throw badTerm("invalid derivation binding", e);
+    else throw Error("invalid derivation attribute");
 }
 
 
@@ -164,7 +164,7 @@ Expr primDerivation(EvalState & state, Expr args)
 
     ATermMap attrs;
     args = evalExpr(state, args);
-    queryAllAttrs(args, attrs);
+    queryAllAttrs(args, attrs, true);
 
     /* Build the derivation expression by processing the attributes. */
     StoreExpr ne;
@@ -177,15 +177,19 @@ Expr primDerivation(EvalState & state, Expr args)
 
     for (ATermIterator i(attrs.keys()); i; ++i) {
         string key = aterm2String(*i);
-        Expr value = attrs.get(key);
+        ATerm value;
+        Expr pos;
+        ATerm rhs = attrs.get(key);
+        ATMatcher m;
+        if (!(atMatch(m, rhs) >> "" >> value >> pos)) abort();
         startNest(nest, lvlVomit, format("processing attribute `%1%'") % key);
 
         Strings ss;
         try {
             processBinding(state, value, ne, ss);
         } catch (Error & e) {
-            throw Error(format("while processing derivation binding `%1%': %2%")
-                % key % e.msg());
+            throw Error(format("while processing derivation attribute `%1%' at %2%:\n%3%")
+                % key % showPos(pos) % e.msg());
         }
 
         /* The `args' attribute is special: it supplies the
@@ -213,11 +217,11 @@ Expr primDerivation(EvalState & state, Expr args)
     
     /* Do we have all required attributes? */
     if (ne.derivation.builder == "")
-        throw badTerm("required attribute `builder' missing", args);
+        throw Error("required attribute `builder' missing");
     if (ne.derivation.platform == "")
-        throw badTerm("required attribute `system' missing", args);
+        throw Error("required attribute `system' missing");
     if (drvName == "")
-        throw badTerm("required attribute `name' missing", args);
+        throw Error("required attribute `name' missing");
         
     /* Determine the output path. */
     if (!outHashGiven) outHash = hashDerivation(state, ne);
@@ -238,10 +242,10 @@ Expr primDerivation(EvalState & state, Expr args)
     printMsg(lvlChatty, format("instantiated `%1%' -> `%2%'")
         % drvName % drvPath);
 
-    attrs.set("outPath", ATmake("Path(<str>)", outPath.c_str()));
-    attrs.set("drvPath", ATmake("Path(<str>)", drvPath.c_str()));
-    attrs.set("drvHash", ATmake("Str(<str>)", ((string) drvHash).c_str()));
-    attrs.set("type", ATmake("Str(\"derivation\")"));
+    attrs.set("outPath", ATmake("(Path(<str>), NoPos)", outPath.c_str()));
+    attrs.set("drvPath", ATmake("(Path(<str>), NoPos)", drvPath.c_str()));
+    attrs.set("drvHash", ATmake("(Str(<str>), NoPos)", ((string) drvHash).c_str()));
+    attrs.set("type", ATmake("(Str(\"derivation\"), NoPos)"));
 
     return makeAttrs(attrs);
 }
@@ -263,7 +267,7 @@ Expr primToString(EvalState & state, Expr arg)
         atMatch(m, arg) >> "Path" >> s ||
         atMatch(m, arg) >> "Uri" >> s)
         return ATmake("Str(<str>)", s.c_str());
-    else throw badTerm("cannot coerce to string", arg);
+    else throw Error("cannot coerce value to string");
 }