about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/libexpr/eval.cc55
-rw-r--r--src/libexpr/nixexpr.cc39
-rw-r--r--src/libexpr/nixexpr.hh28
-rw-r--r--src/libexpr/parser.y166
-rw-r--r--tests/lang/eval-okay-dynamic-attrs-2.exp1
-rw-r--r--tests/lang/eval-okay-dynamic-attrs-2.nix1
-rw-r--r--tests/lang/eval-okay-dynamic-attrs.exp1
-rw-r--r--tests/lang/eval-okay-dynamic-attrs.nix17
-rw-r--r--tests/lang/eval-okay-redefine-builtin.exp1
-rw-r--r--tests/lang/eval-okay-redefine-builtin.nix3
10 files changed, 249 insertions, 63 deletions
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 3db4bb66f435..3d8ee9934016 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -129,6 +129,18 @@ string showType(const Value & v)
 }
 
 
+Symbol getName(const AttrName & name, EvalState & state, Env & env) {
+    if (name.symbol.set()) {
+        return name.symbol;
+    } else {
+        Value nameValue;
+        name.expr->eval(state, env, nameValue);
+        state.forceStringNoCtx(nameValue);
+        return state.symbols.create(nameValue.string.s);
+    }
+}
+
+
 EvalState::EvalState()
     : sWith(symbols.create("<with>"))
     , sOutPath(symbols.create("outPath"))
@@ -247,6 +259,11 @@ LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, con
     throw EvalError(format(s) % s2 % s3);
 }
 
