Skip to content

Commit

Permalink
nixd/Sema: add lowering support for attrs
Browse files Browse the repository at this point in the history
  • Loading branch information
inclyc committed Sep 21, 2023
1 parent fe8f0de commit 063e133
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 32 deletions.
12 changes: 10 additions & 2 deletions nixd/include/nixd/Sema/Lowering.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,23 @@ class ExprAttrsBuilder {
Lowering &LW;
nix::ExprAttrs *Result;

/// Nested attributes, we create a new builder for them, and collapse the map
/// while finishing
std::map<nix::Symbol, std::unique_ptr<ExprAttrsBuilder>> Nested;

std::map<syntax::Node *, std::unique_ptr<ExprAttrsBuilder>> DynamicNested;

// Use a map to detect duplicated fields
// Not using the map inside nix::ExprAttrs because we want to preserve the
// range
std::map<nix::Symbol, syntax::Node *> Fields;
std::map<nix::Symbol, const syntax::Node *> Fields;

public:
ExprAttrsBuilder(Lowering &LW);
void addAttr(const syntax::Node *Attr, const syntax::Node *Body);
void addAttribute(const syntax::Attribute &A);
void addBinds(const syntax::Binds &Binds);
void addAttrSet(const syntax::AttrSet &AS);
void addInherited(const syntax::InheritedAttribute &IA);
nix::ExprAttrs *finish();
};
Expand All @@ -40,7 +49,6 @@ struct Lowering {
nix::ExprLambda *lowerFunction(const syntax::Function *Fn);
nix::Formal lowerFormal(const syntax::Formal &Formal);
nix::AttrPath lowerAttrPath(const syntax::AttrPath &Path);
nix::ExprAttrs *lowerBinds(const syntax::Binds &Binds);
};

} // namespace nixd
165 changes: 135 additions & 30 deletions nixd/lib/Sema/Lowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
#include "nixd/Syntax/Diagnostic.h"
#include "nixd/Syntax/Nodes.h"

#include <llvm/Support/ErrorHandling.h>
#include <nix/nixexpr.hh>

#include <llvm/Support/FormatVariadic.h>

#include <memory>

