Skip to content

Commit 21f7b26

Browse files
authored
libnixf: give warnings when user code overrides built-in names (#722)
Fixes: #718
1 parent a03bc42 commit 21f7b26

File tree

3 files changed

+123
-15
lines changed

3 files changed

+123
-15
lines changed

libnixf/src/Basic/diagnostic.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,4 +252,16 @@ class Diagnostic(TypedDict):
252252
"severity": "Error",
253253
"message": "this is not a prelude builtin, the `builtins.` prefix is required",
254254
},
255+
{
256+
"sname": "sema-primop-overridden",
257+
"cname": "PrimOpOverridden",
258+
"severity": "Warning",
259+
"message": "overriding a builtin name `{}` is discouraged, rename it to avoid confusion",
260+
},
261+
{
262+
"sname": "sema-constant-overridden",
263+
"cname": "ConstantOverridden",
264+
"severity": "Warning",
265+
"message": "overriding a builtin name `{}` is discouraged, rename it to avoid confusion",
266+
},
255267
]

libnixf/src/Sema/VariableLookup.cpp

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,72 @@
44
#include "nixf/Basic/Nodes/Lambda.h"
55
#include "nixf/Sema/PrimOpInfo.h"
66

7+
#include <set>
8+
79
using namespace nixf;
810