+LocalNoInlineNoReturn(void throwEvalError(const char * s, const Symbol & sym, const Pos & p1, const Pos & p2))
+{
+    throw EvalError(format(s) % sym % p1 % p2);
+}
+
 LocalNoInlineNoReturn(void throwTypeError(const char * s))
 {
     throw TypeError(s);
@@ -557,12 +574,14 @@ void ExprPath::eval(EvalState & state, Env & env, Value & v)
 void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
 {
     state.mkAttrs(v, attrs.size());
+    Env *dynamicEnv = &env;
 
     if (recursive) {
         /* Create a new environment that contains the attributes in
            this `rec'. */
         Env & env2(state.allocEnv(attrs.size()));
         env2.up = &env;
+        dynamicEnv = &env2;
 
         AttrDefs::iterator overrides = attrs.find(state.sOverrides);
         bool hasOverrides = overrides != attrs.end();
@@ -605,9 +624,24 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
         }
     }
 
-    else {
+    else
         foreach (AttrDefs::iterator, i, attrs)
             v.attrs->push_back(Attr(i->first, i->second.e->maybeThunk(state, env), &i->second.pos));
+
+    /* dynamic attrs apply *after* rec and __overrides */
+    foreach (DynamicAttrDefs::iterator, i, dynamicAttrs) {
+        Value nameVal;
+        i->nameExpr->eval(state, *dynamicEnv, nameVal);
+        state.forceStringNoCtx(nameVal);
+        Symbol nameSym = state.symbols.create(nameVal.string.s);
+        Bindings::iterator j = v.attrs->find(nameSym);
+        if (j != v.attrs->end())
+            throwEvalError("dynamic attribute `%1%' at %2% already defined at %3%", nameSym, i->pos, *j->pos);
+
+        i->valueExpr->setName(nameSym);
+        /* Keep sorted order so find can catch duplicates */
+        v.attrs->insert(lower_bound(v.attrs->begin(), v.attrs->end(), Attr(nameSym, 0)),
+                Attr(nameSym, i->valueExpr->maybeThunk(state, *dynamicEnv), &i->pos));
     }
 }
 
@@ -661,17 +695,18 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
         foreach (AttrPath::const_iterator, i, attrPath) {
             nrLookups++;
             Bindings::iterator j;
+            Symbol name = getName(*i, state, env);
             if (def) {
                 state.forceValue(*vAttrs);
                 if (vAttrs->type != tAttrs ||
-                    (j = vAttrs->attrs->find(*i)) == vAttrs->attrs->end())
+                    (j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
                 {
                     def->eval(state, env, v);
                     return;
                 }
             } else {
                 state.forceAttrs(*vAttrs);
-                if ((j = vAttrs->attrs->find(*i)) == vAttrs->attrs->end())
+                if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
                     throwEvalError("attribute `%1%' missing", showAttrPath(attrPath));
             }
             vAttrs = j->value;
@@ -702,8 +737,9 @@ void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v)
     foreach (AttrPath::const_iterator, i, attrPath) {
         state.forceValue(*vAttrs);
         Bindings::iterator j;
+        Symbol name = getName(*i, state, env);
         if (vAttrs->type != tAttrs ||
-            (j = vAttrs->attrs->find(*i)) == vAttrs->attrs->end())
+            (j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
         {
             mkBool(v, false);
             return;
@@ -908,6 +944,17 @@ void ExprOpNot::eval(EvalState & state, Env & env, Value & v)
 }
 
 
+void ExprBuiltin::eval(EvalState & state, Env & env, Value & v)
+{
+    // Not a hot path at all, but would be nice to access state.baseEnv directly
+    Env *baseEnv = &env;
+    while (baseEnv->up) baseEnv = baseEnv->up;
+    Bindings::iterator binding = baseEnv->values[0]->attrs->find(name);
+    assert(binding != baseEnv->values[0]->attrs->end());
+    v = *binding->value;
+}
+
+
 void ExprOpEq::eval(EvalState & state, Env & env, Value & v)
 {
     Value v1; e1->eval(state, env, v1);
diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc
index f4b4295e290c..9f0bc2630ddc 100644
--- a/src/libexpr/nixexpr.cc
+++ b/src/libexpr/nixexpr.cc
@@ -61,6 +61,8 @@ void ExprAttrs::show(std::ostream & str)
             str << "inherit " << i->first << " " << "; ";
         else
             str << i->first << " = " << *i->second.e << "; ";
+    foreach (DynamicAttrDefs::iterator, i, dynamicAttrs)
+        str << "\"${" << *i->nameExpr << "}\" = " << *i->valueExpr << "; ";
     str << "}";
 }
 
@@ -121,6 +123,11 @@ void ExprOpNot::show(std::ostream & str)
     str << "! " << *e;
 }
 
+void ExprBuiltin::show(std::ostream & str)
+{
+    str << "builtins." << name;
+}
+
 void ExprConcatStrings::show(std::ostream & str)
 {
     bool first = true;
@@ -148,12 +155,19 @@ std::ostream & operator << (std::ostream & str, const Pos & pos)
 
 string showAttrPath(const AttrPath & attrPath)
 {
-    string s;
+    std::ostringstream out;
+    bool first = true;
     foreach (AttrPath::const_iterator, i, attrPath) {
-        if (!s.empty()) s += '.';
-        s += *i;
+        if (!first)
+            out << '.';
+        else
+            first = false;
+        if (i->symbol.set())
+            out << i->symbol;
+        else
+            out << "\"${" << *i->expr << "}\"";
     }
-    return s;
+    return out.str();
 }
 
 
@@ -213,17 +227,25 @@ void ExprSelect::bindVars(const StaticEnv & env)
 {
     e->bindVars(env);
     if (def) def->bindVars(env);
+    foreach (AttrPath::iterator, i, attrPath)
+        if (!i->symbol.set())
+            i->expr->bindVars(env);
 }
 
 void ExprOpHasAttr::bindVars(const StaticEnv & env)
 {
     e->bindVars(env);
+    foreach (AttrPath::iterator, i, attrPath)
+        if (!i->symbol.set())
+            i->expr->bindVars(env);
 }
 
 void ExprAttrs::bindVars(const StaticEnv & env)
 {
+    const StaticEnv *dynamicEnv = &env;
     if (recursive) {
         StaticEnv newEnv(false, &env);
+        dynamicEnv = &newEnv;
 
         unsigned int displ = 0;
         foreach (AttrDefs::iterator, i, attrs)
@@ -236,6 +258,11 @@ void ExprAttrs::bindVars(const StaticEnv & env)
     else
         foreach (AttrDefs::iterator, i, attrs)
             i->second.e->bindVars(env);
+
+    foreach (DynamicAttrDefs::iterator, i, dynamicAttrs) {
+        i->nameExpr->bindVars(*dynamicEnv);
+        i->valueExpr->bindVars(*dynamicEnv);
+    }
 }
 
 void ExprList::bindVars(const StaticEnv & env)
@@ -314,6 +341,10 @@ void ExprOpNot::bindVars(const StaticEnv & env)
     e->bindVars(env);
 }
 
+void ExprBuiltin::bindVars(const StaticEnv & env)
+{
+}
+
 void ExprConcatStrings::bindVars(const StaticEnv & env)
 {
     foreach (vector<Expr *>::iterator, i, *es)
diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh
index 61eb81fab138..bc6993477c48 100644
--- a/src/libexpr/nixexpr.hh
+++ b/src/libexpr/nixexpr.hh
@@ -50,10 +50,19 @@ struct Env;
 struct Value;
 struct EvalState;
 struct StaticEnv;
+struct Expr;
 
 
 /* An attribute path is a sequence of attribute names. */
-typedef vector<Symbol> AttrPath;
+struct AttrName
+{
+    Symbol symbol;
+    Expr *expr;
+    AttrName(const Symbol & s) : symbol(s) {};
+    AttrName(Expr *e) : expr(e) {};
+};
+
+typedef std::vector<AttrName> AttrPath;
 
 string showAttrPath(const AttrPath & attrPath);
 
@@ -138,7 +147,7 @@ struct ExprSelect : Expr
     Expr * e, * def;
     AttrPath attrPath;
     ExprSelect(Expr * e, const AttrPath & attrPath, Expr * def) : e(e), def(def), attrPath(attrPath) { };
-    ExprSelect(Expr * e, const Symbol & name) : e(e), def(0) { attrPath.push_back(name); };
+    ExprSelect(Expr * e, const Symbol & name) : e(e), def(0) { attrPath.push_back(AttrName(name)); };
     COMMON_METHODS
 };
 
@@ -163,6 +172,14 @@ struct ExprAttrs : Expr
     };
     typedef std::map<Symbol, AttrDef> AttrDefs;
     AttrDefs attrs;
+    struct DynamicAttrDef {
+        Expr * nameExpr;
+        Expr * valueExpr;
+        Pos pos;
+        DynamicAttrDef(Expr * nameExpr, Expr * valueExpr, const Pos & pos) : nameExpr(nameExpr), valueExpr(valueExpr), pos(pos) { };
+    };
+    typedef std::vector<DynamicAttrDef> DynamicAttrDefs;
+    DynamicAttrDefs dynamicAttrs;
     ExprAttrs() : recursive(false) { };
     COMMON_METHODS
 };
@@ -248,6 +265,13 @@ struct ExprOpNot : Expr
     COMMON_METHODS
 };
 
+struct ExprBuiltin : Expr
+{
+    Symbol name;
+    ExprBuiltin(const Symbol & name) : name(name) { };
+    COMMON_METHODS
+};
+
 #define MakeBinOp(name, s) \
     struct Expr##name : Expr \
     { \
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index 7699cf502b79..28972cf72416 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -74,37 +74,49 @@ static void dupAttr(const AttrPath & attrPath, const Pos & pos, const Pos & prev
 
 static void dupAttr(Symbol attr, const Pos & pos, const Pos & prevPos)
 {
-    AttrPath attrPath; attrPath.push_back(attr);
     throw ParseError(format("attribute `%1%' at %2% already defined at %3%")
-        % showAttrPath(attrPath) % pos % prevPos);
+        % attr % pos % prevPos);
 }
 
 
 static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
     Expr * e, const Pos & pos)
 {
-    unsigned int n = 0;
-    foreach (AttrPath::const_iterator, i, attrPath) {
-        n++;
-        ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(*i);
-        if (j != attrs->attrs.end()) {
-            if (!j->second.inherited) {
-                ExprAttrs * attrs2 = dynamic_cast<ExprAttrs *>(j->second.e);
-                if (!attrs2 || n == attrPath.size()) dupAttr(attrPath, pos, j->second.pos);
-                attrs = attrs2;
-            } else
-                dupAttr(attrPath, pos, j->second.pos);
-        } else {
-            if (n == attrPath.size())
-                attrs->attrs[*i] = ExprAttrs::AttrDef(e, pos);
-            else {
+    AttrPath::iterator i;
+    // All attrpaths have at least one attr
+    assert(!attrPath.empty());
+    for (i = attrPath.begin(); i + 1 < attrPath.end(); i++) {
+        if (i->symbol.set()) {
+            ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
+            if (j != attrs->attrs.end()) {
+                if (!j->second.inherited) {
+                    ExprAttrs * attrs2 = dynamic_cast<ExprAttrs *>(j->second.e);
+                    if (!attrs2) dupAttr(attrPath, pos, j->second.pos);
+                    attrs = attrs2;
+                } else
+                    dupAttr(attrPath, pos, j->second.pos);
+            } else {
                 ExprAttrs * nested = new ExprAttrs;
-                attrs->attrs[*i] = ExprAttrs::AttrDef(nested, pos);
+                attrs->attrs[i->symbol] = ExprAttrs::AttrDef(nested, pos);
                 attrs = nested;
             }
+        } else {
+            ExprAttrs *nested = new ExprAttrs;
+            attrs->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, nested, pos));
+            attrs = nested;
         }
     }
-    e->setName(attrPath.back());
+    if (i->symbol.set()) {
+        ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
+        if (j != attrs->attrs.end()) {
+            dupAttr(attrPath, pos, j->second.pos);
+        } else {
+            attrs->attrs[i->symbol] = ExprAttrs::AttrDef(e, pos);
+            e->setName(i->symbol);
+        }
+    } else {
+        attrs->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, e, pos));
+    }
 }
 
 
