diff --git a/.clang-tidy b/.clang-tidy index ddd147ca1..ca4ee519e 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -8,6 +8,8 @@ Checks: > performance-*, portability-*, readability-*, + -llvm-else-after-return, + -readability-else-after-return, -readability-use-anyofallof, -misc-const-correctness, -misc-unused-parameters, diff --git a/libnixf/src/Sema/VariableLookup.cpp b/libnixf/src/Sema/VariableLookup.cpp index dfe045ef1..59491021d 100644 --- a/libnixf/src/Sema/VariableLookup.cpp +++ b/libnixf/src/Sema/VariableLookup.cpp @@ -76,41 +76,44 @@ void VariableLookupAnalysis::emitEnvLivenessWarning( void VariableLookupAnalysis::lookupVar(const ExprVar &Var, const std::shared_ptr &Env) { - const std::string &Name = Var.id().name(); - - bool EnclosedWith = false; // If there is a "With" enclosed this var name. - EnvNode *WithEnv = nullptr; - EnvNode *CurEnv = Env.get(); + const auto &Name = Var.id().name(); + const auto *CurEnv = Env.get(); std::shared_ptr Def; + std::vector WithEnvs; for (; CurEnv; CurEnv = CurEnv->parent()) { if (CurEnv->defs().contains(Name)) { Def = CurEnv->defs().at(Name); break; } - // Find the most nested `with` expression, and set uses. - if (CurEnv->isWith() && !EnclosedWith) { - EnclosedWith = true; - WithEnv = CurEnv; + // Find all nested "with" expression, variables potentially come from those. + // For example + // with lib; + // with builtins; + // generators <--- this variable may come from "lib" | "builtins" + // + // We cannot determine where it precisely come from, thus mark all Envs + // alive. + if (CurEnv->isWith()) { + WithEnvs.emplace_back(CurEnv); } } if (Def) { Def->usedBy(Var); Results.insert({&Var, LookupResult{LookupResultKind::Defined, Def}}); - return; - } - if (EnclosedWith) { - Def = WithDefs.at(WithEnv->syntax()); - Def->usedBy(Var); + } else if (!WithEnvs.empty()) { // comes from enclosed "with" expressions. + for (const auto *WithEnv : WithEnvs) { + Def = WithDefs.at(WithEnv->syntax()); + Def->usedBy(Var); + } Results.insert({&Var, LookupResult{LookupResultKind::FromWith, Def}}); - return; + } else { + // Otherwise, this variable is undefined. + Results.insert({&Var, LookupResult{LookupResultKind::Undefined, nullptr}}); + Diagnostic &Diag = + Diags.emplace_back(Diagnostic::DK_UndefinedVariable, Var.range()); + Diag << Var.id().name(); } - - // Otherwise, this variable is undefined. - Results.insert({&Var, LookupResult{LookupResultKind::Undefined, nullptr}}); - Diagnostic &Diag = - Diags.emplace_back(Diagnostic::DK_UndefinedVariable, Var.range()); - Diag << Var.id().name(); } void VariableLookupAnalysis::dfs(const ExprLambda &Lambda, diff --git a/libnixf/test/Sema/VariableLookup.cpp b/libnixf/test/Sema/VariableLookup.cpp index 2b1c3c48a..0861a3b22 100644 --- a/libnixf/test/Sema/VariableLookup.cpp +++ b/libnixf/test/Sema/VariableLookup.cpp @@ -324,4 +324,18 @@ in 1 ASSERT_EQ(Diags.size(), 1); } +TEST_F(VLATest, Issue606_NestedWith) { + const char *Src = R"( +with builtins; + with builtins; + concatLists + )"; + + std::shared_ptr AST = parse(Src, Diags); + VariableLookupAnalysis VLA(Diags); + VLA.runOnAST(*AST); + + ASSERT_EQ(Diags.size(), 0); +} + } // namespace diff --git a/nixd/docs/features.md b/nixd/docs/features.md index eeeab4985..7e87e00f3 100644 --- a/nixd/docs/features.md +++ b/nixd/docs/features.md @@ -32,13 +32,16 @@ Screenshots: ### Semantic Tokens +> [!WARNING] +> This feature is experimental and not enabled by default. + [nixd language server](https://github.com/nix-community/nixd) tries to make some tokens looks different by sending your editor some integers. However, types in nix language is pretty different from standard LSP types. So, as a result, attrnames, selection, variables are colored as different integers, but the colors may, or may not rendered properly in your editor. > [!TIP] -> `--semantic-tokens=false` to disable the feature. +> `--semantic-tokens=true` to enable the feature. #### Attribute name coloring diff --git a/nixd/lib/Controller/LifeTime.cpp b/nixd/lib/Controller/LifeTime.cpp index fb693ca1a..4c9b8ba20 100644 --- a/nixd/lib/Controller/LifeTime.cpp +++ b/nixd/lib/Controller/LifeTime.cpp @@ -38,7 +38,7 @@ opt DefaultNixOSOptionsExpr{ opt EnableSemanticTokens{"semantic-tokens", desc("Enable/Disable semantic tokens"), - init(true), cat(NixdCategory)}; + init(false), cat(NixdCategory)}; // Here we try to wrap nixpkgs, nixos options in a single emtpy attrset in test. std::string getDefaultNixpkgsExpr() { diff --git a/nixd/tools/nixd/test/initialize.md b/nixd/tools/nixd/test/initialize.md index 20058dce1..23eecf413 100644 --- a/nixd/tools/nixd/test/initialize.md +++ b/nixd/tools/nixd/test/initialize.md @@ -49,32 +49,6 @@ CHECK-NEXT: "referencesProvider": true, CHECK-NEXT: "renameProvider": { CHECK-NEXT: "prepareProvider": true CHECK-NEXT: }, -CHECK-NEXT: "semanticTokensProvider": { -CHECK-NEXT: "full": true, -CHECK-NEXT: "legend": { -CHECK-NEXT: "tokenModifiers": [ -CHECK-NEXT: "static", -CHECK-NEXT: "abstract", -CHECK-NEXT: "async" -CHECK-NEXT: ], -CHECK-NEXT: "tokenTypes": [ -CHECK-NEXT: "function", -CHECK-NEXT: "string", -CHECK-NEXT: "number", -CHECK-NEXT: "type", -CHECK-NEXT: "keyword", -CHECK-NEXT: "variable", -CHECK-NEXT: "interface", -CHECK-NEXT: "variable", -CHECK-NEXT: "regexp", -CHECK-NEXT: "macro", -CHECK-NEXT: "method", -CHECK-NEXT: "regexp", -CHECK-NEXT: "regexp" -CHECK-NEXT: ] -CHECK-NEXT: }, -CHECK-NEXT: "range": false -CHECK-NEXT: }, CHECK-NEXT: "textDocumentSync": { CHECK-NEXT: "change": 2, CHECK-NEXT: "openClose": true, diff --git a/nixd/tools/nixd/test/semantic-tokens/initialize.md b/nixd/tools/nixd/test/semantic-tokens/initialize.md new file mode 100644 index 000000000..5fa92e212 --- /dev/null +++ b/nixd/tools/nixd/test/semantic-tokens/initialize.md @@ -0,0 +1,107 @@ +# RUN: nixd --lit-test --semantic-tokens=true < %s | FileCheck %s + +Check basic handshake with the server, i.e. "initialize" + +<-- initialize(0) + +```json +{ + "jsonrpc":"2.0", + "id":0, + "method":"initialize", + "params":{ + "processId":123, + "rootPath":"", + "capabilities":{ }, + "trace":"off" + } +} +``` + +<-- reply:initialize(0) + +``` + CHECK: { +CHECK-NEXT: "id": 0, +CHECK-NEXT: "jsonrpc": "2.0", +CHECK-NEXT: "result": { +CHECK-NEXT: "capabilities": { +CHECK-NEXT: "codeActionProvider": { +CHECK-NEXT: "codeActionKinds": [ +CHECK-NEXT: "quickfix" +CHECK-NEXT: ], +CHECK-NEXT: "resolveProvider": false +CHECK-NEXT: }, +CHECK-NEXT: "completionProvider": { +CHECK-NEXT: "resolveProvider": true, +CHECK-NEXT: "triggerCharacters": [ +CHECK-NEXT: "." +CHECK-NEXT: ] +CHECK-NEXT: }, +CHECK-NEXT: "definitionProvider": true, +CHECK-NEXT: "documentFormattingProvider": true, +CHECK-NEXT: "documentHighlightProvider": true, +CHECK-NEXT: "documentLinkProvider": {}, +CHECK-NEXT: "documentSymbolProvider": true, +CHECK-NEXT: "hoverProvider": true, +CHECK-NEXT: "inlayHintProvider": true, +CHECK-NEXT: "referencesProvider": true, +CHECK-NEXT: "renameProvider": { +CHECK-NEXT: "prepareProvider": true +CHECK-NEXT: }, +CHECK-NEXT: "semanticTokensProvider": { +CHECK-NEXT: "full": true, +CHECK-NEXT: "legend": { +CHECK-NEXT: "tokenModifiers": [ +CHECK-NEXT: "static", +CHECK-NEXT: "abstract", +CHECK-NEXT: "async" +CHECK-NEXT: ], +CHECK-NEXT: "tokenTypes": [ +CHECK-NEXT: "function", +CHECK-NEXT: "string", +CHECK-NEXT: "number", +CHECK-NEXT: "type", +CHECK-NEXT: "keyword", +CHECK-NEXT: "variable", +CHECK-NEXT: "interface", +CHECK-NEXT: "variable", +CHECK-NEXT: "regexp", +CHECK-NEXT: "macro", +CHECK-NEXT: "method", +CHECK-NEXT: "regexp", +CHECK-NEXT: "regexp" +CHECK-NEXT: ] +CHECK-NEXT: }, +CHECK-NEXT: "range": false +CHECK-NEXT: }, +CHECK-NEXT: "textDocumentSync": { +CHECK-NEXT: "change": 2, +CHECK-NEXT: "openClose": true, +CHECK-NEXT: "save": true +CHECK-NEXT: } +CHECK-NEXT: } +CHECK-NEXT: "serverInfo": { +CHECK-NEXT: "name": "nixd", +CHECK-NEXT: "version": {{.*}} +CHECK-NEXT: } +CHECK-NEXT: } +CHECK-NEXT: } +``` + +<-- initialized + +```json +{ + "jsonrpc":"2.0", + "method":"initialized", + "params":{ + + } +} +``` + + +```json +{"jsonrpc":"2.0","method":"exit"} +```