Skip to content

Commit

Permalink
libnixf: variable lookups (#372)
Browse files Browse the repository at this point in the history
  • Loading branch information
inclyc authored Mar 30, 2024
1 parent f39e6c2 commit 0abc071
Show file tree
Hide file tree
Showing 7 changed files with 672 additions and 12 deletions.
37 changes: 26 additions & 11 deletions libnixf/include/nixf/Basic/DiagnosticKinds.inc
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ DIAG("lex-float-no-exp", FloatNoExp, Fatal,
DIAG("lex-float-leading-zero", FloatLeadingZero, Warning,
"float begins with extra zeros `{}` is nixf extension")
DIAG("parse-expected", Expected, Error, "expected {}")
DIAG("parse-attrpath-extra-dot", AttrPathExtraDot, Error, "extra `.` at the end of attrpath")
DIAG("parse-select-extra-dot", SelectExtraDot, Error, "extra `.` after expression, but missing attrpath")
DIAG("parse-unexpected-between", UnexpectedBetween, Error, "unexpected {} between {} and {}")
DIAG("parse-attrpath-extra-dot", AttrPathExtraDot, Error,
"extra `.` at the end of attrpath")
DIAG("parse-select-extra-dot", SelectExtraDot, Error,
"extra `.` after expression, but missing attrpath")
DIAG("parse-unexpected-between", UnexpectedBetween, Error,
"unexpected {} between {} and {}")
DIAG("parse-unexpected", UnexpectedText, Error, "unexpected text")
DIAG("parse-missing-sep-formals", MissingSepFormals, Error, "missing seperator `,` between two lambda formals")
DIAG("parse-lambda-arg-extra-at", LambdaArgExtraAt, Error, "extra `@` for lambda arg")
DIAG("parse-missing-sep-formals", MissingSepFormals, Error,
"missing seperator `,` between two lambda formals")
DIAG("parse-lambda-arg-extra-at", LambdaArgExtraAt, Error,
"extra `@` for lambda arg")
DIAG("let-dynamic", LetDynamic, Error,
"dynamic attributes are not allowed in let ... in ... expression")
DIAG("empty-inherit", EmptyInherit, Warning, "empty inherit expression")
Expand All @@ -33,15 +38,25 @@ DIAG("merge-diff-rec", MergeDiffRec, Warning,
DIAG("bison", BisonParse, Fatal, "{}")
DIAG("invalid-float", InvalidFloat, Fatal, "invalid float {}")
DIAG("invalid-integer", InvalidInteger, Fatal, "invalid integer {}")
DIAG("sema-duplicated-attrname", DuplicatedAttrName, Error, "duplicated attrname `{}`")
DIAG("sema-duplicated-attrname", DuplicatedAttrName, Error,
"duplicated attrname `{}`")
DIAG("sema-dynamic-inherit", DynamicInherit, Error,
"dynamic attributes are not allowed in inherit")
DIAG("sema-empty-formal", EmptyFormal, Error, "empty formal")
DIAG("sema-formal-missing-comma", FormalMissingComma, Error, "missing `,` for lambda formal")
DIAG("sema-formal-extra-ellipsis", FormalExtraEllipsis, Error, "extra `...` for lambda formal")
DIAG("sema-misplaced-ellipsis", FormalMisplacedEllipsis, Error, "misplaced `...` for lambda formal")
DIAG("sema-dup-formal", DuplicatedFormal, Error,
"duplicated function formal")
DIAG("sema-formal-missing-comma", FormalMissingComma, Error,
"missing `,` for lambda formal")
DIAG("sema-formal-extra-ellipsis", FormalExtraEllipsis, Error,
"extra `...` for lambda formal")
DIAG("sema-misplaced-ellipsis", FormalMisplacedEllipsis, Error,
"misplaced `...` for lambda formal")
DIAG("sema-dup-formal", DuplicatedFormal, Error, "duplicated function formal")
DIAG("sema-dup-formal-arg", DuplicatedFormalToArg, Error,
"function argument duplicated to a function formal")
DIAG("sema-undefined-variable", UndefinedVariable, Error,
"undefined variable `{}`")
DIAG("sema-def-not-used", DefinitionNotUsed, Warning,
"definition `{}` is not used")
DIAG("sema-extra-rec", ExtraRecursive, Warning,
"attrset is not necessary to be `rec`ursive ")
DIAG("sema-extra-with", ExtraWith, Warning, "unused `with` expression")
#endif // DIAG
1 change: 1 addition & 0 deletions libnixf/include/nixf/Basic/Nodes/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ class ExprLet : public Expr {
[[nodiscard]] const Binds *binds() const {
return Attrs ? Attrs->binds() : nullptr;
}
[[nodiscard]] const ExprAttrs *attrs() const { return Attrs.get(); }
[[nodiscard]] const Expr *expr() const { return E.get(); }
[[nodiscard]] const Misc &let() const { return *KwLet; }
[[nodiscard]] const Misc *in() const { return KwIn.get(); }
Expand Down
2 changes: 1 addition & 1 deletion libnixf/include/nixf/Basic/Nodes/Lambda.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class LambdaArg : public Node {
std::shared_ptr<Formals> F)
: Node(NK_LambdaArg, Range), ID(std::move(ID)), F(std::move(F)) {}

[[nodiscard]] Identifier *id() { return ID.get(); }
[[nodiscard]] Identifier *id() const { return ID.get(); }

[[nodiscard]] Formals *formals() const { return F.get(); }

Expand Down
126 changes: 126 additions & 0 deletions libnixf/include/nixf/Sema/VariableLookup.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/// \file
/// \brief Lookup variable names, from it's parent scope.
///
/// This file declares a variable-lookup analysis on AST.
/// We do variable lookup for liveness checking, and emit diagnostics
/// like "unused with", or "undefined variable".
/// The implementation aims to be consistent with C++ nix (NixOS/nix).

#pragma once

#include "nixf/Basic/Diagnostic.h"
#include "nixf/Basic/Nodes/Attrs.h"
#include "nixf/Basic/Nodes/Basic.h"
#include "nixf/Basic/Nodes/Expr.h"
#include "nixf/Basic/Nodes/Lambda.h"
#include "nixf/Basic/Nodes/Simple.h"

#include <map>
#include <memory>
#include <string>
#include <vector>

namespace nixf {

/// \brief Represents a definition
class Definition {
std::vector<const ExprVar *> Uses;
const Node *Syntax;

public:
explicit Definition(const Node *Syntax) : Syntax(Syntax) {}
Definition(std::vector<const ExprVar *> Uses, const Node *Syntax)
: Uses(std::move(Uses)), Syntax(Syntax) {}

[[nodiscard]] const Node *syntax() const { return Syntax; }

[[nodiscard]] const std::vector<const ExprVar *> &uses() const {
return Uses;
}

void usedBy(const ExprVar &User) { Uses.emplace_back(&User); }

[[nodiscard]] bool isBuiltin() const { return Syntax; }
};

/// \brief A set of variable definitions, which may inherit parent environment.
class EnvNode {
public:
using DefMap = std::map<std::string, std::shared_ptr<Definition>>;

private:
const std::shared_ptr<EnvNode> Parent; // Points to the parent node.

DefMap Defs; // Definitions.

const Node *Syntax;

public:
EnvNode(std::shared_ptr<EnvNode> Parent, DefMap Defs, const Node *Syntax)
: Parent(std::move(Parent)), Defs(std::move(Defs)), Syntax(Syntax) {}

[[nodiscard]] EnvNode *parent() const { return Parent.get(); }

/// \brief Where this node comes from.
[[nodiscard]] const Node *syntax() const { return Syntax; }

[[nodiscard]] bool isWith() const {
return Syntax && Syntax->kind() == Node::NK_ExprWith;
}

[[nodiscard]] const DefMap &defs() const { return Defs; }

[[nodiscard]] bool isLive() const;
};

class VariableLookupAnalysis {
public:
enum class LookupResultKind {
Undefined,
FromWith,
Defined,
};

struct LookupResult {
LookupResultKind Kind;
std::shared_ptr<const Definition> Def;
};

private:
std::vector<Diagnostic> &Diags;

std::map<const Node *, std::shared_ptr<Definition>>
WithDefs; // record with ... ; users.

void lookupVar(const ExprVar &Var, const std::shared_ptr<EnvNode> &Env);

std::shared_ptr<EnvNode> dfsAttrs(const SemaAttrs &SA,
const std::shared_ptr<EnvNode> &Env,
const Node *Syntax);

void emitEnvLivenessWarning(const std::shared_ptr<EnvNode> &NewEnv);

void dfsDynamicAttrs(const std::vector<Attribute> &DynamicAttrs,
const std::shared_ptr<EnvNode> &Env);

// "dfs" is an abbreviation of "Deep-First-Search".
void dfs(const ExprLambda &Lambda, const std::shared_ptr<EnvNode> &Env);
void dfs(const ExprAttrs &Attrs, const std::shared_ptr<EnvNode> &Env);
void dfs(const ExprLet &Let, const std::shared_ptr<EnvNode> &Env);
void dfs(const ExprWith &With, const std::shared_ptr<EnvNode> &Env);

void dfs(const Node &Root, const std::shared_ptr<EnvNode> &Env);

void trivialDispatch(const Node &Root, const std::shared_ptr<EnvNode> &Env);

std::map<const ExprVar *, LookupResult> Results;

public:
VariableLookupAnalysis(std::vector<Diagnostic> &Diags);

void runOnAST(const Node &Root);

LookupResult query(const ExprVar &Var) { return Results.at(&Var); }
};

} // namespace nixf
2 changes: 2 additions & 0 deletions libnixf/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ libnixf = library(
'src/Parse/ParseStrings.cpp',
'src/Parse/ParseSupport.cpp',
'src/Sema/SemaActions.cpp',
'src/Sema/VariableLookup.cpp',
include_directories: libnixf_inc,
dependencies: libnixf_deps,
install: true,
Expand Down Expand Up @@ -59,6 +60,7 @@ test('unit/libnixf/Parse',
test('unit/libnixf/Sema',
executable('unit-libnixf-sema',
'test/Sema/SemaActions.cpp',
'test/Sema/VariableLookup.cpp',
dependencies: [ nixf, gtest_main ],
include_directories: [ 'src/Sema' ] # Private headers
)
Expand Down
Loading

0 comments on commit 0abc071

Please sign in to comment.