@@ -243,7 +255,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
   char * id; // !!! -> Symbol
   char * path;
   char * uri;
-  std::vector<nix::Symbol> * attrNames;
+  std::vector<nix::AttrName> * attrNames;
   std::vector<nix::Expr *> * string_parts;
 }
 
@@ -254,7 +266,8 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
 %type <formals> formals
 %type <formal> formal
 %type <attrNames> attrs attrpath
-%type <string_parts> string_parts ind_string_parts
+%type <string_parts> string_parts_interpolated ind_string_parts
+%type <e> string_parts string_attr
 %type <id> attr
 %token <id> ID ATTRPATH
 %token <e> STR IND_STR
@@ -300,7 +313,11 @@ expr_function
   | WITH expr ';' expr_function
     { $$ = new ExprWith(CUR_POS, $2, $4); }
   | LET binds IN expr_function
-    { $$ = new ExprLet($2, $4); }
+    { if (!$2->dynamicAttrs.empty())
+        throw ParseError(format("dynamic attributes not allowed in let at %1%")
+            % CUR_POS);
+      $$ = new ExprLet($2, $4);
+    }
   | expr_if
   ;
 
@@ -311,13 +328,13 @@ expr_if
 
 expr_op
   : '!' expr_op %prec NOT { $$ = new ExprOpNot($2); }