namespace nixd {

using syntax::Diagnostic;
using syntax::Node;

nix::Formal Lowering::lowerFormal(const syntax::Formal &Formal) {
Expand Down Expand Up @@ -133,9 +137,126 @@ ExprAttrsBuilder::ExprAttrsBuilder(Lowering &LW) : LW(LW) {
Result = LW.Ctx.Pool.record(new nix::ExprAttrs);
}

nix::ExprAttrs *ExprAttrsBuilder::finish() { return Result; }
nix::ExprAttrs *ExprAttrsBuilder::finish() {
for (const auto &[Sym, Builder] : Nested) {
auto *NestedAttr = Builder->finish();
nix::ExprAttrs::AttrDef Def(NestedAttr, NestedAttr->pos, false);
Result->attrs.insert({Sym, Def});
}
for (const auto &[Node, Builder] : DynamicNested) {
nix::Expr *Name = LW.lower(Node);
nix::ExprAttrs *Value = Builder->finish();
nix::ExprAttrs::DynamicAttrDef DDef(Name, Value, Value->pos);
Result->dynamicAttrs.emplace_back(DDef);
}
return Result;
}

void ExprAttrsBuilder::addBinds(const syntax::Binds &Binds) {
for (Node *Attr : Binds.Attributes) {
switch (Attr->getKind()) {
case Node::NK_Attribute:
addAttribute(*dynamic_cast<syntax::Attribute *>(Attr));
break;
case Node::NK_InheritedAttribute:
addInherited(*dynamic_cast<syntax::InheritedAttribute *>(Attr));
break;
default:
llvm_unreachable("attr in binds must be either A or IA!");
}
}
}

void ExprAttrsBuilder::addAttrSet(const syntax::AttrSet &AS) {
if (AS.Recursive) {
Diagnostic Diag;
Diag.Kind = Diagnostic::Warning;
Diag.Msg = "merging nested `rec` attributes, nested `rec` will be "
"implictly ignored";
Diag.Range = AS.Range;
LW.Diags.emplace_back(std::move(Diag));
return;
}
assert(AS.AttrBinds && "binds should not be empty! parser error?");
addBinds(*AS.AttrBinds);
}

void ExprAttrsBuilder::addAttr(const syntax::Node *Attr,
const syntax::Node *Body) {
switch (Attr->getKind()) {
case Node::NK_Identifier: {
const auto *ID = dynamic_cast<const syntax::Identifier *>(Attr);
nix::Symbol Sym = ID->Symbol;
if (Fields.contains(Sym)) {
// duplicated, but if they are two attrset, we can try to merge them.
if (Body->getKind() != Node::NK_AttrSet) {
// the body is not an attribute set, report error.
auto Diag =
mkAttrDupDiag(LW.STable[Sym], ID->Range, Fields[Sym]->Range);
LW.Diags.emplace_back(std::move(Diag));
return;
}
const auto *BodyAttrSet = dynamic_cast<const syntax::AttrSet *>(Body);
addAttrSet(*BodyAttrSet);
} else {
Fields[Sym] = Attr;
nix::ExprAttrs::AttrDef Def(LW.lower(Body), Attr->Range.Begin);
Result->attrs.insert({Sym, Def});
}
break;
}
default: {
nix::Expr *NameExpr = LW.lower(Attr);
nix::Expr *ValueExpr = LW.lower(Body);
nix::ExprAttrs::DynamicAttrDef DDef(NameExpr, ValueExpr, Attr->Range.Begin);
Result->dynamicAttrs.emplace_back(DDef);
}
}
}

void ExprAttrsBuilder::addAttribute(const syntax::Attribute &A) {
assert(!A.Path->Names.empty() && "attrpath should not be empty!");

// The targeting attribute builder we are actually inserting
// we will select the attrpath (`a.b.c`) and assign it to this variable
// left only one nix::Symbol or nix::Expr*, insert to it.
ExprAttrsBuilder *S = this;

void ExprAttrsBuilder::addAttribute(const syntax::Attribute &A) {}
// select attrpath, without the last one.
for (auto I = A.Path->Names.begin(); I + 1 != A.Path->Names.end(); I++) {
Node *Name = *I;

switch (Name->getKind()) {
case Node::NK_Identifier: {
auto *ID = dynamic_cast<syntax::Identifier *>(Name);
nix::Symbol Sym = ID->Symbol;
if (S->Fields.contains(Sym)) {
if (!S->Nested.contains(Sym)) {
// contains a field but the body is not an attr, then we cannot
// perform merging.
Diagnostic Diag =
mkAttrDupDiag(LW.STable[Sym], ID->Range, S->Fields[Sym]->Range);
LW.Diags.emplace_back(std::move(Diag));
return;
}
// select this symbol, and consider merging it.
S = S->Nested[Sym].get();
} else {
// create a new builder and select to it.
S->Nested[Sym] = std::make_unique<ExprAttrsBuilder>(LW);
S->Fields[Sym] = Name;
S = S->Nested[Sym].get();
}
break; // case Node::NK_Identifier:
}
default: {
DynamicNested[Name] = std::make_unique<ExprAttrsBuilder>(LW);
S = DynamicNested[Name].get();
}
}
}
S->addAttr(A.Path->Names.back(), A.Body);
}

void ExprAttrsBuilder::addInherited(const syntax::InheritedAttribute &IA) {
if (IA.InheritedAttrs->Names.empty()) {
Expand Down Expand Up @@ -198,27 +319,7 @@ void ExprAttrsBuilder::addInherited(const syntax::InheritedAttribute &IA) {
}
}

nix::ExprAttrs *Lowering::lowerBinds(const syntax::Binds &Binds) {
ExprAttrsBuilder Builder(*this);

for (auto *Attr : Binds.Attributes) {
switch (Attr->getKind()) {
case Node::NK_Attribute: {
Builder.addAttribute(*dynamic_cast<syntax::Attribute *>(Attr));
break;
}
case Node::NK_InheritedAttribute: {
Builder.addInherited(*dynamic_cast<syntax::InheritedAttribute *>(Attr));
break;
}
default:
llvm_unreachable("must be A/IA! incorrect parsing rules?");
}
}
return Builder.finish();
}

nix::Expr *Lowering::lower(const nixd::syntax::Node *Root) {
nix::Expr *Lowering::lower(const syntax::Node *Root) {
if (!Root)
return nullptr;

Expand All @@ -243,16 +344,20 @@ nix::Expr *Lowering::lower(const nixd::syntax::Node *Root) {
Ctx.Pool.record(new nix::ExprWith(With->Range.Begin, Attrs, Body));
return NixWith;
}
case Node::NK_Binds: {
const auto *Binds = dynamic_cast<const syntax::Binds *>(Root);
return lowerBinds(*Binds);
}
case Node::NK_AttrSet: {
const auto *AttrSet = dynamic_cast<const syntax::AttrSet *>(Root);
assert(AttrSet->AttrBinds && "null AttrBinds of the AttrSet!");
nix::ExprAttrs *Binds = lowerBinds(*AttrSet->AttrBinds);
Binds->recursive = AttrSet->Recursive;
return Binds;
ExprAttrsBuilder Builder(*this);
Builder.addBinds(*AttrSet->AttrBinds);
auto Result = Builder.finish();
Result->recursive = AttrSet->Recursive;
return Result;
}
case Node::NK_Int: {
const auto *Int = dynamic_cast<const syntax::Int *>(Root);
auto *NixInt = new nix::ExprInt(Int->N);
Ctx.Pool.record(NixInt);
return NixInt;
}
}

Expand Down
7 changes: 7 additions & 0 deletions nixd/tools/nixd-lint/test/lowering-attrset-dynamic.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# RUN: true

# FIXME: we cannot deal with string_parts currently
# rewrite this test after we can do such thing.
{
"${bar}" = 1;
}
7 changes: 7 additions & 0 deletions nixd/tools/nixd-lint/test/lowering-attrset-merge-inherit.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# RUN: nixd-lint %s | FileCheck %s
{
inherit aa;

# CHECK: duplicated attr `aa`
aa = 1;
}
10 changes: 10 additions & 0 deletions nixd/tools/nixd-lint/test/lowering-attrset-merging-2.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# RUN: nixd-lint %s | FileCheck %s
{
a.bbb = 1;

# CHECK: error: duplicated attr `b`
a.bbb.c = 2;

# CHECK: duplicated attr `a`
a = 3;
}
10 changes: 10 additions & 0 deletions nixd/tools/nixd-lint/test/lowering-attrset-rec.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# RUN: nixd-lint %s | FileCheck %s
{
foo.bar = 1;

# CHECK: merging nested `rec` attributes, nested `rec` will be implictly ignored
foo = rec {
x = 1;
y = x;
};
}
12 changes: 12 additions & 0 deletions nixd/tools/nixd-lint/test/lowering-attrset-select-merging.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# RUN: nixd-lint -dump-nix-ast %s | FileCheck %s

# CHECK: ExprAttrs: { a = { b = 1; c = 2; }; x = 1; }
{
# CHECK: ExprAttrs: { b = 1; c = 2; }
a.b = 1;
a.c = 2;

a = {
x = 1;
};
}

0 comments on commit 063e133

Please sign in to comment.