diff --git a/default.nix b/default.nix index d0e09641c..96728bbe7 100644 --- a/default.nix +++ b/default.nix @@ -3,6 +3,7 @@ , bison , boost182 , flex +, fmt , gtest , libbacktrace , lit @@ -41,6 +42,7 @@ stdenv.mkDerivation { gtest boost182 llvmPackages.llvm + fmt ]; env.CXXFLAGS = "-include ${nix.dev}/include/nix/config.h"; diff --git a/meson.build b/meson.build index fd5d8c698..d0d4a91b0 100644 --- a/meson.build +++ b/meson.build @@ -31,6 +31,7 @@ gtest_main = dependency('gtest_main') llvm = dependency('llvm') boost = dependency('boost') +fmt = dependency('fmt') cpp = meson.get_compiler('cpp') backtrace = cpp.find_library('backtrace') diff --git a/nixd/include/nixd/Basic/Diagnostic.h b/nixd/include/nixd/Basic/Diagnostic.h new file mode 100644 index 000000000..3512d5854 --- /dev/null +++ b/nixd/include/nixd/Basic/Diagnostic.h @@ -0,0 +1,156 @@ +/// Diagnostic.h, diagnostic types and definitions +/// +/// Diagnostics are structures with a main message, +/// and optionally some additional information (body). +/// +/// For diagnostics with a body, +/// they may need a special overrided function to format the message. +/// +#pragma once + +#include "Range.h" +#include +#include + +#include + +namespace nixd { + +class PartialDiagnostic { +public: + virtual const char *format() const { + if (Result.empty()) + Result = fmt::vformat(message(), Args); + return Result.c_str(); + }; + + [[nodiscard]] virtual const char *message() const = 0; + + virtual ~PartialDiagnostic() = default; + + template PartialDiagnostic &operator<<(const T &Var) { + Args.push_back(Var); + return *this; + } + +protected: + mutable std::string Result; + fmt::dynamic_format_arg_store Args; + /// Location of this diagnostic + RangeIdx Range; +}; + +class Note : public PartialDiagnostic { +public: + /// Internal kind + enum NoteKind { +#define DIAG_NOTE(SNAME, CNAME, MESSAGE) NK_##CNAME, +#include "NoteKinds.inc" +#undef DIAG_NOTE + }; + + Note(RangeIdx Range, NoteKind Kind) : Range(Range), Kind(Kind) {} + + template PartialDiagnostic &operator<<(const T &Var) { + Args.push_back(Var); + return *this; + } + + NoteKind kind() const { return Kind; } + + [[nodiscard]] static const char *message(NoteKind Kind); + + [[nodiscard]] const char *message() const override { return message(kind()); } + + RangeIdx range() const { return Range; } + +private: + RangeIdx Range; + NoteKind Kind; +}; + +/// The super class for all diagnostics. +/// concret diagnostic types are defined in Diagnostic*.inc +class Diagnostic : public PartialDiagnostic { +public: + /// Each diagnostic contains a severity field, + /// should be "Fatal", "Error" or "Warning" + /// this will affect the eval process. + /// + /// "Fatal" -- shouldn't eval the code, e.g. parsing error. + /// "Error" -- trigger an error in nix, but we can eval the code. + /// "Warning" -- just a warning. + /// "Note" -- some additional information about the error. + enum Severity { DS_Fatal, DS_Error, DS_Warning }; + + /// Internal kind + enum DiagnosticKind { +#define DIAG(SNAME, CNAME, SEVERITY, MESSAGE) DK_##CNAME, +#include "DiagnosticKinds.inc" +#undef DIAG + }; + + Diagnostic(RangeIdx Range, DiagnosticKind Kind) : Range(Range), Kind(Kind) {} + + [[nodiscard]] DiagnosticKind kind() const { return Kind; }; + + [[nodiscard]] static Severity severity(DiagnosticKind Kind); + + [[nodiscard]] static const char *message(DiagnosticKind Kind); + + [[nodiscard]] const char *message() const override { return message(kind()); } + + /// Short name, switch name. + /// There might be a human readable short name that controls the diagnostic + /// For example, one may pass -Wno-dup-formal to suppress duplicated formals. + /// A special case for parsing errors, generated from bison + /// have the sname "bison" + [[nodiscard]] static const char *sname(DiagnosticKind Kind); + + [[nodiscard]] virtual const char *sname() const { return sname(kind()); } + + Note ¬e(RangeIdx Range, Note::NoteKind Kind) { + return *Notes.emplace_back(std::make_unique(Range, Kind)); + } + + std::vector> ¬es() { return Notes; } + + RangeIdx range() const { return Range; } + +private: + /// Location of this diagnostic + RangeIdx Range; + + std::vector> Notes; + + DiagnosticKind Kind; +}; + +class DiagnosticEngine { +public: + Diagnostic &diag(RangeIdx Range, Diagnostic::DiagnosticKind Kind) { + auto Diag = std::make_unique(Range, Kind); + switch (getServerity(Diag->kind())) { + case Diagnostic::DS_Fatal: + return *Fatals.emplace_back(std::move(Diag)); + case Diagnostic::DS_Error: + return *Errors.emplace_back(std::move(Diag)); + case Diagnostic::DS_Warning: + return *Warnings.emplace_back(std::move(Diag)); + } + } + + std::vector> &warnings() { return Warnings; } + std::vector> &errors() { return Errors; } + std::vector> &fatals() { return Fatals; } + +private: + Diagnostic::Severity getServerity(Diagnostic::DiagnosticKind Kind) { + return Diagnostic::severity(Kind); + } + std::vector> Warnings; + std::vector> Errors; + std::vector> Fatals; +}; + +} // namespace nixd diff --git a/nixd/include/nixd/Basic/DiagnosticKinds.inc b/nixd/include/nixd/Basic/DiagnosticKinds.inc new file mode 100644 index 000000000..a37f6bea5 --- /dev/null +++ b/nixd/include/nixd/Basic/DiagnosticKinds.inc @@ -0,0 +1,30 @@ +/// DiagnosticKinds.inc, diagnostic declarations + +#ifdef DIAG +DIAG("let-dynamic", LetDynamic, Error, + "dynamic attributes are not allowed in let ... in ... expression") +DIAG("inherit-dynamic", InheritDynamic, Error, + "dynamic attributes are not allowed in inherit") +DIAG("empty-inherit", EmptyInherit, Warning, "empty inherit expression") +DIAG("or-identifier", OrIdentifier, Warning, + "keyword `or` used as an identifier") +DIAG("deprecated-url-literal", DeprecatedURL, Warning, + "URL literal is deprecated") +DIAG("deprecated-let", DeprecatedLet, Warning, + "using deprecated `let' syntactic sugar `let {{..., body = ...}}' -> " + "(rec {{..., body = ...}}).body'") +DIAG("path-trailing-slash", PathTrailingSlash, Fatal, + "path has a trailing slash") +DIAG("dup-formal", DuplicatedFormal, Error, + "duplicated function formal declaration") +DIAG("dup-formal-arg", DuplicatedFormalToArg, Error, + "function argument duplicated to a function formal") +DIAG("merge-diff-rec", MergeDiffRec, Warning, + "merging two attributes with different `rec` modifiers, the latter " + "will be implicitly ignored") +DIAG("bison", BisonParse, Fatal, "{}") +DIAG("invalid-float", InvalidFloat, Fatal, "invalid float {}") +DIAG("invalid-integer", InvalidInteger, Fatal, "invalid integer {}") +DIAG("dup-formal-arg", DuplicatedAttr, Error, "duplicated attr `{}`") + +#endif // DIAG diff --git a/nixd/include/nixd/Basic/NoteKinds.inc b/nixd/include/nixd/Basic/NoteKinds.inc new file mode 100644 index 000000000..e9e159183 --- /dev/null +++ b/nixd/include/nixd/Basic/NoteKinds.inc @@ -0,0 +1,10 @@ +/// DiagnosticNodes.inc, note declarations + +#ifdef DIAG_NOTE +DIAG_NOTE("note-prev", PrevDeclared, "previously declared here") +DIAG_NOTE("merge-diff-rec-this-rec", ThisRecursive, + "this attribute set is {}recursive") +DIAG_NOTE("merge-diff-rec-consider", RecConsider, + "while this attribute set is marked as {}recursive, it " + "will be considered as {}recursive") +#endif // DIAG_NOTE diff --git a/nixd/include/nixd/Syntax/Range.h b/nixd/include/nixd/Basic/Range.h similarity index 100% rename from nixd/include/nixd/Syntax/Range.h rename to nixd/include/nixd/Basic/Range.h diff --git a/nixd/include/nixd/Sema/EvalContext.h b/nixd/include/nixd/Sema/EvalContext.h new file mode 100644 index 000000000..12260a6e5 --- /dev/null +++ b/nixd/include/nixd/Sema/EvalContext.h @@ -0,0 +1,16 @@ +#pragma once + +#include "nixd/Support/GCPool.h" + +#include + +namespace nixd { + +struct EvalContext { + using ES = std::vector>; + GCPool Pool; + GCPool FormalsPool; + GCPool ESPool; +}; + +} // namespace nixd diff --git a/nixd/include/nixd/Sema/Lowering.h b/nixd/include/nixd/Sema/Lowering.h new file mode 100644 index 000000000..bfa7b3741 --- /dev/null +++ b/nixd/include/nixd/Sema/Lowering.h @@ -0,0 +1,131 @@ +#pragma once + +#include "nixd/Basic/Diagnostic.h" +#include "nixd/Sema/EvalContext.h" +#include "nixd/Syntax/Nodes.h" + +#include +#include +#include + +namespace nixd { + +struct Lowering; + +class ExprAttrsBuilder { + // The official parser does this work during the lowering process, via + // @addAttr in src/libexpr/parser.y + + Lowering &LW; + nix::ExprAttrs *Result; + + RangeIdx Range; + + bool Recursive; + + /// let ... in ... + /// It is not allowed to use dynamic binds here, so we want to give diagnostic + /// to each occurrence. + bool IsLet; + + /// Nested attributes, we create a new builder for them, and collapse the map + /// while finishing + std::map> Nested; + + std::map> DynamicNested; + + // Use a map to detect duplicated fields + // Not using the map inside nix::ExprAttrs because we want to preserve the + // range + std::map Fields; + +public: + ExprAttrsBuilder(Lowering &LW, RangeIdx Range, bool Recursive, bool IsLet); + + void addAttr(const syntax::Node *Attr, const syntax::Node *Body, + bool Recursive); + 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(); +}; + +struct Lowering { + nix::SymbolTable &STable; + nix::PosTable &PTable; + DiagnosticEngine &Diags; + EvalContext &Ctx; + const nix::SourcePath &BasePath; + + nix::Expr *lower(const syntax::Node *Root); + nix::ExprLambda *lowerFunction(const syntax::Function *Fn); + nix::Formal lowerFormal(const syntax::Formal &Formal); + nix::AttrPath lowerAttrPath(const syntax::AttrPath &Path); + nix::Expr *lowerOp(const syntax::Node *Op); + +private: + constexpr static std::string_view LessThan = "__lessThan"; + constexpr static std::string_view Sub = "__sub"; + constexpr static std::string_view Mul = "__mul"; + constexpr static std::string_view Div = "__div"; + constexpr static std::string_view CurPos = "__curPos"; + constexpr static std::string_view FindFile = "__findFile"; + constexpr static std::string_view NixPath = "__nixPath"; + + nix::Expr *stripIndentation(const syntax::IndStringParts &ISP); + + nix::ExprVar *mkVar(std::string_view Sym) { + return mkVar(STable.create(Sym)); + } + + nix::ExprVar *mkVar(nix::Symbol Sym) { + auto *Var = new nix::ExprVar(Sym); + Ctx.Pool.record(Var); + return Var; + }; + + nix::ExprOpNot *mkNot(nix::Expr *Body) { + auto *OpNot = new nix::ExprOpNot(Body); + Ctx.Pool.record(OpNot); + return OpNot; + } + + nix::ExprCall *mkCall(std::string_view FnName, nix::PosIdx P, + std::vector Args) { + nix::ExprVar *Fn = mkVar(FnName); + auto *Nix = new nix::ExprCall(P, Fn, std::move(Args)); + return Ctx.Pool.record(Nix); + } + + template + nix::Expr *lowerCallOp(std::string_view FnName, const syntax::Node *Op, + bool SwapOperands = false, bool Not = false) { + auto *SOP = dynamic_cast(Op); + assert(SOP && "types are not matching between op pointer & syntax type!"); + nix::Expr *LHS = lower(SOP->LHS); + nix::Expr *RHS = lower(SOP->RHS); + if (SwapOperands) + std::swap(LHS, RHS); + nix::PosIdx P = SOP->OpRange.Begin; + nix::ExprCall *Call = mkCall(FnName, P, {LHS, RHS}); + if (Not) + return mkNot(Call); + return Call; + } + + /// The bin-op expression is actually legal in the nix evaluator + /// OpImpl -> nix::ExprOpImpl + template + NixTy *lowerLegalOp(const syntax::Node *Op) { + auto *SOP = dynamic_cast(Op); + assert(SOP && "types are not matching between op pointer & syntax type!"); + nix::Expr *LHS = lower(SOP->LHS); + nix::Expr *RHS = lower(SOP->RHS); + nix::PosIdx P = SOP->OpRange.Begin; + auto *Nix = new NixTy(P, LHS, RHS); + return Ctx.Pool.record(Nix); + } +}; + +} // namespace nixd diff --git a/nixd/include/nixd/Support/Position.h b/nixd/include/nixd/Support/Position.h index f7ff86d73..88fe7d5e3 100644 --- a/nixd/include/nixd/Support/Position.h +++ b/nixd/include/nixd/Support/Position.h @@ -1,7 +1,7 @@ #pragma once +#include "nixd/Basic/Range.h" #include "nixd/Nix/PosAdapter.h" -#include "nixd/Syntax/Range.h" #include "lspserver/Protocol.h" diff --git a/nixd/include/nixd/Syntax/Diagnostic.h b/nixd/include/nixd/Syntax/Diagnostic.h deleted file mode 100644 index 17f13fd19..000000000 --- a/nixd/include/nixd/Syntax/Diagnostic.h +++ /dev/null @@ -1,33 +0,0 @@ -/// Nixd diagnostic types. Provide structured diagnostic with location, FixIt -/// hints, annotation, and notes -#pragma once - -#include "nixd/Syntax/Range.h" - -namespace nixd::syntax { - -// The note. -struct Note { - nixd::RangeIdx Range; - - std::string Msg; -}; - -struct Edit { - nixd::RangeIdx Range; - - std::string NewText; -}; - -struct Diagnostic { - nixd::RangeIdx Range; - - std::string Msg; - - enum DiagKind { Warning, Error } Kind; - - std::vector Notes; - std::vector Edits; -}; - -} // namespace nixd::syntax diff --git a/nixd/include/nixd/Syntax/Nodes.h b/nixd/include/nixd/Syntax/Nodes.h index 44738962b..16d76d8ac 100644 --- a/nixd/include/nixd/Syntax/Nodes.h +++ b/nixd/include/nixd/Syntax/Nodes.h @@ -1,7 +1,7 @@ /// FIXME: comment for this file. #pragma once -#include "Range.h" +#include "nixd/Basic/Range.h" namespace nixd::syntax { diff --git a/nixd/include/nixd/Syntax/Nodes.inc b/nixd/include/nixd/Syntax/Nodes.inc index b6ee215c6..47c5dd06a 100644 --- a/nixd/include/nixd/Syntax/Nodes.inc +++ b/nixd/include/nixd/Syntax/Nodes.inc @@ -171,13 +171,13 @@ NODE(Braced, { }) NODE(AttrSet, { - Binds *AttrBinds; + syntax::Binds *Binds; bool Recursive; COMMON_METHOD }) NODE(LegacyLet, { - Binds *AttrBinds; + syntax::Binds *Binds; COMMON_METHOD }) diff --git a/nixd/include/nixd/Syntax/Parser/Require.h b/nixd/include/nixd/Syntax/Parser/Require.h index 034a8886f..95a044cee 100644 --- a/nixd/include/nixd/Syntax/Parser/Require.h +++ b/nixd/include/nixd/Syntax/Parser/Require.h @@ -1,7 +1,7 @@ #pragma once +#include "nixd/Basic/Diagnostic.h" #include "nixd/Support/GCPool.h" -#include "nixd/Syntax/Diagnostic.h" #include "nixd/Syntax/Nodes.h" #include @@ -26,7 +26,7 @@ struct ParseData { nixd::GCPool Nodes; - std::vector Diags; + DiagnosticEngine Diags; }; // Note: copied from diff --git a/nixd/lib/Basic/Diagnostic.cpp b/nixd/lib/Basic/Diagnostic.cpp new file mode 100644 index 000000000..f8f3efa5c --- /dev/null +++ b/nixd/lib/Basic/Diagnostic.cpp @@ -0,0 +1,43 @@ +#include "nixd/Basic/Diagnostic.h" +#include "nixd/Basic/Range.h" + +namespace nixd { + +nixd::Diagnostic::Severity nixd::Diagnostic::severity(DiagnosticKind Kind) { + switch (Kind) { +#define DIAG(SNAME, CNAME, SEVERITY, MESSAGE) \ + case DK_##CNAME: \ + return DS_##SEVERITY; +#include "nixd/Basic/DiagnosticKinds.inc" +#undef DIAG + } +} +const char *nixd::Diagnostic::message(DiagnosticKind Kind) { + switch (Kind) { +#define DIAG(SNAME, CNAME, SEVERITY, MESSAGE) \ + case DK_##CNAME: \ + return MESSAGE; +#include "nixd/Basic/DiagnosticKinds.inc" +#undef DIAG + } +} +const char *nixd::Diagnostic::sname(DiagnosticKind Kind) { + switch (Kind) { +#define DIAG(SNAME, CNAME, SEVERITY, MESSAGE) \ + case DK_##CNAME: \ + return SNAME; +#include "nixd/Basic/DiagnosticKinds.inc" +#undef DIAG + } +} +const char *nixd::Note::message(NoteKind Kind) { + switch (Kind) { +#define DIAG_NOTE(SNAME, CNAME, MESSAGE) \ + case NK_##CNAME: \ + return MESSAGE; +#include "nixd/Basic/NoteKinds.inc" +#undef DIAG_NOTE + } +} + +} // namespace nixd diff --git a/nixd/lib/Basic/meson.build b/nixd/lib/Basic/meson.build new file mode 100644 index 000000000..440f04078 --- /dev/null +++ b/nixd/lib/Basic/meson.build @@ -0,0 +1,13 @@ +libnixdBasicDeps = [ nix_all, llvm, fmt ] +libnixdBasic = library('nixdBasic' +, [ 'Diagnostic.cpp' + ] +, include_directories: nixd_inc +, dependencies: libnixdBasicDeps +, install: true +) + +nixdBasic = declare_dependency( include_directories: nixd_inc + , dependencies: libnixdBasicDeps + , link_with: libnixdBasic + ) diff --git a/nixd/lib/Sema/Lowering.cpp b/nixd/lib/Sema/Lowering.cpp new file mode 100644 index 000000000..aef6aab80 --- /dev/null +++ b/nixd/lib/Sema/Lowering.cpp @@ -0,0 +1,746 @@ +#include "nixd/Sema/Lowering.h" +#include "nixd/Basic/Diagnostic.h" +#include "nixd/Basic/Range.h" +#include "nixd/Sema/EvalContext.h" +#include "nixd/Syntax/Nodes.h" + +#include +#include + +#include +#include + +#include + +namespace nixd { + +using syntax::Node; +using DK = Diagnostic::DiagnosticKind; +using NK = Note::NoteKind; + +nix::Formal Lowering::lowerFormal(const syntax::Formal &Formal) { + auto *Def = Lowering::lower(Formal.Default); + nix::Formal F; + + F.pos = Formal.Range.Begin; + F.name = Formal.ID->Symbol; + F.def = Def; + return F; +} + +nix::ExprLambda *Lowering::lowerFunction(const syntax::Function *Fn) { + // Implementation note: + // The official parser does this in the semantic action, and we deferred it + // here, as a part of the progressive lowering process. + // We should be consistant with the official implementation, not strictly + // required. + + // @toFormals + // Link: src/libexpr/parser.y#L156 2a52ec4e928c254338a612a6b40355512298ef38 + + if (!Fn) + return nullptr; + + auto *Formals = Fn->FunctionFormals; + + nix::Formals *NixFormals = nullptr; + + if (Formals) { + std::sort(Formals->Formals.begin(), Formals->Formals.end(), + [](syntax::Formal *A, syntax::Formal *B) { + auto APos = A->Range.Begin; + auto BPos = B->Range.Begin; + return std::tie(A->ID->Symbol, APos) < + std::tie(B->ID->Symbol, BPos); + }); + + // Check if there is a formal duplicated to another, if so, emit a + // diagnostic expressively to our user + // + // We are not using upstream O(n) algorithm because we need to detect all + // duplicated stuff, instead of the first one. + std::map Names; + for (const auto *Formal : Formals->Formals) { + auto Sym = Formal->ID->Symbol; + if (Names.contains(Sym)) { + // We have seen this symbol before, so this formal is duplicated + const syntax::Formal *Previous = Names[Sym]; + Diags.diag(Formal->Range, Diagnostic::DK_DuplicatedFormal) + .note(Previous->Range, NK::NK_PrevDeclared); + } else { + Names[Sym] = Formal; + } + } + + // Check if the function argument is duplicated with the formal + if (Fn->Arg) { + auto ArgSym = Fn->Arg->Symbol; + if (Names.contains(ArgSym)) { + const syntax::Formal *Previous = Names[ArgSym]; + Diags.diag(Fn->Arg->Range, Diagnostic::DK_DuplicatedFormalToArg) + .note(Previous->Range, NK::NK_PrevDeclared); + } + } + // Construct nix::Formals, from ours + auto *NixFormals = Ctx.FormalsPool.record(new nix::Formals); + NixFormals->ellipsis = Formals->Ellipsis; + for (auto [_, Formal] : Names) { + nix::Formal F = lowerFormal(*Formal); + NixFormals->formals.emplace_back(F); + } + } + + auto *Body = lower(Fn->Body); + + nix::ExprLambda *NewLambda; + if (Fn->Arg) { + NewLambda = + new nix::ExprLambda(Fn->Range.Begin, Fn->Arg->Symbol, NixFormals, Body); + } else { + NewLambda = new nix::ExprLambda(Fn->Range.Begin, NixFormals, Body); + } + Ctx.Pool.record(NewLambda); + + return NewLambda; +} + +static void diagAttrDup(DiagnosticEngine &DE, const std::string &Name, + RangeIdx Range, RangeIdx Prev) { + auto &Diag = DE.diag(Range, DK::DK_DuplicatedAttr); + Diag << Name; + Diag.note(Prev, NK::NK_PrevDeclared); +} + +ExprAttrsBuilder::ExprAttrsBuilder(Lowering &LW, RangeIdx Range, bool Recursive, + bool IsLet) + : LW(LW), Range(Range), Recursive(Recursive), IsLet(IsLet) { + Result = LW.Ctx.Pool.record(new nix::ExprAttrs); +} + +nix::ExprAttrs *ExprAttrsBuilder::finish() { + Result->recursive = Recursive; + 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(Attr)); + break; + case Node::NK_InheritedAttribute: + addInherited(*dynamic_cast(Attr)); + break; + default: + llvm_unreachable("attr in binds must be either A or IA!"); + } + } +} + +static const char *nonPrefix(bool P) { return P ? "" : "non-"; } + +static void diagRec(DiagnosticEngine &DE, RangeIdx ThisRange, + RangeIdx MergingRange, bool ThisRec) { + Diagnostic &Diag = DE.diag(MergingRange, DK::DK_MergeDiffRec); + Diag.note(ThisRange, NK::NK_ThisRecursive) << nonPrefix(ThisRec); + Diag.note(MergingRange, NK::NK_RecConsider) + << nonPrefix(!ThisRec) << nonPrefix(ThisRec); +} + +void ExprAttrsBuilder::addAttrSet(const syntax::AttrSet &AS) { + if (AS.Recursive != Recursive) { + diagRec(LW.Diags, Range, AS.Range, Recursive); + } + assert(AS.Binds && "binds should not be empty! parser error?"); + addBinds(*AS.Binds); +} + +void ExprAttrsBuilder::addAttr(const syntax::Node *Attr, + const syntax::Node *Body, bool Recursive) { + if (Recursive != this->Recursive) { + diagRec(LW.Diags, Range, Attr->Range, this->Recursive); + } + switch (Attr->getKind()) { + case Node::NK_Identifier: { + const auto *ID = dynamic_cast(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 || !Nested.contains(Sym)) { + // the body is not an attribute set, report error. + diagAttrDup(LW.Diags, LW.STable[Sym], ID->Range, Fields[Sym]->Range); + return; + } + const auto *BodyAttrSet = dynamic_cast(Body); + Nested[Sym]->addAttrSet(*BodyAttrSet); + } else { + Fields[Sym] = Attr; + if (Body->getKind() == Node::NK_AttrSet) { + // If the body is an attrset, we want to defer it's lowering process, + // because we have chance to merge it latter + const auto *BodyAttrSet = dynamic_cast(Body); + Nested[Sym] = std::make_unique( + LW, BodyAttrSet->Range, BodyAttrSet->Recursive, IsLet); + Nested[Sym]->addBinds(*BodyAttrSet->Binds); + } else { + nix::ExprAttrs::AttrDef Def(LW.lower(Body), Attr->Range.Begin); + Result->attrs.insert({Sym, Def}); + } + } + break; + } + default: { + if (IsLet) { + // reject dynamic attrs in let expression + LW.Diags.diag(Attr->Range, Diagnostic::DK_LetDynamic); + return; + } + 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; + + // 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(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. + diagAttrDup(LW.Diags, LW.STable[Sym], ID->Range, + S->Fields[Sym]->Range); + 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( + LW, Name->Range, /*Recursive=*/false, IsLet); + S->Fields[Sym] = Name; + S = S->Nested[Sym].get(); + } + break; // case Node::NK_Identifier: + } + default: { + if (IsLet) { + // reject dynamic attrs in let expression + LW.Diags.diag(Name->Range, Diagnostic::DK_LetDynamic); + return; + } + DynamicNested[Name] = std::make_unique( + LW, Name->Range, /*Recursive=*/false, IsLet); + S = DynamicNested[Name].get(); + } + } + } + // Implictly created attribute set, by attrpath selecting, are always + // non-recursive + S->addAttr(A.Path->Names.back(), A.Body, + /*Recursive=*/S != this ? false : this->Recursive); +} + +void ExprAttrsBuilder::addInherited(const syntax::InheritedAttribute &IA) { + if (IA.InheritedAttrs->Names.empty()) + LW.Diags.diag(IA.Range, Diagnostic::DK_EmptyInherit); + + // inherit (expr) attr1 attr2 + // lowering `(expr)` to nix expression + nix::Expr *Subject = LW.lower(IA.E); + + for (Node *Name : IA.InheritedAttrs->Names) { + + // Extract nix::Symbol, report error if we encountered dynamic fields + nix::Symbol Sym; + switch (Name->getKind()) { + case Node::NK_Identifier: { + Sym = dynamic_cast(Name)->Symbol; + break; + } + case Node::NK_String: { + Sym = LW.STable.create(dynamic_cast(Name)->S); + break; + } + default: { + LW.Diags.diag(Name->Range, Diagnostic::DK_InheritDynamic); + } + } + + // Check if it is duplicated + if (Fields.contains(Sym)) { + std::string SymStr = LW.STable[Sym]; + diagAttrDup(LW.Diags, SymStr, Name->Range, Fields[Sym]->Range); + continue; + } + + // Insert an AttrDef to the result + Fields.insert({Sym, Name}); + nix::Expr *AttrBody; + if (Subject) { + // inherit (expr) attr1 attr2 + AttrBody = new nix::ExprSelect(Name->Range.Begin, Subject, Sym); + } else { + // inherit attr1 attr2 ... + AttrBody = new nix::ExprVar(Name->Range.Begin, Sym); + } + LW.Ctx.Pool.record(AttrBody); + nix::ExprAttrs::AttrDef Def(AttrBody, Name->Range.Begin, true); + Result->attrs.insert({Sym, Def}); + } +} + +nix::AttrPath Lowering::lowerAttrPath(const syntax::AttrPath &Path) { + nix::AttrPath Ret; + for (Node *Name : Path.Names) { + switch (Name->getKind()) { + case Node::NK_Identifier: { + nix::Symbol Sym = dynamic_cast(Name)->Symbol; + Ret.emplace_back(Sym); + break; + } + default: { + nix::Expr *E = lower(Name); + Ret.emplace_back(E); + } + } + } + return Ret; +} + +nix::Expr *Lowering::lowerOp(const syntax::Node *Op) { + if (!Op) + return nullptr; + + switch (Op->getKind()) { + case Node::NK_OpNot: { + const auto *OpNot = dynamic_cast(Op); + return mkNot(lower(OpNot->Body)); + } + + // arithmetic + case Node::NK_OpAdd: { + // A + B + // -> + // ExprConcatStrings: + // - pos: "+" + // - forceString: false + // - es: {{A.pos, A}, {B.pos, B}} + const auto *OpAdd = dynamic_cast(Op); + nix::Expr *A = lower(OpAdd->LHS); + nix::Expr *B = lower(OpAdd->RHS); + nix::PosIdx APos = OpAdd->LHS->Range.Begin; + nix::PosIdx BPos = OpAdd->RHS->Range.Begin; + nix::PosIdx Pos = OpAdd->OpRange.Begin; + auto *ES = Ctx.ESPool.record(new EvalContext::ES{{APos, A}, {BPos, B}}); + auto *ECS = new nix::ExprConcatStrings(Pos, /*forceString=*/false, ES); + return Ctx.Pool.record(ECS); + } + case Node::NK_OpSub: + return lowerCallOp(Sub, Op); + case Node::NK_OpMul: + return lowerCallOp(Mul, Op); + case Node::NK_OpDiv: + return lowerCallOp(Div, Op); + + // comparison + case Node::NK_OpNegate: { + // -X + // -> + // (__sub 0 X) + const auto *OpNegate = dynamic_cast(Op); + nix::Expr *I0 = Ctx.Pool.record(new nix::ExprInt(0)); + nix::Expr *X = lower(OpNegate->Body); + nix::PosIdx P = OpNegate->OpRange.Begin; + return mkCall(Sub, P, {I0, X}); + } + case Node::NK_OpLe: { + // A < B + // -> + // (__lessThan A B) + return lowerCallOp(LessThan, Op); + } + case Node::NK_OpLeq: { + // A <= B + // -> + // (! (__lessThan B A)) + return lowerCallOp(LessThan, Op, /*SwapOperands=*/true, + /*Not=*/true); + } + case Node::NK_OpGe: { + // A > B + // -> + // (__lessThan B A) + return lowerCallOp(LessThan, Op, /*SwapOperands=*/true); + } + case Node::NK_OpGeq: { + // A >= B + // -> + // ! (__lessThan A B) + return lowerCallOp(LessThan, Op, /*SwapOperands=*/false, + /*Not=*/true); + } + + // legal nodes, the list here has the same order with official parser. + // src/libexpr/nixexpr.hh, `MakeBinOp` + case Node::NK_OpEq: + return lowerLegalOp(Op); + case Node::NK_OpNEq: + return lowerLegalOp(Op); + case Node::NK_OpAnd: + return lowerLegalOp(Op); + case Node::NK_OpOr: + return lowerLegalOp(Op); + case Node::NK_OpImpl: + return lowerLegalOp(Op); + case Node::NK_OpUpdate: + return lowerLegalOp(Op); + case Node::NK_OpConcatLists: + return lowerLegalOp(Op); + + case Node::NK_OpHasAttr: { + const auto *OpHasAttr = dynamic_cast(Op); + assert(OpHasAttr->Path && "OpHasAttr->Path must not be nullptr! (parser?)"); + nix::AttrPath Path = lowerAttrPath(*OpHasAttr->Path); + nix::Expr *Body = lower(OpHasAttr->Operand); + auto *NixOpHasAttr = new nix::ExprOpHasAttr(Body, std::move(Path)); + return Ctx.Pool.record(NixOpHasAttr); + } + default: + llvm_unreachable("do not know how to lower this op!"); + } + return nullptr; +} + +nix::Expr *Lowering::stripIndentation(const syntax::IndStringParts &ISP) { + // Note: this function is adopted from the offical implementation, see the + // comments there for what they want to do. + // @stripIndentation, src/libexpr/parser.y + if (ISP.SubStrings.empty()) + return Ctx.Pool.record(new nix::ExprString("")); + + bool AtStartOfLine = true; + std::size_t MinIndent = 1000000; + std::size_t CurIndent = 0; + for (Node *SubStr : ISP.SubStrings) { + auto *IndStr = dynamic_cast(SubStr); + if (!IndStr || !IndStr->HasIndentation) { + if (AtStartOfLine) { + AtStartOfLine = false; + MinIndent = std::min(CurIndent, MinIndent); + } + continue; + } + for (char C : IndStr->S) { + if (AtStartOfLine) { + if (C == ' ') { + CurIndent++; + } else if (C == '\n') { + CurIndent = 0; + } else { + AtStartOfLine = false; + MinIndent = std::min(CurIndent, MinIndent); + } + } else if (C == '\n') { + AtStartOfLine = true; + CurIndent = 0; + } + } + } + + auto *NewES = new EvalContext::ES(); + AtStartOfLine = true; + std::size_t CurDropped = 0; + auto I = ISP.SubStrings.begin(); + auto N = ISP.SubStrings.size(); + + const auto TrimExpr = [&](nix::Expr *E) { + AtStartOfLine = false; + CurDropped = 0; + NewES->emplace_back(std::pair{(*I)->Range.Begin, E}); + }; + + const auto TrimString = [&](std::string &Before) { + std::string After; + for (char C : Before) { + if (AtStartOfLine) { + if (C == ' ') { + if (CurDropped >= MinIndent) + After += C; + } else if (C == '\n') { + CurDropped = 0; + After += C; + } else { + AtStartOfLine = false; + CurDropped = 0; + After += C; + } + } else { + After += C; + if (C == '\n') + AtStartOfLine = true; + } + } + + if (N == 1) { + auto P = After.find_last_of('\n'); + if (P != std::string::npos && + After.find_first_not_of(' ', P + 1) == std::string::npos) + After = std::string(After, 0, P + 1); + } + + NewES->emplace_back((*I)->Range.Begin, + Ctx.Pool.record(new nix::ExprString(std::move(After)))); + }; + + for (; I != ISP.SubStrings.end(); ++I, --N) { + switch ((*I)->getKind()) { + case Node::NK_IndString: { + auto *IndStr = dynamic_cast(*I); + TrimString(IndStr->S); + break; + } + case Node::NK_InterpExpr: { + auto *IE = dynamic_cast(*I); + TrimExpr(lower(IE->Body)); + break; + } + default: + llvm_unreachable("encountered neither string nor interpolated expr" + "in indented strings!"); + } + } + + if (NewES->size() == 1) { + if (auto *EStr = dynamic_cast((*NewES)[0].second)) { + return EStr; + } + } + return Ctx.Pool.record( + new nix::ExprConcatStrings(ISP.Range.Begin, /*forceString=*/true, NewES)); +} + +nix::Expr *Lowering::lower(const syntax::Node *Root) { + if (!Root) + return nullptr; + + switch (Root->getKind()) { + case Node::NK_Function: { + const auto *Fn = dynamic_cast(Root); + return lowerFunction(Fn); + } + case Node::NK_Assert: { + const auto *Assert = dynamic_cast(Root); + auto *Cond = lower(Assert->Cond); + auto *Body = lower(Assert->Body); + auto *NixAssert = + Ctx.Pool.record(new nix::ExprAssert(Assert->Range.Begin, Cond, Body)); + return NixAssert; + } + case Node::NK_With: { + const auto *With = dynamic_cast(Root); + auto *Attrs = lower(With->Attrs); + auto *Body = lower(With->Body); + auto *NixWith = + Ctx.Pool.record(new nix::ExprWith(With->Range.Begin, Attrs, Body)); + return NixWith; + } + case Node::NK_AttrSet: { + const auto *AttrSet = dynamic_cast(Root); + assert(AttrSet->Binds && "null Binds of the AttrSet!"); + ExprAttrsBuilder Builder(*this, AttrSet->Range, AttrSet->Recursive, + /*IsLet=*/false); + Builder.addBinds(*AttrSet->Binds); + return Builder.finish(); + } + case Node::NK_Int: { + const auto *Int = dynamic_cast(Root); + auto *NixInt = new nix::ExprInt(Int->N); + Ctx.Pool.record(NixInt); + return NixInt; + } + case Node::NK_Let: { + const auto *Let = dynamic_cast(Root); + ExprAttrsBuilder Builder(*this, Let->Binds->Range, /*Recursive=*/true, + /*IsLet=*/true); + Builder.addBinds(*Let->Binds); + nix::ExprAttrs *Attrs = Builder.finish(); + nix::Expr *Body = lower(Let->Body); + + // special work for let expression, the expression there is implictly + // recursive, and we should not mark `rec` to the evaluator, because the + // official parser does not do this also. + Attrs->recursive = false; + return Ctx.Pool.record(new nix::ExprLet(Attrs, Body)); + } + case Node::NK_LegacyLet: { + // let { ..., .body = ... } -> rec { ..., body = ... }.body + const auto *LegacyLet = dynamic_cast(Root); + ExprAttrsBuilder Builder(*this, LegacyLet->Binds->Range, + /*Recursive=*/true, /*IsLet=*/false); + Builder.addBinds(*LegacyLet->Binds); + nix::ExprAttrs *Attrs = Builder.finish(); + return Ctx.Pool.record( + new nix::ExprSelect(nix::noPos, Attrs, STable.create("body"))); + } + case Node::NK_If: { + const auto *If = dynamic_cast(Root); + auto *Cond = lower(If->Cond); + auto *Then = lower(If->Then); + auto *Else = lower(If->Else); + auto *NixIf = + Ctx.Pool.record(new nix::ExprIf(If->Range.Begin, Cond, Then, Else)); + return NixIf; + } + + // operators + case Node::NK_OpNot: + case Node::NK_OpNegate: + case Node::NK_OpHasAttr: +#define BIN_OP(NAME, _) case Node::NK_##NAME: +#include "nixd/Syntax/BinaryOps.inc" +#undef BIN_OP + { + return lowerOp(Root); + } + + case Node::NK_Call: { + const auto *Call = dynamic_cast(Root); + nix::Expr *Fn = lower(Call->Fn); + std::vector Args; + + for (Node *Arg : Call->Args) + Args.emplace_back(lower(Arg)); + + auto *NixCall = new nix::ExprCall(Call->Range.Begin, Fn, std::move(Args)); + return Ctx.Pool.record(NixCall); + } + case Node::NK_Select: { + const auto *Select = dynamic_cast(Root); + nix::Expr *Body = lower(Select->Body); + nix::Expr *Default = lower(Select->Default); + nix::PosIdx P = Select->Range.Begin; + assert(Select->Path && "Select->Path must be non-null!"); + nix::AttrPath Path = lowerAttrPath(*Select->Path); + auto *NixSelect = new nix::ExprSelect(P, Body, std::move(Path), Default); + return Ctx.Pool.record(NixSelect); + } + case Node::NK_Variable: { + const auto *Var = dynamic_cast(Root); + nix::Expr *Ret; + nix::PosIdx P = Var->Range.Begin; + nix::Symbol Sym = Var->ID->Symbol; + if (Sym == STable.create(CurPos)) + Ret = new nix::ExprPos(P); + else + Ret = new nix::ExprVar(P, Sym); + return Ctx.Pool.record(Ret); + } + case Node::NK_String: { + const auto *String = dynamic_cast(Root); + auto *NixString = new nix::ExprString(std::string(String->S)); + return Ctx.Pool.record(NixString); + } + case Node::NK_ConcatStrings: { + const auto *CS = dynamic_cast(Root); + nix::PosIdx P = CS->Range.Begin; + auto *ES = Ctx.ESPool.record(new EvalContext::ES); + for (Node *SubStr : CS->SubStrings) { + auto *NixSubStr = lower(SubStr); + ES->emplace_back(std::pair{SubStr->Range.Begin, NixSubStr}); + } + auto *NixConcatStrings = new nix::ExprConcatStrings(P, CS->ForceString, ES); + return Ctx.Pool.record(NixConcatStrings); + } + case Node::NK_InterpExpr: { + const auto *IE = dynamic_cast(Root); + return lower(IE->Body); + } + case Node::NK_IndStringParts: { + const auto *ISP = dynamic_cast(Root); + return stripIndentation(*ISP); + } + case Node::NK_Path: { + const auto *Path = dynamic_cast(Root); + const std::string &S = Path->S; + + // conformance hack for offical parser. + // add back in the trailing '/' to the first segment + nix::Path NixPath(nix::absPath(S, BasePath.path.abs())); + if (S.length() > 1 && S.back() == '/') + NixPath += "/"; + + return Ctx.Pool.record(new nix::ExprPath(std::move(NixPath))); + } + case Node::NK_SPath: { + // -> (__findPath __nixPath nixpkgs) + const auto *SPath = dynamic_cast(Root); + + // strip '<' '>' + std::string Path = SPath->S.substr(1, SPath->S.length() - 2); + + nix::PosIdx Pos = SPath->Range.Begin; + nix::ExprVar *Fn = mkVar(FindFile); + nix::ExprVar *NP = mkVar(NixPath); + auto *Arg = Ctx.Pool.record(new nix::ExprString(std::move(Path))); + return Ctx.Pool.record(new nix::ExprCall(Pos, Fn, {NP, Arg})); + } + case Node::NK_HPath: { + // HPath, ~/. -> $HOME + const auto *HPath = dynamic_cast(Root); + nix::Path Path(nix::getHome() + HPath->S.substr(1, HPath->S.length() - 1)); + return Ctx.Pool.record(new nix::ExprPath(std::move(Path))); + } + case Node::NK_List: { + // [ a b c ] + const auto *List = dynamic_cast(Root); + auto *Ret = Ctx.Pool.record(new nix::ExprList); + for (Node *Elem : List->Body->Elems) { + Ret->elems.emplace_back(lower(Elem)); + } + return Ret; + } + case Node::NK_URI: { + const auto *URI = dynamic_cast(Root); + auto *NixString = new nix::ExprString(std::string(URI->S)); + return Ctx.Pool.record(NixString); + } + case Node::NK_Braced: { + const auto *Braced = dynamic_cast(Root); + return lower(Braced->Body); + } + default: + llvm_unreachable("don't know how to lowering this node!"); + + } // switch + + return nullptr; +} + +} // namespace nixd diff --git a/nixd/lib/Sema/meson.build b/nixd/lib/Sema/meson.build index 2313d3d56..39f5d638b 100644 --- a/nixd/lib/Sema/meson.build +++ b/nixd/lib/Sema/meson.build @@ -1,11 +1,7 @@ -libnixdSemaDeps = [ nixd_lsp_server - , nix_all - , nixdAST - , llvm - ] +libnixdSemaDeps = [ nixdSyntax, nixdBasic ] libnixdSema = library('nixdSema' -, 'CompletionBuilder.cpp' +, 'Lowering.cpp' , include_directories: nixd_inc , dependencies: libnixdSemaDeps , install: true diff --git a/nixd/lib/Syntax/Lexer/Lexer.l b/nixd/lib/Syntax/Lexer/Lexer.l index 2fe5c259c..6c575b648 100644 --- a/nixd/lib/Syntax/Lexer/Lexer.l +++ b/nixd/lib/Syntax/Lexer/Lexer.l @@ -63,11 +63,7 @@ or { return OR_KW; } yylval->N = boost::lexical_cast(yytext); } catch (const boost::bad_lexical_cast &) { auto Range = mkRange(*yylloc, *Data); - Diagnostic Diag; - Diag.Msg = llvm::formatv("invalid integer {0}", yytext); - Diag.Kind = Diagnostic::Error; - Diag.Range = Range; - Data->Diags.emplace_back(std::move(Diag)); + Data->Diags.diag(Range, Diagnostic::DK_InvalidInteger) << std::string(yytext); yyterminate(); } return INT; @@ -77,11 +73,7 @@ or { return OR_KW; } yylval->NF = strtod(yytext, 0); if (errno != 0) { auto Range = mkRange(*yylloc, *Data); - Diagnostic Diag; - Diag.Msg = llvm::formatv("invalid float {0}", yytext); - Diag.Kind = Diagnostic::Error; - Diag.Range = Range; - Data->Diags.emplace_back(std::move(Diag)); + Data->Diags.diag(Range, Diagnostic::DK_InvalidFloat) << std::string(yytext); yyterminate(); } return FLOAT; @@ -210,11 +202,7 @@ or { return OR_KW; } {ANY} | <> { auto Range = mkRange(*yylloc, *Data); - Diagnostic Diag; - Diag.Msg = llvm::formatv("path has a trailing slash"); - Diag.Kind = Diagnostic::Error; - Diag.Range = Range; - Data->Diags.emplace_back(std::move(Diag)); + Data->Diags.diag(Range, Diagnostic::DK_PathTrailingSlash); yyterminate(); } diff --git a/nixd/lib/Syntax/Lexer/Prologue.cpp b/nixd/lib/Syntax/Lexer/Prologue.cpp index 98ee05420..67b4294b3 100644 --- a/nixd/lib/Syntax/Lexer/Prologue.cpp +++ b/nixd/lib/Syntax/Lexer/Prologue.cpp @@ -1,7 +1,6 @@ #include "Parser.tab.h" #include "nixd/Support/Position.h" -#include "nixd/Syntax/Diagnostic.h" #include "nixd/Syntax/Nodes.h" #include "nixd/Syntax/Parser/Require.h" @@ -10,7 +9,7 @@ #include -using nixd::syntax::Diagnostic; +using nixd::Diagnostic; #ifdef __clang__ #pragma clang diagnostic ignored "-Wunneeded-internal-declaration" diff --git a/nixd/lib/Syntax/Parser/Parser.y b/nixd/lib/Syntax/Parser/Parser.y index 0fde0431e..d1ae03342 100644 --- a/nixd/lib/Syntax/Parser/Parser.y +++ b/nixd/lib/Syntax/Parser/Parser.y @@ -288,24 +288,19 @@ expr_simple } | LET '{' binds '}' { auto N = decorateNode(new LegacyLet, *yylocp, *Data); - N->AttrBinds = $3; + N->Binds = $3; $$ = N; - - Diagnostic Diag; - Diag.Msg = "using deprecated `let' syntactic sugar `let {..., body = ...}' -> (rec {..., body = ...}).body'"; - Diag.Kind = Diagnostic::Warning; - Diag.Range = N->Range; - Data->Diags.emplace_back(std::move(Diag)); + Data->Diags.diag($$->Range, Diagnostic::DK_DeprecatedLet); } | REC '{' binds '}' { auto N = decorateNode(new AttrSet, *yylocp, *Data); - N->AttrBinds = $3; + N->Binds = $3; N->Recursive = true; $$ = N; } | '{' binds '}' { auto N = decorateNode(new AttrSet, *yylocp, *Data); - N->AttrBinds = $2; + N->Binds = $2; N->Recursive = false; $$ = N; } @@ -416,24 +411,14 @@ uri : URI { $$ = decorateNode(new nixd::syntax::URI, *yylocp, *Data); $$->S = std::string($1); - - Diagnostic Diag; - Diag.Msg = "URL literal is deprecated"; - Diag.Kind = Diagnostic::Warning; - Diag.Range = $$->Range; - Data->Diags.emplace_back(std::move(Diag)); + Data->Diags.diag($$->Range, Diagnostic::DK_DeprecatedURL); } identifier_or : OR_KW { $$ = decorateNode(new Identifier, *yylocp, *Data); $$->Symbol = Data->State.Symbols.create("or"); - - Diagnostic Diag; - Diag.Msg = "keyword `or` used as an identifier"; - Diag.Kind = Diagnostic::Warning; - Diag.Range = $$->Range; - Data->Diags.emplace_back(std::move(Diag)); + Data->Diags.diag($$->Range, Diagnostic::DK_OrIdentifier); } var_or: identifier_or { diff --git a/nixd/lib/Syntax/Parser/Prologue.cpp b/nixd/lib/Syntax/Parser/Prologue.cpp index c1ff1246e..c692a16ff 100644 --- a/nixd/lib/Syntax/Parser/Prologue.cpp +++ b/nixd/lib/Syntax/Parser/Prologue.cpp @@ -2,7 +2,7 @@ #include "Lexer.tab.h" -#include "nixd/Syntax/Diagnostic.h" +#include "nixd/Basic/Diagnostic.h" #include "nixd/Syntax/Nodes.h" #include "nixd/Syntax/Parser/Require.h" @@ -10,6 +10,8 @@ using namespace nixd::syntax; +using nixd::Diagnostic; + YY_DECL; /// Convert a yacc location (yylloc) to nix::PosIdx @@ -28,11 +30,7 @@ static nixd::RangeIdx mkRange(YYLTYPE YL, nixd::syntax::ParseData &Data) { void yyerror(YYLTYPE *YL, yyscan_t Scanner, nixd::syntax::ParseData *Data, const char *Error) { auto Range = mkRange(*YL, *Data); - Diagnostic Diag; - Diag.Msg = llvm::formatv("{0}", Error); - Diag.Kind = Diagnostic::Error; - Diag.Range = Range; - Data->Diags.emplace_back(std::move(Diag)); + Data->Diags.diag(Range, Diagnostic::DK_BisonParse) << std::string(Error); } template diff --git a/nixd/lib/Syntax/meson.build b/nixd/lib/Syntax/meson.build index c00af5097..4550f573c 100644 --- a/nixd/lib/Syntax/meson.build +++ b/nixd/lib/Syntax/meson.build @@ -19,7 +19,7 @@ parser = custom_target('parser' ) #------------------------------------------------------------------------------# -libnixdSyntaxDeps = [ nix_all, nixdExpr, nixdSupport ] +libnixdSyntaxDeps = [ nix_all, nixdBasic, nixdExpr, nixdSupport ] libnixdSyntaxInc = [ nixd_inc, './' ] libnixdSyntax = library('nixdSyntax' , lexer diff --git a/nixd/lib/meson.build b/nixd/lib/meson.build index 7d0f8f3e9..bede995f0 100644 --- a/nixd/lib/meson.build +++ b/nixd/lib/meson.build @@ -1,4 +1,5 @@ subdir('Expr') +subdir('Basic') subdir('Nix') subdir('Support') subdir('Syntax') diff --git a/nixd/tools/nixd-lint/meson.build b/nixd/tools/nixd-lint/meson.build index 7484227f5..8618ec6af 100644 --- a/nixd/tools/nixd-lint/meson.build +++ b/nixd/tools/nixd-lint/meson.build @@ -2,6 +2,8 @@ nixd_lint = executable('nixd-lint' , 'nixd-lint.cpp' , dependencies: [ boost , nixdSyntax + , nixdSema + , nixdExpr ] + nix_all , install: true ) diff --git a/nixd/tools/nixd-lint/nixd-lint.cpp b/nixd/tools/nixd-lint/nixd-lint.cpp index 392c2b598..338352ee0 100644 --- a/nixd/tools/nixd-lint/nixd-lint.cpp +++ b/nixd/tools/nixd-lint/nixd-lint.cpp @@ -1,4 +1,7 @@ -#include "nixd/Syntax/Diagnostic.h" +#include "nixd/Basic/Diagnostic.h" +#include "nixd/Expr/Expr.h" +#include "nixd/Sema/EvalContext.h" +#include "nixd/Sema/Lowering.h" #include "nixd/Syntax/Parser.h" #include "nixd/Syntax/Parser/Require.h" @@ -23,6 +26,12 @@ opt Filename(Positional, desc(""), init("-"), const OptionCategory *Cat[] = {&Misc}; +opt DumpNixAST("dump-nix-ast", init(false), + desc("Dump the lowered nix AST"), cat(Misc)); + +opt ShowPosition("show-position", init(false), + desc("Print location information"), cat(Misc)); + static void printCodeLines(std::ostream &Out, const std::string &Prefix, std::shared_ptr BeginPos, std::shared_ptr EndPos, @@ -31,15 +40,15 @@ static void printCodeLines(std::ostream &Out, const std::string &Prefix, // previous line of code. if (LOC.prevLineOfCode.has_value()) { Out << std::endl - << fmt("%1% %|2$5d|| %3%", Prefix, (BeginPos->line - 1), - *LOC.prevLineOfCode); + << nix::fmt("%1% %|2$5d|| %3%", Prefix, (BeginPos->line - 1), + *LOC.prevLineOfCode); } if (LOC.errLineOfCode.has_value()) { // line of code containing the error. Out << std::endl - << fmt("%1% %|2$5d|| %3%", Prefix, (BeginPos->line), - *LOC.errLineOfCode); + << nix::fmt("%1% %|2$5d|| %3%", Prefix, (BeginPos->line), + *LOC.errLineOfCode); // error arrows for the column range. if (BeginPos->column > 0) { auto Start = BeginPos->column; @@ -51,13 +60,13 @@ static void printCodeLines(std::ostream &Out, const std::string &Prefix, std::string arrows("^"); Out << std::endl - << fmt("%1% |%2%" ANSI_RED "%3%" ANSI_NORMAL, Prefix, Spaces, - arrows); + << nix::fmt("%1% |%2%" ANSI_RED "%3%" ANSI_NORMAL, Prefix, + Spaces, arrows); if (BeginPos->line == EndPos->line) { Out << ANSI_RED; for (auto I = BeginPos->column + 1; I < EndPos->column; I++) { - Out << (I == EndPos->column - 1 ? "^" : "~"); + Out << "~"; } Out << ANSI_NORMAL; } @@ -67,8 +76,103 @@ static void printCodeLines(std::ostream &Out, const std::string &Prefix, // next line of code. if (LOC.nextLineOfCode.has_value()) { Out << std::endl - << fmt("%1% %|2$5d|| %3%", Prefix, (BeginPos->line + 1), - *LOC.nextLineOfCode); + << nix::fmt("%1% %|2$5d|| %3%", Prefix, (BeginPos->line + 1), + *LOC.nextLineOfCode); + } +} + +struct ASTDump : nixd::RecursiveASTVisitor { + nix::SymbolTable &STable; + nix::PosTable &PTable; + int Depth = 0; + + bool traverseExpr(const nix::Expr *E) { + Depth++; + if (!nixd::RecursiveASTVisitor::traverseExpr(E)) + return false; + Depth--; + return true; + } + + bool visitExpr(const nix::Expr *E) const { + if (!E) + return true; + for (int I = 0; I < Depth; I++) { + std::cout << " "; + } + std::cout << nixd::getExprName(E) << ": "; + E->show(STable, std::cout); + + if (ShowPosition) { + std::cout << " "; + showPosition(*E); + } + + std::cout << " "; + std::cout << "\n"; + return true; + } + + void showPosition(const nix::Expr &E) const { + nix::PosIdx PID = E.getPos(); + if (PID != nix::noPos) { + nix::Pos Pos = PTable[PID]; + std::cout << Pos.line << ":" << Pos.column; + } + } +}; + +void printNote(nixd::Note &Note, nix::SymbolTable &STable, + nix::PosTable &PTable) { + auto NoteBegin = PTable[Note.range().Begin]; + auto NoteEnd = PTable[Note.range().End]; + std::cout << "\n"; + std::cout << ANSI_CYAN "note: " ANSI_NORMAL; + std::cout << Note.format() << "\n"; + + if (NoteBegin) { + std::cout << "\n" + << ANSI_BLUE << "at " ANSI_WARNING << NoteBegin << ANSI_NORMAL + << ":"; + if (auto Lines = + std::shared_ptr(NoteBegin)->getCodeLines()) { + std::cout << "\n"; + printCodeLines(std::cout, "", NoteBegin, NoteEnd, *Lines); + std::cout << "\n"; + } + } +} + +void printDiag(nixd::Diagnostic::Severity Serverity, nixd::Diagnostic &Diag, + nix::SymbolTable &STable, nix::PosTable &PTable) { + auto BeginPos = PTable[Diag.range().Begin]; + auto EndPos = PTable[Diag.range().End]; + switch (Serverity) { + case nixd::Diagnostic::DS_Warning: + std::cout << ANSI_WARNING "warning: " ANSI_NORMAL; + break; + case nixd::Diagnostic::DS_Error: + std::cout << ANSI_RED "error: " ANSI_NORMAL; + break; + case nixd::Diagnostic::DS_Fatal: + std::cout << ANSI_RED "fatal: " ANSI_NORMAL; + break; + } + std::cout << Diag.format() << "\n"; + if (BeginPos) { + std::cout << "\n" + << ANSI_BLUE << "at " ANSI_WARNING << BeginPos << ANSI_NORMAL + << ":"; + if (auto Lines = + std::shared_ptr(BeginPos)->getCodeLines()) { + std::cout << "\n"; + printCodeLines(std::cout, "", BeginPos, EndPos, *Lines); + std::cout << "\n"; + } + } + + for (const auto &Note : Diag.notes()) { + printNote(*Note, STable, PTable); } } @@ -97,49 +201,35 @@ int main(int argc, char *argv[]) { nixd::syntax::ParseData Data{.State = S, .Origin = Origin}; nixd::syntax::parse(Buffer, &Data); + nixd::EvalContext Ctx; + nixd::Lowering Lowering{ + .STable = *STable, + .PTable = *PTable, + .Diags = Data.Diags, + .Ctx = Ctx, + .BasePath = nix::SourcePath(nix::CanonPath(BasePath.string()))}; + nix::Expr *NixTree = Lowering.lower(Data.Result); + + if (DumpNixAST) { + ASTDump D{.STable = *STable, .PTable = *PTable}; + D.traverseExpr(NixTree); + } + + for (const auto &Err : Data.Diags.fatals()) { + printDiag(nixd::Diagnostic::Severity::DS_Fatal, *Err, *STable, *PTable); + } + + for (const auto &Err : Data.Diags.errors()) { + printDiag(nixd::Diagnostic::Severity::DS_Error, *Err, *STable, *PTable); + } + + for (const auto &Err : Data.Diags.warnings()) { + printDiag(nixd::Diagnostic::Severity::DS_Warning, *Err, *STable, *PTable); + } + /* for (const auto &Diag : Data.Diags) { - auto BeginPos = (*PTable)[Diag.Range.Begin]; - auto EndPos = (*PTable)[Diag.Range.End]; - switch (Diag.Kind) { - case nixd::syntax::Diagnostic::Warning: - std::cout << ANSI_WARNING "warning: " ANSI_NORMAL; - break; - case nixd::syntax::Diagnostic::Error: - std::cout << ANSI_RED "error: " ANSI_NORMAL; - break; - } - std::cout << Diag.Msg << "\n"; - if (BeginPos) { - std::cout << "\n" - << ANSI_BLUE << "at " ANSI_WARNING << BeginPos << ANSI_NORMAL - << ":"; - if (auto Lines = - std::shared_ptr(BeginPos)->getCodeLines()) { - std::cout << "\n"; - printCodeLines(std::cout, "", BeginPos, EndPos, *Lines); - std::cout << "\n"; - } - } - for (const auto &Note : Diag.Notes) { - auto NoteBegin = (*PTable)[Note.Range.Begin]; - auto NoteEnd = (*PTable)[Note.Range.End]; - std::cout << "\n"; - std::cout << ANSI_CYAN "note: " ANSI_NORMAL; - std::cout << Note.Msg << "\n"; - - if (NoteBegin) { - std::cout << "\n" - << ANSI_BLUE << "at " ANSI_WARNING << NoteBegin << ANSI_NORMAL - << ":"; - if (auto Lines = - std::shared_ptr(NoteBegin)->getCodeLines()) { - std::cout << "\n"; - printCodeLines(std::cout, "", NoteBegin, NoteEnd, *Lines); - std::cout << "\n"; - } - } - } } + */ return 0; } diff --git a/nixd/tools/nixd-lint/test/bind-inherit-dup.nix b/nixd/tools/nixd-lint/test/bind-inherit-dup.nix new file mode 100644 index 000000000..82040c0ec --- /dev/null +++ b/nixd/tools/nixd-lint/test/bind-inherit-dup.nix @@ -0,0 +1,9 @@ +# RUN: nixd-lint %s | FileCheck %s + +{ + inherit aaa bbb; + + # CHECK: duplicated attr `aaa` + # CHECK: previously defined here + inherit "aaa"; +} diff --git a/nixd/tools/nixd-lint/test/bind-inherit-dynamic.nix b/nixd/tools/nixd-lint/test/bind-inherit-dynamic.nix new file mode 100644 index 000000000..13afae17d --- /dev/null +++ b/nixd/tools/nixd-lint/test/bind-inherit-dynamic.nix @@ -0,0 +1,6 @@ +# RUN: nixd-lint %s | FileCheck %s + +{ + # CHECK: error: dynamic attributes are not allowed in inherit + inherit "foo${a}bar"; +} diff --git a/nixd/tools/nixd-lint/test/bind-inherit-empty.nix b/nixd/tools/nixd-lint/test/bind-inherit-empty.nix new file mode 100644 index 000000000..c0ed3f850 --- /dev/null +++ b/nixd/tools/nixd-lint/test/bind-inherit-empty.nix @@ -0,0 +1,6 @@ +# RUN: nixd-lint %s | FileCheck %s + +{ + # CHECK: empty inherit expression + inherit; +} diff --git a/nixd/tools/nixd-lint/test/bind-inherit.nix b/nixd/tools/nixd-lint/test/bind-inherit.nix new file mode 100644 index 000000000..b1d900116 --- /dev/null +++ b/nixd/tools/nixd-lint/test/bind-inherit.nix @@ -0,0 +1,10 @@ +# RUN: nixd-lint -dump-nix-ast %s | FileCheck %s + +# CHECK: ExprAttrs: { inherit a ; inherit b ; inherit bar ; inherit c ; inherit foo ; } +{ + # CHECK: ExprVar: a + inherit a b c bar; + + # CHECK: ExprVar: foo + inherit "foo"; +} diff --git a/nixd/tools/nixd-lint/test/duplicated-fn-arg.nix b/nixd/tools/nixd-lint/test/duplicated-fn-arg.nix new file mode 100644 index 000000000..f5bc93c97 --- /dev/null +++ b/nixd/tools/nixd-lint/test/duplicated-fn-arg.nix @@ -0,0 +1,10 @@ +# RUN: nixd-lint %s | FileCheck %s + +# CHECK: duplicated function formal declaration +a @ { b + # CHECK: previously declared here +, a ? 1 + # CHECK: function argument duplicated to a function forma +, a + # CHECK: previously declared here +}: { } diff --git a/nixd/tools/nixd-lint/test/legacy-let.nix b/nixd/tools/nixd-lint/test/legacy-let.nix index 84c4fe578..a94b0e52d 100644 --- a/nixd/tools/nixd-lint/test/legacy-let.nix +++ b/nixd/tools/nixd-lint/test/legacy-let.nix @@ -1,5 +1,9 @@ # RUN: nixd-lint %s | FileCheck %s +# RUN: nixd-lint -dump-nix-ast %s | FileCheck %s --check-prefix=AST +# CHECK: syntax error, unexpected ';', expecting end of file + +# AST: (rec { a = 1; }).body let { a = 1; } # CHECK: using deprecated `let' -; # CHECK: syntax error, unexpected ';', expecting end of file +; diff --git a/nixd/tools/nixd-lint/test/let-dynamic.nix b/nixd/tools/nixd-lint/test/let-dynamic.nix new file mode 100644 index 000000000..5d4a8dbd0 --- /dev/null +++ b/nixd/tools/nixd-lint/test/let-dynamic.nix @@ -0,0 +1,8 @@ +# RUN: nixd-lint %s | FileCheck %s + +# CHECK: dynamic attributes are not allowed in let ... in ... expression +let + ${1} = 3; + a = 1; +in +1 diff --git a/nixd/tools/nixd-lint/test/let-simple.nix b/nixd/tools/nixd-lint/test/let-simple.nix new file mode 100644 index 000000000..69ac262c9 --- /dev/null +++ b/nixd/tools/nixd-lint/test/let-simple.nix @@ -0,0 +1,9 @@ +# RUN: nixd-lint -dump-nix-ast %s | FileCheck %s + +# CHECK: ExprLet: (let x = 1; y = 2; z = 3; in 1) +let + x = 1; + y = 2; + z = 3; +in +1 diff --git a/nixd/tools/nixd-lint/test/lowering-assert.nix b/nixd/tools/nixd-lint/test/lowering-assert.nix new file mode 100644 index 000000000..5ea4d7a73 --- /dev/null +++ b/nixd/tools/nixd-lint/test/lowering-assert.nix @@ -0,0 +1,4 @@ +# RUN: nixd-lint %s | FileCheck %s + +# CHECK: function argument duplicated to a function formal +assert 0; a @ { a }: 1 diff --git a/nixd/tools/nixd-lint/test/lowering-attrset-dynamic.nix b/nixd/tools/nixd-lint/test/lowering-attrset-dynamic.nix new file mode 100644 index 000000000..dbc253406 --- /dev/null +++ b/nixd/tools/nixd-lint/test/lowering-attrset-dynamic.nix @@ -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; +} diff --git a/nixd/tools/nixd-lint/test/lowering-attrset-merge-inherit.nix b/nixd/tools/nixd-lint/test/lowering-attrset-merge-inherit.nix new file mode 100644 index 000000000..0217c4aed --- /dev/null +++ b/nixd/tools/nixd-lint/test/lowering-attrset-merge-inherit.nix @@ -0,0 +1,7 @@ +# RUN: nixd-lint %s | FileCheck %s +{ + inherit aa; + + # CHECK: duplicated attr `aa` + aa = 1; +} diff --git a/nixd/tools/nixd-lint/test/lowering-attrset-merging-2.nix b/nixd/tools/nixd-lint/test/lowering-attrset-merging-2.nix new file mode 100644 index 000000000..adcf2d53c --- /dev/null +++ b/nixd/tools/nixd-lint/test/lowering-attrset-merging-2.nix @@ -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; +} diff --git a/nixd/tools/nixd-lint/test/lowering-attrset-rec-2.nix b/nixd/tools/nixd-lint/test/lowering-attrset-rec-2.nix new file mode 100644 index 000000000..8f3f1eb5c --- /dev/null +++ b/nixd/tools/nixd-lint/test/lowering-attrset-rec-2.nix @@ -0,0 +1,14 @@ +# RUN: nixd-lint %s | FileCheck %s +{ + + foo = rec { + x = 1; + y = 2; + }; + + # CHECK: merging two attributes with different `rec` modifiers, the latter will be implicitly ignored + # CHECK: this attribute set is recursive + # CHECK: while this attribute set is marked as non-recursive, it will be considered as recursive + foo.bar = 1; + +} diff --git a/nixd/tools/nixd-lint/test/lowering-attrset-rec-3.nix b/nixd/tools/nixd-lint/test/lowering-attrset-rec-3.nix new file mode 100644 index 000000000..0925de110 --- /dev/null +++ b/nixd/tools/nixd-lint/test/lowering-attrset-rec-3.nix @@ -0,0 +1,15 @@ +# RUN: nixd-lint %s | FileCheck %s +{ + + foo = rec { + x = 1; + y = 2; + }; + + # CHECK: merging two attributes with different `rec` modifiers, the latter will be implicitly ignored + # CHECK: this attribute set is recursive + # CHECK: while this attribute set is marked as non-recursive, it will be considered as recursive + foo = { + bar = 1; + }; +} diff --git a/nixd/tools/nixd-lint/test/lowering-attrset-rec.nix b/nixd/tools/nixd-lint/test/lowering-attrset-rec.nix new file mode 100644 index 000000000..9dce853d5 --- /dev/null +++ b/nixd/tools/nixd-lint/test/lowering-attrset-rec.nix @@ -0,0 +1,13 @@ +# RUN: nixd-lint %s | FileCheck %s +rec { + foo.bar = 1; + + # CHECK: merging two attributes with different `rec` modifiers, the latter will be implicitly ignored + + # CHECK: this attribute set is non-recursive + # CHECK: while this attribute set is marked as recursive, it will be considered as non-recursive + foo = rec { + x = 1; + y = 2; + }; +} diff --git a/nixd/tools/nixd-lint/test/lowering-attrset-select-merging.nix b/nixd/tools/nixd-lint/test/lowering-attrset-select-merging.nix new file mode 100644 index 000000000..1113f408d --- /dev/null +++ b/nixd/tools/nixd-lint/test/lowering-attrset-select-merging.nix @@ -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; x = 1; } + a.b = 1; + a.c = 2; + + a = { + x = 1; + }; +} diff --git a/nixd/tools/nixd-lint/test/lowering-call.nix b/nixd/tools/nixd-lint/test/lowering-call.nix new file mode 100644 index 000000000..d26a7320c --- /dev/null +++ b/nixd/tools/nixd-lint/test/lowering-call.nix @@ -0,0 +1,4 @@ +# RUN: nixd-lint -dump-nix-ast %s | FileCheck %s + +# CHECK: ExprCall: (2 1) +2 1 diff --git a/nixd/tools/nixd-lint/test/lowering-indstring.nix b/nixd/tools/nixd-lint/test/lowering-indstring.nix new file mode 100644 index 000000000..ee51ca832 --- /dev/null +++ b/nixd/tools/nixd-lint/test/lowering-indstring.nix @@ -0,0 +1,16 @@ +# RUN: nixd-lint -dump-nix-ast %s | FileCheck %s + +{ + # CHECK: ExprString: "asdada\nasdasd\n" + foo = '' + asdada + asdasd + ''; + + # CHECK: ExprConcatStrings: ("asdasd\nasdasdasd\nasdasdasd\n" + a) + bar = '' +asdasd +asdasdasd +asdasdasd +${a}''; +} diff --git a/nixd/tools/nixd-lint/test/lowering-ops.nix b/nixd/tools/nixd-lint/test/lowering-ops.nix new file mode 100644 index 000000000..2e89de1e5 --- /dev/null +++ b/nixd/tools/nixd-lint/test/lowering-ops.nix @@ -0,0 +1,62 @@ +# RUN: nixd-lint -dump-nix-ast %s | FileCheck %s +# RUN: nixd-lint -dump-nix-ast -show-position %s | FileCheck --check-prefix=POSITION %s + +{ + # POSITION: ExprCall: (__sub 0 1) 6:14 + Position = -1; + + # CHECK: ExprOpNot: (! 1) + Not = !1; + + # CHECK: ExprCall: (__sub 0 1) + Negate = -1; + + # CHECK: ExprOpEq: (1 == 1) + Eq = 1 == 1; + + # CHECK: ExprOpNEq: (1 != 1) + NEq = 1 != 1; + + # CHECK: ExprCall: (__lessThan 2 1) + Ge = 1 > 2; + + # CHECK: ExprCall: (__lessThan 1 2) + Le = 1 < 2; + + # CHECK: (! (__lessThan 2 1)) + Leq = 1 <= 2; + + # CHECK: ExprOpNot: (! (__lessThan 1 2)) + Geq = 1 >= 2; + + # CHECK: ExprOpAnd: (1 && 2) + And = 1 && 2; + + # CHECK: ExprOpOr: (1 || 2) + Or = 1 || 2; + + # CHECK: ExprOpImpl: (1 -> 2) + Impl = 1 -> 2; + + # CHECK: ExprOpUpdate: (1 // 2) + Update = 1 // 2; + + # CHECK: ExprConcatStrings: (1 + 2) + Add = 1 + 2; + + # CHECK: ExprCall: (__sub 1 2) + Sub = 1 - 2; + + # CHECK: ExprCall: (__mul 1 2) + Mul = 1 * 2; + + # CHECK: ExprCall: (__div 1 2) + Div = 1 / 2; + + # CHECK: ExprOpConcatLists: (1 ++ 2) + Concat = 1 ++ 2; + + # TODO: check that we can deal with dynamic attrpath + # CHECK: ExprOpHasAttr: ((1) ? a.b.c) + HasAttr = 1 ? a.b.c; +} diff --git a/nixd/tools/nixd-lint/test/lowering-path.nix b/nixd/tools/nixd-lint/test/lowering-path.nix new file mode 100644 index 000000000..90c59829e --- /dev/null +++ b/nixd/tools/nixd-lint/test/lowering-path.nix @@ -0,0 +1,13 @@ +# RUN: nixd-lint -dump-nix-ast %s | FileCheck %s + +{ + # CHECK: lowering-path.nix + a = ./.; + + # CHECK: ExprCall: (__findFile __nixPath "nixpkgs") + spath = ; + + # CHECK-NOT: ~/foo.nix + # CHECK: foo.nix + hpath = ~/foo.nix; +} diff --git a/nixd/tools/nixd-lint/test/lowering-select.nix b/nixd/tools/nixd-lint/test/lowering-select.nix new file mode 100644 index 000000000..b5c15c926 --- /dev/null +++ b/nixd/tools/nixd-lint/test/lowering-select.nix @@ -0,0 +1,9 @@ +# RUN: nixd-lint -dump-nix-ast %s | FileCheck %s + +{ + # CHECK: ExprSelect: (1).a.b.c + a = 1 . a.b.c; + + # CHECK: ExprSelect: (1).a.b.c or ((2).foo.bar) + b = 1 . a.b.c or 2 . foo.bar; +} diff --git a/nixd/tools/nixd-lint/test/lowering-string.nix b/nixd/tools/nixd-lint/test/lowering-string.nix new file mode 100644 index 000000000..5930a872f --- /dev/null +++ b/nixd/tools/nixd-lint/test/lowering-string.nix @@ -0,0 +1,8 @@ +# RUN: nixd-lint -dump-nix-ast %s | FileCheck %s +{ + # CHECK: ExprConcatStrings: (aa + "bbb" + cc) + s1 = "${aa}bbb${cc}"; + + # CHECK: ExprString: "aabbcc" + s2 = "aabbcc"; +} diff --git a/nixd/tools/nixd-lint/test/lowering-variable.nix b/nixd/tools/nixd-lint/test/lowering-variable.nix new file mode 100644 index 000000000..20d62b7d1 --- /dev/null +++ b/nixd/tools/nixd-lint/test/lowering-variable.nix @@ -0,0 +1,9 @@ +# RUN: nixd-lint -dump-nix-ast %s | FileCheck %s + +{ + # CHECK: ExprVar: a + a = a; + + # CHECK: ExprPos: __curPos + p = __curPos; +} diff --git a/nixd/tools/nixd-lint/test/lowering-with.nix b/nixd/tools/nixd-lint/test/lowering-with.nix new file mode 100644 index 000000000..94418a533 --- /dev/null +++ b/nixd/tools/nixd-lint/test/lowering-with.nix @@ -0,0 +1,4 @@ +# RUN: nixd-lint %s | FileCheck %s + +# CHECK: function argument duplicated to a function formal +with 1; { a } @ a : 1