-| '-' expr_op %prec NEGATE { $$ = new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__sub")), new ExprInt(0)), $2); }
+| '-' expr_op %prec NEGATE { $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("sub")), new ExprInt(0)), $2); }
   | expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); }
   | expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); }
-  | expr_op '<' expr_op { $$ = new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__lessThan")), $1), $3); }
-  | expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__lessThan")), $3), $1)); }
-  | expr_op '>' expr_op { $$ = new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__lessThan")), $3), $1); }
-  | expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__lessThan")), $1), $3)); }
+  | expr_op '<' expr_op { $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("lessThan")), $1), $3); }
+  | expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("lessThan")), $3), $1)); }
+  | expr_op '>' expr_op { $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("lessThan")), $3), $1); }
+  | expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("lessThan")), $1), $3)); }
   | expr_op AND expr_op { $$ = new ExprOpAnd($1, $3); }
   | expr_op OR expr_op { $$ = new ExprOpOr($1, $3); }
   | expr_op IMPL expr_op { $$ = new ExprOpImpl($1, $3); }
@@ -329,9 +346,9 @@ expr_op
       l->push_back($3);
       $$ = new ExprConcatStrings(false, l);
     }
-  | expr_op '-' expr_op { $$ = new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__sub")), $1), $3); }
-  | expr_op '*' expr_op { $$ = new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__mul")), $1), $3); }
-  | expr_op '/' expr_op { $$ = new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__div")), $1), $3); }
+  | expr_op '-' expr_op { $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("sub")), $1), $3); }
+  | expr_op '*' expr_op { $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("mul")), $1), $3); }
+  | expr_op '/' expr_op { $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("div")), $1), $3); }
   | expr_op CONCAT expr_op { $$ = new ExprOpConcatLists($1, $3); }
   | expr_app
   ;
@@ -362,12 +379,7 @@ expr_simple
           $$ = new ExprVar(CUR_POS, data->symbols.create($1));
   }
   | INT { $$ = new ExprInt($1); }