911
namespace {
1012

13+
std::set<std::string> Constants{
14+
"builtins",
15+
// This is an undocumented keyword actually.
16+
"__curPos",
17+
18+
"true",
19+
"false",
20+
"null",
21+
"__currentTime",
22+
"__currentSystem",
23+
"__nixVersion",
24+
"__storeDir",
25+
"__langVersion",
26+
"__importNative",
27+
"__traceVerbose",
28+
"__nixPath",
29+
"derivation",
30+
};
31+
1132
/// Builder a map of definitions. If there are something overlapped, maybe issue
1233
/// a diagnostic.
1334
class DefBuilder {
1435
EnvNode::DefMap Def;
36+
std::vector<Diagnostic> &Diags;
37+
38+
std::shared_ptr<Definition> addSimple(std::string Name, const Node *Entry,
39+
Definition::DefinitionSource Source) {
40+
assert(!Def.contains(Name));
41+
auto NewDef = std::make_shared<Definition>(Entry, Source);
42+
Def.insert({std::move(Name), NewDef});
43+
return NewDef;
44+
}
1545

1646
public:
47+
DefBuilder(std::vector<Diagnostic> &Diags) : Diags(Diags) {}
48+
1749
void addBuiltin(std::string Name) {
1850
// Don't need to record def map for builtins.
19-
auto _ = add(std::move(Name), nullptr, Definition::DS_Builtin);
51+
auto _ = addSimple(std::move(Name), nullptr, Definition::DS_Builtin);
2052
}
2153

2254
[[nodiscard("Record ToDef Map!")]] std::shared_ptr<Definition>
2355
add(std::string Name, const Node *Entry,
2456
Definition::DefinitionSource Source) {
25-
assert(!Def.contains(Name));
26-
auto NewDef = std::make_shared<Definition>(Entry, Source);
27-
Def.insert({std::move(Name), NewDef});
28-
return NewDef;
57+
auto PrimOpLookup = lookupGlobalPrimOpInfo(Name);
58+
if (PrimOpLookup != PrimopLookupResult::NotFound) {
59+
// Overriding a builtin primop is discouraged.
60+
Diagnostic &D =
61+
Diags.emplace_back(Diagnostic::DK_PrimOpOverridden, Entry->range());
62+
D << Name;
63+
}
64+
65+
// Lookup constants
66+
if (Constants.contains(Name)) {
67+
Diagnostic &D =
68+
Diags.emplace_back(Diagnostic::DK_ConstantOverridden, Entry->range());
69+
D << Name;
70+
}
71+
72+
return addSimple(std::move(Name), Entry, Source);
2973
}
3074

3175
EnvNode::DefMap finish() { return std::move(Def); }
@@ -141,7 +185,7 @@ void VariableLookupAnalysis::dfs(const ExprLambda &Lambda,
141185
return;
142186

143187
// Create a new EnvNode, as lambdas may have formal & arg.
144-
DefBuilder DBuilder;
188+
DefBuilder DBuilder(Diags);
145189
assert(Lambda.arg());
146190
const LambdaArg &Arg = *Lambda.arg();
147191

@@ -212,7 +256,7 @@ std::shared_ptr<EnvNode> VariableLookupAnalysis::dfsAttrs(
212256
const Node *Syntax, Definition::DefinitionSource Source) {
213257
if (SA.isRecursive()) {
214258
// rec { }, or let ... in ...
215-
DefBuilder DB;
259+
DefBuilder DB(Diags);
216260
// For each static names, create a name binding.
217261
for (const auto &[Name, Attr] : SA.staticAttrs())
218262
ToDef.insert_or_assign(&Attr.key(), DB.add(Name, &Attr.key(), Source));
@@ -413,21 +457,16 @@ void VariableLookupAnalysis::dfs(const Node &Root,
413457

414458
void VariableLookupAnalysis::runOnAST(const Node &Root) {
415459
// Create a basic env
416-
DefBuilder DB;
417-
std::vector<std::string> BaseEnv{
418-
"builtins",
419-
// This is an undocumented keyword actually.
420-
"__curPos",
421-
};
460+
DefBuilder DB(Diags);
422461

423462
for (const auto &[Name, Info] : PrimOpsInfo) {
424463
if (!Info.Internal && !Name.starts_with("__")) {
425464
// Only add non-internal primops without "__" prefix.
426-
BaseEnv.push_back(Name);
465+
DB.addBuiltin(Name);
427466
}
428467
}
429468

430-
for (const auto &Builtin : BaseEnv)
469+
for (const auto &Builtin : Constants)
431470
DB.addBuiltin(Builtin);
432471

433472
auto Env = std::make_shared<EnvNode>(nullptr, DB.finish(), nullptr);

libnixf/test/Sema/VariableLookup.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,4 +398,61 @@ TEST_F(VLATest, Builtins_Unknown) {
398398
ASSERT_EQ(Diags[0].fixes().size(), 0);
399399
}
400400

401+
TEST_F(VLATest, PrimOp_Overridden) {
402+
const char *Src = R"(
403+
let
404+
__add = true;
405+
in __add
406+
)";
407+
408+
std::shared_ptr<Node> AST = parse(Src, Diags);
409+
VariableLookupAnalysis VLA(Diags);
410+
VLA.runOnAST(*AST);
411+
ASSERT_EQ(Diags.size(), 1);
412+
ASSERT_EQ(Diags[0].kind(), Diagnostic::DK_PrimOpOverridden);
413+
ASSERT_EQ(Diags[0].fixes().size(), 0);
414+
}
415+
416+
TEST_F(VLATest, Constant_true) {
417+
const char *Src = R"(true)";
418+
419+
std::shared_ptr<Node> AST = parse(Src, Diags);
420+
VariableLookupAnalysis VLA(Diags);
421+
VLA.runOnAST(*AST);
422+
ASSERT_EQ(Diags.size(), 0);
423+
}
424+
425+
TEST_F(VLATest, Constant_false) {
426+
const char *Src = R"(false)";
427+
428+
std::shared_ptr<Node> AST = parse(Src, Diags);
429+
VariableLookupAnalysis VLA(Diags);
430+
VLA.runOnAST(*AST);
431+
ASSERT_EQ(Diags.size(), 0);
432+
}
433+
434+
TEST_F(VLATest, Constant_null) {
435+
const char *Src = R"(null)";
436+
437+
std::shared_ptr<Node> AST = parse(Src, Diags);
438+
VariableLookupAnalysis VLA(Diags);
439+
VLA.runOnAST(*AST);
440+
ASSERT_EQ(Diags.size(), 0);
441+
}
442+
443+
TEST_F(VLATest, Constant_Overridden) {
444+
const char *Src = R"(
445+
let
446+
false = true;
447+
in false
448+
)";
449+
450+
std::shared_ptr<Node> AST = parse(Src, Diags);
451+
VariableLookupAnalysis VLA(Diags);
452+
VLA.runOnAST(*AST);
453+
ASSERT_EQ(Diags.size(), 1);
454+
ASSERT_EQ(Diags[0].kind(), Diagnostic::DK_ConstantOverridden);
455+
ASSERT_EQ(Diags[0].fixes().size(), 0);
456+
}
457+
401458
} // namespace

0 commit comments

Comments
 (0)