-  | '"' string_parts '"' {
-      /* For efficiency, and to simplify parse trees a bit. */
-      if ($2->empty()) $$ = new ExprString(data->symbols.create(""));
-      else if ($2->size() == 1 && dynamic_cast<ExprString *>($2->front())) $$ = $2->front();
-      else $$ = new ExprConcatStrings(true, $2);
-  }
+  | '"' string_parts '"' { $$ = $2; }
   | IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE {
       $$ = stripIndentation(data->symbols, *$2);
   }
@@ -381,7 +393,7 @@ expr_simple
          ‘throw’. */
       $$ = path2 == ""
           ? (Expr * ) new ExprApp(
-              new ExprVar(noPos, data->symbols.create("throw")),
+              new ExprBuiltin(data->symbols.create("throw")),
               new ExprString(data->symbols.create(
                       (format("file `%1%' was not found in the Nix search path (add it using $NIX_PATH or -I)") % path).str())))
           : (Expr * ) new ExprPath(path2);
@@ -400,9 +412,27 @@ expr_simple
   ;
 
 string_parts
-  : string_parts STR { $$ = $1; $1->push_back($2); }
-  | string_parts DOLLAR_CURLY expr '}' { backToString(scanner); $$ = $1; $1->push_back($3); }
-  | { $$ = new vector<Expr *>; }
+  : STR
+  | string_parts_interpolated { $$ = new ExprConcatStrings(true, $1); }
+  | { $$ = new ExprString(data->symbols.create("")) }
+  ;
+
+string_parts_interpolated
+  : string_parts_interpolated STR { $$ = $1; $1->push_back($2); }
+  | string_parts_interpolated DOLLAR_CURLY expr '}' { backToString(scanner); $$ = $1; $1->push_back($3); }
+  | STR DOLLAR_CURLY expr '}'
+    {
+      backToString(scanner);
+      $$ = new vector<Expr *>;
+      $$->push_back($1);
+      $$->push_back($3);
+    }
+  | DOLLAR_CURLY expr '}'
+    {
+      backToString(scanner);
+      $$ = new vector<Expr *>;
+      $$->push_back($2);
+    }
   ;
 
 ind_string_parts
@@ -416,39 +446,69 @@ binds
   | binds INHERIT attrs ';'
     { $$ = $1;
       foreach (AttrPath::iterator, i, *$3) {
-          if ($$->attrs.find(*i) != $$->attrs.end())
-              dupAttr(*i, makeCurPos(@3, data), $$->attrs[*i].pos);
+          if ($$->attrs.find(i->symbol) != $$->attrs.end())
+              dupAttr(i->symbol, makeCurPos(@3, data), $$->attrs[i->symbol].pos);
           Pos pos = makeCurPos(@3, data);
-          $$->attrs[*i] = ExprAttrs::AttrDef(new ExprVar(CUR_POS, *i), pos, true);
+          $$->attrs[i->symbol] = ExprAttrs::AttrDef(new ExprVar(CUR_POS, i->symbol), pos, true);
       }
     }
   | binds INHERIT '(' expr ')' attrs ';'
     { $$ = $1;
       /* !!! Should ensure sharing of the expression in $4. */
-      foreach (vector<Symbol>::iterator, i, *$6) {
-          if ($$->attrs.find(*i) != $$->attrs.end())
-              dupAttr(*i, makeCurPos(@6, data), $$->attrs[*i].pos);
-          $$->attrs[*i] = ExprAttrs::AttrDef(new ExprSelect($4, *i), makeCurPos(@6, data));
+      foreach (AttrPath::iterator, i, *$6) {
+          if ($$->attrs.find(i->symbol) != $$->attrs.end())
+              dupAttr(i->symbol, makeCurPos(@6, data), $$->attrs[i->symbol].pos);
+          $$->attrs[i->symbol] = ExprAttrs::AttrDef(new ExprSelect($4, i->symbol), makeCurPos(@6, data));
       }
     }
   | { $$ = new ExprAttrs; }
   ;
 
 attrs
-  : attrs attr { $$ = $1; $1->push_back(data->symbols.create($2)); /* !!! dangerous */ }
-  | { $$ = new vector<Symbol>; }
+  : attrs attr { $$ = $1; $1->push_back(AttrName(data->symbols.create($2))); }
+  | attrs string_attr
+    { $$ = $1;
+      ExprString *str = dynamic_cast<ExprString *>($2);
+      if (str) {
+          $$->push_back(AttrName(str->s));
+          delete str;
+      } else
+        throw ParseError(format("dynamic attributes not allowed in inherit at %1%")
+            % makeCurPos(@2, data));
+    }
+  | { $$ = new AttrPath; }
   ;
 
 attrpath
-  : attrpath '.' attr { $$ = $1; $1->push_back(data->symbols.create($3)); }
-  | attr { $$ = new vector<Symbol>; $$->push_back(data->symbols.create($1)); }
+  : attrpath '.' attr { $$ = $1; $1->push_back(AttrName(data->symbols.create($3))); }
+  | attrpath '.' string_attr
+    { $$ = $1;
+      ExprString *str = dynamic_cast<ExprString *>($3);
+      if (str) {
+          $$->push_back(AttrName(str->s));
+          delete str;
+      } else
+          $$->push_back(AttrName($3));
+    }
+  | attr { $$ = new vector<AttrName>; $$->push_back(AttrName(data->symbols.create($1))); }
+  | string_attr
+    { $$ = new vector<AttrName>;
+      ExprString *str = dynamic_cast<ExprString *>($1);
+      if (str) {
+          $$->push_back(AttrName(str->s));
+          delete str;
+      } else
+          $$->push_back(AttrName($1));
+    }
   ;
 
 attr
   : ID { $$ = $1; }
   | OR_KW { $$ = "or"; }
-  | '"' STR '"'
-    { $$ = strdup(((string) ((ExprString *) $2)->s).c_str()); delete $2; }
+  ;
+
+string_attr
+  : '"' string_parts '"' { $$ = $2; }
   ;
 
 expr_list
diff --git a/tests/lang/eval-okay-dynamic-attrs-2.exp b/tests/lang/eval-okay-dynamic-attrs-2.exp
new file mode 100644
index 000000000000..27ba77ddaf61
--- /dev/null
+++ b/tests/lang/eval-okay-dynamic-attrs-2.exp
@@ -0,0 +1 @@
+true
diff --git a/tests/lang/eval-okay-dynamic-attrs-2.nix b/tests/lang/eval-okay-dynamic-attrs-2.nix
new file mode 100644
index 000000000000..6d57bf854908
--- /dev/null
+++ b/tests/lang/eval-okay-dynamic-attrs-2.nix
@@ -0,0 +1 @@
+{ a."${"b"}" = true; a."${"c"}" = false; }.a.b
diff --git a/tests/lang/eval-okay-dynamic-attrs.exp b/tests/lang/eval-okay-dynamic-attrs.exp
new file mode 100644
index 000000000000..df8750afc036
--- /dev/null
+++ b/tests/lang/eval-okay-dynamic-attrs.exp
@@ -0,0 +1 @@
+{ binds = true; hasAttrs = true; multiAttrs = true; recBinds = true; selectAttrs = true; selectOrAttrs = true; }
diff --git a/tests/lang/eval-okay-dynamic-attrs.nix b/tests/lang/eval-okay-dynamic-attrs.nix
new file mode 100644
index 000000000000..ee02ac7e6579
--- /dev/null
+++ b/tests/lang/eval-okay-dynamic-attrs.nix
@@ -0,0 +1,17 @@
+let
+  aString = "a";
+
+  bString = "b";
+in {
+  hasAttrs = { a.b = null; } ? "${aString}".b;
+
+  selectAttrs = { a.b = true; }.a."${bString}";
+
+  selectOrAttrs = { }."${aString}" or true;
+
+  binds = { "${aString}"."${bString}c" = true; }.a.bc;
+
+  recBinds = rec { "${bString}" = a; a = true; }.b;
+
+  multiAttrs = { "${aString}" = true; "${bString}" = false; }.a;
+}
diff --git a/tests/lang/eval-okay-redefine-builtin.exp b/tests/lang/eval-okay-redefine-builtin.exp
new file mode 100644
index 000000000000..c508d5366f70
--- /dev/null
+++ b/tests/lang/eval-okay-redefine-builtin.exp
@@ -0,0 +1 @@
+false
diff --git a/tests/lang/eval-okay-redefine-builtin.nix b/tests/lang/eval-okay-redefine-builtin.nix
new file mode 100644
index 000000000000..df9fc3f37d22
--- /dev/null
+++ b/tests/lang/eval-okay-redefine-builtin.nix
@@ -0,0 +1,3 @@
+let
+  throw = abort "Error!";
+in (builtins.tryEval <foobaz>).success