[PATCH] D58185: [clangd] Handle unresolved scope specifier when fixing includes.

2019-02-19 Thread Eric Liu via Phabricator via cfe-commits
This revision was automatically updated to reflect the committed changes.
Closed by commit rL354330: [clangd] Handle unresolved scope specifier when 
fixing includes. (authored by ioeric, committed by ).
Herald added a project: LLVM.
Herald added a subscriber: llvm-commits.

Repository:
  rL LLVM

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D58185/new/

https://reviews.llvm.org/D58185

Files:
  clang-tools-extra/trunk/clangd/IncludeFixer.cpp
  clang-tools-extra/trunk/unittests/clangd/DiagnosticsTests.cpp

Index: clang-tools-extra/trunk/clangd/IncludeFixer.cpp
===
--- clang-tools-extra/trunk/clangd/IncludeFixer.cpp
+++ clang-tools-extra/trunk/clangd/IncludeFixer.cpp
@@ -19,6 +19,10 @@
 #include "clang/AST/Type.h"
 #include "clang/Basic/Diagnostic.h"
 #include "clang/Basic/DiagnosticSema.h"
+#include "clang/Basic/LangOptions.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Basic/TokenKinds.h"
 #include "clang/Sema/DeclSpec.h"
 #include "clang/Sema/Lookup.h"
 #include "clang/Sema/Scope.h"
@@ -28,6 +32,7 @@
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/None.h"
 #include "llvm/ADT/Optional.h"
+#include "llvm/ADT/StringRef.h"
 #include "llvm/ADT/StringSet.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/FormatVariadic.h"
@@ -172,6 +177,121 @@
   }
   return Fixes;
 }
+
+// Returns the identifiers qualified by an unresolved name. \p Loc is the
+// start location of the unresolved name. For the example below, this returns
+// "::X::Y" that is qualified by unresolved name "clangd":
+// clang::clangd::X::Y
+//~
+llvm::Optional qualifiedByUnresolved(const SourceManager &SM,
+  SourceLocation Loc,
+  const LangOptions &LangOpts) {
+  std::string Result;
+
+  SourceLocation NextLoc = Loc;
+  while (auto CCTok = Lexer::findNextToken(NextLoc, SM, LangOpts)) {
+if (!CCTok->is(tok::coloncolon))
+  break;
+auto IDTok = Lexer::findNextToken(CCTok->getLocation(), SM, LangOpts);
+if (!IDTok || !IDTok->is(tok::raw_identifier))
+  break;
+Result.append(("::" + IDTok->getRawIdentifier()).str());
+NextLoc = IDTok->getLocation();
+  }
+  if (Result.empty())
+return llvm::None;
+  return Result;
+}
+
+// An unresolved name and its scope information that can be extracted cheaply.
+struct CheapUnresolvedName {
+  std::string Name;
+  // This is the part of what was typed that was resolved, and it's in its
+  // resolved form not its typed form (think `namespace clang { clangd::x }` -->
+  // `clang::clangd::`).
+  llvm::Optional ResolvedScope;
+
+  // Unresolved part of the scope. When the unresolved name is a specifier, we
+  // use the name that comes after it as the alternative name to resolve and use
+  // the specifier as the extra scope in the accessible scopes.
+  llvm::Optional UnresolvedScope;
+};
+
+// Extracts unresolved name and scope information around \p Unresolved.
+// FIXME: try to merge this with the scope-wrangling code in CodeComplete.
+llvm::Optional extractUnresolvedNameCheaply(
+const SourceManager &SM, const DeclarationNameInfo &Unresolved,
+CXXScopeSpec *SS, const LangOptions &LangOpts, bool UnresolvedIsSpecifier) {
+  bool Invalid = false;
+  llvm::StringRef Code = SM.getBufferData(
+  SM.getDecomposedLoc(Unresolved.getBeginLoc()).first, &Invalid);
+  if (Invalid)
+return llvm::None;
+  CheapUnresolvedName Result;
+  Result.Name = Unresolved.getAsString();
+  if (SS && SS->isNotEmpty()) { // "::" or "ns::"
+if (auto *Nested = SS->getScopeRep()) {
+  if (Nested->getKind() == NestedNameSpecifier::Global)
+Result.ResolvedScope = "";
+  else if (const auto *NS = Nested->getAsNamespace()) {
+auto SpecifiedNS = printNamespaceScope(*NS);
+
+// Check the specifier spelled in the source.
+// If the resolved scope doesn't end with the spelled scope. The
+// resolved scope can come from a sema typo correction. For example,
+// sema assumes that "clangd::" is a typo of "clang::" and uses
+// "clang::" as the specified scope in:
+// namespace clang { clangd::X; }
+// In this case, we use the "typo" specifier as extra scope instead
+// of using the scope assumed by sema.
+auto B = SM.getFileOffset(SS->getBeginLoc());
+auto E = SM.getFileOffset(SS->getEndLoc());
+std::string Spelling = (Code.substr(B, E - B) + "::").str();
+if (llvm::StringRef(SpecifiedNS).endswith(Spelling))
+  Result.ResolvedScope = SpecifiedNS;
+else
+  Result.UnresolvedScope = Spelling;
+  } else if (const auto *ANS = Nested->getAsNamespaceAlias()) {
+Result.ResolvedScope = printNamespaceScope(*ANS->getNamespace());
+  } else {
+// We don't fix symbols in scopes that are not top-level 

[PATCH] D58185: [clangd] Handle unresolved scope specifier when fixing includes.

2019-02-19 Thread Eric Liu via Phabricator via cfe-commits
ioeric updated this revision to Diff 187359.
ioeric added a comment.

- minor cleanup


Repository:
  rCTE Clang Tools Extra

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D58185/new/

https://reviews.llvm.org/D58185

Files:
  clangd/IncludeFixer.cpp
  unittests/clangd/DiagnosticsTests.cpp

Index: unittests/clangd/DiagnosticsTests.cpp
===
--- unittests/clangd/DiagnosticsTests.cpp
+++ unittests/clangd/DiagnosticsTests.cpp
@@ -20,6 +20,7 @@
 namespace clangd {
 namespace {
 
+using testing::_;
 using testing::ElementsAre;
 using testing::Field;
 using testing::IsEmpty;
@@ -369,6 +370,8 @@
 $insert[[]]namespace ns {
 void foo() {
   $unqualified1[[X]] x;
+  // No fix if the unresolved type is used as specifier. (ns::)X::Nested will be
+  // considered the unresolved type.
   $unqualified2[[X]]::Nested n;
 }
 }
@@ -391,10 +394,7 @@
   AllOf(Diag(Test.range("unqualified1"), "unknown type name 'X'"),
 WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
 "Add include \"x.h\" for symbol ns::X"))),
-  AllOf(Diag(Test.range("unqualified2"),
- "use of undeclared identifier 'X'"),
-WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
-"Add include \"x.h\" for symbol ns::X"))),
+  Diag(Test.range("unqualified2"), "use of undeclared identifier 'X'"),
   AllOf(Diag(Test.range("qualified1"),
  "no type named 'X' in namespace 'ns'"),
 WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
@@ -487,6 +487,88 @@
   }
 }
 
+TEST(IncludeFixerTest, UnresolvedNameAsSpecifier) {
+  Annotations Test(R"cpp(
+$insert[[]]namespace ns {
+}
+void g() {  ns::$[[scope]]::X_Y();  }
+  )cpp");
+  auto TU = TestTU::withCode(Test.code());
+  auto Index = buildIndexWithSymbol(
+  SymbolWithHeader{"ns::scope::X_Y", "unittest:///x.h", "\"x.h\""});
+  TU.ExternalIndex = Index.get();
+
+  EXPECT_THAT(
+  TU.build().getDiagnostics(),
+  UnorderedElementsAre(AllOf(
+  Diag(Test.range(), "no member named 'scope' in namespace 'ns'"),
+  WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
+  "Add include \"x.h\" for symbol ns::scope::X_Y");
+}
+
+TEST(IncludeFixerTest, UnresolvedSpecifierWithSemaCorrection) {
+  Annotations Test(R"cpp(
+$insert[[]]namespace clang {
+void f() {
+  // "clangd::" will be corrected to "clang::" by Sema.
+  $q1[[clangd]]::$x[[X]] x;
+  $q2[[clangd]]::$ns[[ns]]::Y y;
+}
+}
+  )cpp");
+  auto TU = TestTU::withCode(Test.code());
+  auto Index = buildIndexWithSymbol(
+  {SymbolWithHeader{"clang::clangd::X", "unittest:///x.h", "\"x.h\""},
+   SymbolWithHeader{"clang::clangd::ns::Y", "unittest:///y.h", "\"y.h\""}});
+  TU.ExternalIndex = Index.get();
+
+  EXPECT_THAT(
+  TU.build().getDiagnostics(),
+  UnorderedElementsAre(
+  AllOf(
+  Diag(Test.range("q1"), "use of undeclared identifier 'clangd'; "
+ "did you mean 'clang'?"),
+  WithFix(_, // change clangd to clang
+  Fix(Test.range("insert"), "#include \"x.h\"\n",
+  "Add include \"x.h\" for symbol clang::clangd::X"))),
+  AllOf(
+  Diag(Test.range("x"), "no type named 'X' in namespace 'clang'"),
+  WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
+  "Add include \"x.h\" for symbol clang::clangd::X"))),
+  AllOf(
+  Diag(Test.range("q2"), "use of undeclared identifier 'clangd'; "
+ "did you mean 'clang'?"),
+  WithFix(
+  _, // change clangd to clangd
+  Fix(Test.range("insert"), "#include \"y.h\"\n",
+  "Add include \"y.h\" for symbol clang::clangd::ns::Y"))),
+  AllOf(Diag(Test.range("ns"),
+ "no member named 'ns' in namespace 'clang'"),
+WithFix(Fix(
+Test.range("insert"), "#include \"y.h\"\n",
+"Add include \"y.h\" for symbol clang::clangd::ns::Y");
+}
+
+TEST(IncludeFixerTest, SpecifiedScopeIsNamespaceAlias) {
+  Annotations Test(R"cpp(
+$insert[[]]namespace a {}
+namespace b = a;
+namespace c {
+  b::$[[X]] x;
+}
+  )cpp");
+  auto TU = TestTU::withCode(Test.code());
+  auto Index = buildIndexWithSymbol(
+  SymbolWithHeader{"a::X", "unittest:///x.h", "\"x.h\""});
+  TU.ExternalIndex = Index.get();
+
+  EXPECT_THAT(TU.build().getDiagnostics(),
+  UnorderedElementsAre(AllOf(
+  Diag(Test.range(), "no type named 'X' in namespace 'a'"),
+  WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
+  "Add include \"x.h\" for symbol a::X");
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clan

[PATCH] D58185: [clangd] Handle unresolved scope specifier when fixing includes.

2019-02-19 Thread Eric Liu via Phabricator via cfe-commits
ioeric updated this revision to Diff 187343.
ioeric marked 16 inline comments as done.
ioeric added a comment.

- address review comment


Repository:
  rCTE Clang Tools Extra

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D58185/new/

https://reviews.llvm.org/D58185

Files:
  clangd/IncludeFixer.cpp
  unittests/clangd/DiagnosticsTests.cpp

Index: unittests/clangd/DiagnosticsTests.cpp
===
--- unittests/clangd/DiagnosticsTests.cpp
+++ unittests/clangd/DiagnosticsTests.cpp
@@ -20,6 +20,7 @@
 namespace clangd {
 namespace {
 
+using testing::_;
 using testing::ElementsAre;
 using testing::Field;
 using testing::IsEmpty;
@@ -369,6 +370,8 @@
 $insert[[]]namespace ns {
 void foo() {
   $unqualified1[[X]] x;
+  // No fix if the unresolved type is used as specifier. (ns::)X::Nested will be
+  // considered the unresolved type.
   $unqualified2[[X]]::Nested n;
 }
 }
@@ -391,10 +394,7 @@
   AllOf(Diag(Test.range("unqualified1"), "unknown type name 'X'"),
 WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
 "Add include \"x.h\" for symbol ns::X"))),
-  AllOf(Diag(Test.range("unqualified2"),
- "use of undeclared identifier 'X'"),
-WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
-"Add include \"x.h\" for symbol ns::X"))),
+  Diag(Test.range("unqualified2"), "use of undeclared identifier 'X'"),
   AllOf(Diag(Test.range("qualified1"),
  "no type named 'X' in namespace 'ns'"),
 WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
@@ -487,6 +487,88 @@
   }
 }
 
+TEST(IncludeFixerTest, UnresolvedNameAsSpecifier) {
+  Annotations Test(R"cpp(
+$insert[[]]namespace ns {
+}
+void g() {  ns::$[[scope]]::X_Y();  }
+  )cpp");
+  auto TU = TestTU::withCode(Test.code());
+  auto Index = buildIndexWithSymbol(
+  SymbolWithHeader{"ns::scope::X_Y", "unittest:///x.h", "\"x.h\""});
+  TU.ExternalIndex = Index.get();
+
+  EXPECT_THAT(
+  TU.build().getDiagnostics(),
+  UnorderedElementsAre(AllOf(
+  Diag(Test.range(), "no member named 'scope' in namespace 'ns'"),
+  WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
+  "Add include \"x.h\" for symbol ns::scope::X_Y");
+}
+
+TEST(IncludeFixerTest, UnresolvedSpecifierWithSemaCorrection) {
+  Annotations Test(R"cpp(
+$insert[[]]namespace clang {
+void f() {
+  // "clangd::" will be corrected to "clang::" by Sema.
+  $q1[[clangd]]::$x[[X]] x;
+  $q2[[clangd]]::$ns[[ns]]::Y y;
+}
+}
+  )cpp");
+  auto TU = TestTU::withCode(Test.code());
+  auto Index = buildIndexWithSymbol(
+  {SymbolWithHeader{"clang::clangd::X", "unittest:///x.h", "\"x.h\""},
+   SymbolWithHeader{"clang::clangd::ns::Y", "unittest:///y.h", "\"y.h\""}});
+  TU.ExternalIndex = Index.get();
+
+  EXPECT_THAT(
+  TU.build().getDiagnostics(),
+  UnorderedElementsAre(
+  AllOf(
+  Diag(Test.range("q1"), "use of undeclared identifier 'clangd'; "
+ "did you mean 'clang'?"),
+  WithFix(_, // change clangd to clang
+  Fix(Test.range("insert"), "#include \"x.h\"\n",
+  "Add include \"x.h\" for symbol clang::clangd::X"))),
+  AllOf(
+  Diag(Test.range("x"), "no type named 'X' in namespace 'clang'"),
+  WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
+  "Add include \"x.h\" for symbol clang::clangd::X"))),
+  AllOf(
+  Diag(Test.range("q2"), "use of undeclared identifier 'clangd'; "
+ "did you mean 'clang'?"),
+  WithFix(
+  _, // change clangd to clangd
+  Fix(Test.range("insert"), "#include \"y.h\"\n",
+  "Add include \"y.h\" for symbol clang::clangd::ns::Y"))),
+  AllOf(Diag(Test.range("ns"),
+ "no member named 'ns' in namespace 'clang'"),
+WithFix(Fix(
+Test.range("insert"), "#include \"y.h\"\n",
+"Add include \"y.h\" for symbol clang::clangd::ns::Y");
+}
+
+TEST(IncludeFixerTest, SpecifiedScopeIsNamespaceAlias) {
+  Annotations Test(R"cpp(
+$insert[[]]namespace a {}
+namespace b = a;
+namespace c {
+  b::$[[X]] x;
+}
+  )cpp");
+  auto TU = TestTU::withCode(Test.code());
+  auto Index = buildIndexWithSymbol(
+  SymbolWithHeader{"a::X", "unittest:///x.h", "\"x.h\""});
+  TU.ExternalIndex = Index.get();
+
+  EXPECT_THAT(TU.build().getDiagnostics(),
+  UnorderedElementsAre(AllOf(
+  Diag(Test.range(), "no type named 'X' in namespace 'a'"),
+  WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
+  "Add include \"x.h\" for symbol a::X");
+}
+
 } // na

[PATCH] D58185: [clangd] Handle unresolved scope specifier when fixing includes.

2019-02-19 Thread Eric Liu via Phabricator via cfe-commits
ioeric added inline comments.



Comment at: clangd/IncludeFixer.cpp:191
+//~~
+llvm::Optional qualifiedByUnresolved(llvm::StringRef Code,
+  size_t Offset) {

sammccall wrote:
> this isn't wrong per se (conservative, bails out e.g. on unicode)
> But is it much harder to call Lexer::findNextToken twice and expect a raw 
> identifier and ::?
Good idea. Lexer is a better option.



Comment at: clangd/IncludeFixer.cpp:251
+// namespace clang { clangd::X; }
+// In this case, we use the "typo" qualifier as extra scope instead
+// of using the scope assumed by sema.

jkorous wrote:
> Does this work with anonymous namespaces?
I'm not sure... how do you use an anonymous namespace as a scope specifier?



Comment at: clangd/IncludeFixer.cpp:256
+std::string Spelling = (Code.substr(B, E - B) + "::").str();
+vlog("Spelling scope: {0}, SpecifiedNS: {1}", Spelling, SpecifiedNS);
+if (llvm::StringRef(SpecifiedNS).endswith(Spelling))

sammccall wrote:
> I don't think this vlog is useful as-is (quite far down the stack with no 
> context)
> Did you intend to remove it?
Indeed. Thanks for the catch!



Comment at: clangd/IncludeFixer.cpp:271
+  if (IsUnrsolvedSpecifier) {
+// If the unresolved name is a qualifier e.g.
+//  clang::clangd::X

jkorous wrote:
> Is the term `qualifier` applicable here? (Honest question.)
> 
> It seems like C++ grammar uses `specifier` (same as you in 
> `IsUnrsolvedSpecifier `)
> http://www.nongnu.org/hcb/#nested-name-specifier
You've raised a good point. We've been using `specifier` and `qualifier` 
interchangeably in the project. But specifier does seem to be a more 
appropriate name to use.  I've fixed uses in this file. Uses in other files 
probably need a separate cleanup.



Comment at: clangd/IncludeFixer.cpp:322
 UnresolvedName Unresolved;
 Unresolved.Name = Typo.getAsString();
 Unresolved.Loc = Typo.getBeginLoc();

sammccall wrote:
> Following up on our offline discussion :-)
> I think that since `extractSpecifiedScopes` can want to modify the name, we 
> should just expand that function's signature/responsibility to always 
> determine the name.
> 
> So we'd pass the `const DeclarationNameInfo&` to `extractSpecifiedScopes`, 
> and it would return a struct `{optional ResolvedScope; 
> optional UnresolvedScope; string Name}`. 
> Maybe need to call them `CheapUnresolvedName`/`extractUnresolvedNameCheaply` 
> or  similar.
> But I think the code change is small.
Sounds good. Thanks!


Repository:
  rCTE Clang Tools Extra

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D58185/new/

https://reviews.llvm.org/D58185



___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D58185: [clangd] Handle unresolved scope specifier when fixing includes.

2019-02-18 Thread Jan Korous via Phabricator via cfe-commits
jkorous added a comment.

Hi Eric, I have just couple details.




Comment at: clangd/IncludeFixer.cpp:188
+// first character after the unresolved name in \p Code. For the example below,
+// this returns "::X::Y" that is qualfied by unresolved name "clangd":
+// clang::clangd::X::Y

qualfied -> qualified



Comment at: clangd/IncludeFixer.cpp:193
+  size_t Offset) {
+  auto IsValidIdentifierChar = [](char C) {
+return ((C >= 'a' && C <= 'z') || (C >= 'A' && C <= 'Z') ||

It seems to me that calling Lexer would be better indeed.



Comment at: clangd/IncludeFixer.cpp:216
+  // resolved form not its typed form (think `namespace clang { clangd::x }` 
-->
+  // `clang::clangd::`). This is not done lazily because `SS` can get out of
+  // scope and it's relatively cheap.

Maybe move the comment about work done eagerly to the function?



Comment at: clangd/IncludeFixer.cpp:251
+// namespace clang { clangd::X; }
+// In this case, we use the "typo" qualifier as extra scope instead
+// of using the scope assumed by sema.

Does this work with anonymous namespaces?



Comment at: clangd/IncludeFixer.cpp:263
+Result.Resolved = printNamespaceScope(*ANS->getNamespace());
+  else
+// We don't fix symbols in scopes that are not top-level e.g. class

I'd personally prefer to add parentheses here to have the if/else if/else 
consistent. Up to you though.



Comment at: clangd/IncludeFixer.cpp:271
+  if (IsUnrsolvedSpecifier) {
+// If the unresolved name is a qualifier e.g.
+//  clang::clangd::X

Is the term `qualifier` applicable here? (Honest question.)

It seems like C++ grammar uses `specifier` (same as you in 
`IsUnrsolvedSpecifier `)
http://www.nongnu.org/hcb/#nested-name-specifier



Comment at: unittests/clangd/DiagnosticsTests.cpp:452
 
+TEST(IncludeFixerTest, UnresolvedNameAsQualifier) {
+  Annotations Test(R"cpp(

If (see above) we decide `qualifier` should be replaced by `specifier` or smth 
else please replace here as well, otherwise ignore this.


Repository:
  rCTE Clang Tools Extra

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D58185/new/

https://reviews.llvm.org/D58185



___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D58185: [clangd] Handle unresolved scope specifier when fixing includes.

2019-02-18 Thread Sam McCall via Phabricator via cfe-commits
sammccall accepted this revision.
sammccall added a comment.
This revision is now accepted and ready to land.

Thanks! The layering is *much* clearer now.
I can still suggest a couple of tweaks, but they're pretty much cosmetic.




Comment at: clangd/IncludeFixer.cpp:190
+// clang::clangd::X::Y
+//~~
+llvm::Optional qualifiedByUnresolved(llvm::StringRef Code,

you're showing a range here, it should be a point though?



Comment at: clangd/IncludeFixer.cpp:191
+//~~
+llvm::Optional qualifiedByUnresolved(llvm::StringRef Code,
+  size_t Offset) {

this isn't wrong per se (conservative, bails out e.g. on unicode)
But is it much harder to call Lexer::findNextToken twice and expect a raw 
identifier and ::?



Comment at: clangd/IncludeFixer.cpp:231
+const SourceManager &SM, CXXScopeSpec *SS, llvm::StringRef UnresolvedName,
+SourceLocation UnresolvedLoc, bool IsUnrsolvedSpecifier) {
+  bool Invalid = false;

Unrsolved -> Unresolved

emphasis might be clearer as `UnresolvedIsSpecifier` - there's always an 
unresolved entity, the boolean is indicating that it's a specifier



Comment at: clangd/IncludeFixer.cpp:256
+std::string Spelling = (Code.substr(B, E - B) + "::").str();
+vlog("Spelling scope: {0}, SpecifiedNS: {1}", Spelling, SpecifiedNS);
+if (llvm::StringRef(SpecifiedNS).endswith(Spelling))

I don't think this vlog is useful as-is (quite far down the stack with no 
context)
Did you intend to remove it?



Comment at: clangd/IncludeFixer.cpp:322
 UnresolvedName Unresolved;
 Unresolved.Name = Typo.getAsString();
 Unresolved.Loc = Typo.getBeginLoc();

Following up on our offline discussion :-)
I think that since `extractSpecifiedScopes` can want to modify the name, we 
should just expand that function's signature/responsibility to always determine 
the name.

So we'd pass the `const DeclarationNameInfo&` to `extractSpecifiedScopes`, and 
it would return a struct `{optional ResolvedScope; optional 
UnresolvedScope; string Name}`. 
Maybe need to call them `CheapUnresolvedName`/`extractUnresolvedNameCheaply` or 
 similar.
But I think the code change is small.


Repository:
  rCTE Clang Tools Extra

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D58185/new/

https://reviews.llvm.org/D58185



___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D58185: [clangd] Handle unresolved scope specifier when fixing includes.

2019-02-18 Thread Eric Liu via Phabricator via cfe-commits
ioeric updated this revision to Diff 187222.
ioeric marked 9 inline comments as done.
ioeric added a comment.

- address review comments


Repository:
  rCTE Clang Tools Extra

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D58185/new/

https://reviews.llvm.org/D58185

Files:
  clangd/IncludeFixer.cpp
  unittests/clangd/DiagnosticsTests.cpp

Index: unittests/clangd/DiagnosticsTests.cpp
===
--- unittests/clangd/DiagnosticsTests.cpp
+++ unittests/clangd/DiagnosticsTests.cpp
@@ -20,6 +20,7 @@
 namespace clangd {
 namespace {
 
+using testing::_;
 using testing::ElementsAre;
 using testing::Field;
 using testing::IsEmpty;
@@ -369,6 +370,8 @@
 $insert[[]]namespace ns {
 void foo() {
   $unqualified1[[X]] x;
+  // No fix if the unresolved type is used as qualifier. (ns::)X::Nested will be
+  // considered the unresolved type.
   $unqualified2[[X]]::Nested n;
 }
 }
@@ -391,10 +394,7 @@
   AllOf(Diag(Test.range("unqualified1"), "unknown type name 'X'"),
 WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
 "Add include \"x.h\" for symbol ns::X"))),
-  AllOf(Diag(Test.range("unqualified2"),
- "use of undeclared identifier 'X'"),
-WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
-"Add include \"x.h\" for symbol ns::X"))),
+  Diag(Test.range("unqualified2"), "use of undeclared identifier 'X'"),
   AllOf(Diag(Test.range("qualified1"),
  "no type named 'X' in namespace 'ns'"),
 WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
@@ -449,6 +449,87 @@
   UnorderedElementsAre(Diag(Test.range(), "no member named 'xy' in 'X'")));
 }
 
+TEST(IncludeFixerTest, UnresolvedNameAsQualifier) {
+  Annotations Test(R"cpp(
+$insert[[]]namespace ns {
+}
+void g() {  ns::$[[scope]]::X_Y();  }
+  )cpp");
+  auto TU = TestTU::withCode(Test.code());
+  auto Index = buildIndexWithSymbol(
+  SymbolWithHeader{"ns::scope::X_Y", "unittest:///x.h", "\"x.h\""});
+  TU.ExternalIndex = Index.get();
+
+  EXPECT_THAT(
+  TU.build().getDiagnostics(),
+  UnorderedElementsAre(AllOf(
+  Diag(Test.range(), "no member named 'scope' in namespace 'ns'"),
+  WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
+  "Add include \"x.h\" for symbol ns::scope::X_Y");
+}
+
+TEST(IncludeFixerTest, UnresolvedQualifierWithSemaCorrection) {
+  Annotations Test(R"cpp(
+$insert[[]]namespace clang {
+void f() {
+  // "clangd::" will be corrected to "clang::" by Sema.
+  $q1[[clangd]]::$x[[X]] x;
+  $q2[[clangd]]::$ns[[ns]]::Y y;
+}
+}
+  )cpp");
+  auto TU = TestTU::withCode(Test.code());
+  auto Index = buildIndexWithSymbol(
+  {SymbolWithHeader{"clang::clangd::X", "unittest:///x.h", "\"x.h\""},
+   SymbolWithHeader{"clang::clangd::ns::Y", "unittest:///y.h", "\"y.h\""}});
+  TU.ExternalIndex = Index.get();
+
+  EXPECT_THAT(
+  TU.build().getDiagnostics(),
+  UnorderedElementsAre(
+  AllOf(
+  Diag(Test.range("q1"), "use of undeclared identifier 'clangd'; "
+ "did you mean 'clang'?"),
+  WithFix(_, // change clangd to clang
+  Fix(Test.range("insert"), "#include \"x.h\"\n",
+  "Add include \"x.h\" for symbol clang::clangd::X"))),
+  AllOf(
+  Diag(Test.range("x"), "no type named 'X' in namespace 'clang'"),
+  WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
+  "Add include \"x.h\" for symbol clang::clangd::X"))),
+  AllOf(
+  Diag(Test.range("q2"), "use of undeclared identifier 'clangd'; "
+ "did you mean 'clang'?"),
+  WithFix(
+  _, // change clangd to clangd
+  Fix(Test.range("insert"), "#include \"y.h\"\n",
+  "Add include \"y.h\" for symbol clang::clangd::ns::Y"))),
+  AllOf(Diag(Test.range("ns"),
+ "no member named 'ns' in namespace 'clang'"),
+WithFix(Fix(
+Test.range("insert"), "#include \"y.h\"\n",
+"Add include \"y.h\" for symbol clang::clangd::ns::Y");
+}
+TEST(IncludeFixerTest, SpecifiedScopeIsNamespaceAlias) {
+  Annotations Test(R"cpp(
+$insert[[]]namespace a {}
+namespace b = a;
+namespace c {
+  b::$[[X]] x;
+}
+  )cpp");
+  auto TU = TestTU::withCode(Test.code());
+  auto Index = buildIndexWithSymbol(
+  SymbolWithHeader{"a::X", "unittest:///x.h", "\"x.h\""});
+  TU.ExternalIndex = Index.get();
+
+  EXPECT_THAT(TU.build().getDiagnostics(),
+  UnorderedElementsAre(AllOf(
+  Diag(Test.range(), "no type named 'X' in namespace 'a'"),
+  WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
+ 

[PATCH] D58185: [clangd] Handle unresolved scope specifier when fixing includes.

2019-02-18 Thread Eric Liu via Phabricator via cfe-commits
ioeric added inline comments.



Comment at: clangd/IncludeFixer.cpp:235
+  std::string Spelling = (Code.substr(B, E - B) + "::").str();
+  if (llvm::StringRef(SpecifiedNS).endswith(Spelling))
+SpecifiedScope = SpecifiedNS;

sammccall wrote:
> hmm, won't this heuristic have false positives?
> ```
> // indexed-header.h
> namespace a { int X; }
> 
> // main-file.cc
> namespace b = a;
> namespace c { int Y = b::x; }
> ```
> I worry spelling is going to be "b::" here, while SpecifiedNS is going to be 
> "a::".
Thanks for pointing this out! I completely missed the namespace alias case. 
Fixed and added a test.


Repository:
  rCTE Clang Tools Extra

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D58185/new/

https://reviews.llvm.org/D58185



___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D58185: [clangd] Handle unresolved scope specifier when fixing includes.

2019-02-18 Thread Sam McCall via Phabricator via cfe-commits
sammccall added inline comments.



Comment at: clangd/IncludeFixer.cpp:208
+SM.getBufferData(SM.getDecomposedLoc(Typo.getLoc()).first, &Invalid);
+assert(!Invalid);
 // Extract the typed scope. This is not done lazily because `SS` can get

No idea when this can be invalid, but we could just return here?



Comment at: clangd/IncludeFixer.cpp:211
 // out of scope and it's relatively cheap.
 llvm::Optional SpecifiedScope;
+// Extra scope to append to the query scopes. This is useful, for example,

We can be more precise about this now I think: IIUC this is the **resolved** 
specified scope.
in two senses: it's the part of what was typed that was resolved, and it's in 
its resolved form not its typed form (think `namespace clang { clangd::x }` --> 
`clang::clangd::`)



Comment at: clangd/IncludeFixer.cpp:212
 llvm::Optional SpecifiedScope;
+// Extra scope to append to the query scopes. This is useful, for example,
+// when the unresolved name is a qualier, in which case we use the name 
that

nit: I don't think this is an example, it's the only case right?

Consider calling this UnresolvedSpecifiedScope



Comment at: clangd/IncludeFixer.cpp:213
+// Extra scope to append to the query scopes. This is useful, for example,
+// when the unresolved name is a qualier, in which case we use the name 
that
+// comes after it as the name to resolve and use the qualifier as the extra

nit: qualier -> qualifier
accissible -> accessible



Comment at: clangd/IncludeFixer.cpp:216
+// scope in the accissible scopes.
+llvm::Optional ExtraScope;
 if (SS && SS->isNotEmpty()) { // "::" or "ns::"

I'm a bit concerned about how this mixes extraction of scopes from the source 
code/AST (which is now *really* complicated) with building the query and 
assembling the correction.

Can we reasonably extract the former to a helper function? Not sure exactly 
what the signature would be, but it would be helpful to find out.

The other thing is I think this has a lot in common with the scope-wrangling 
code in CodeComplete, and could *maybe* be unified a bit once isolated.



Comment at: clangd/IncludeFixer.cpp:235
+  std::string Spelling = (Code.substr(B, E - B) + "::").str();
+  if (llvm::StringRef(SpecifiedNS).endswith(Spelling))
+SpecifiedScope = SpecifiedNS;

hmm, won't this heuristic have false positives?
```
// indexed-header.h
namespace a { int X; }

// main-file.cc
namespace b = a;
namespace c { int Y = b::x; }
```
I worry spelling is going to be "b::" here, while SpecifiedNS is going to be 
"a::".



Comment at: clangd/IncludeFixer.cpp:315
 private:
+  // Returns the idenfiers qualified by an unresolved name. \p Offset is the
+  // first character after the unresolved name in \p Code. For the example

identifiers



Comment at: clangd/IncludeFixer.cpp:320
+  //~~
+  llvm::Optional qualifiedByUnresolved(llvm::StringRef Code,
+size_t Offset) const {

this only depends on the params right?
This could be static or even moved out of the class.


Repository:
  rCTE Clang Tools Extra

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D58185/new/

https://reviews.llvm.org/D58185



___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D58185: [clangd] Handle unresolved scope specifier when fixing includes.

2019-02-13 Thread Eric Liu via Phabricator via cfe-commits
ioeric updated this revision to Diff 186674.
ioeric added a comment.

- Remove unintended change.


Repository:
  rCTE Clang Tools Extra

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D58185/new/

https://reviews.llvm.org/D58185

Files:
  clangd/IncludeFixer.cpp
  unittests/clangd/DiagnosticsTests.cpp

Index: unittests/clangd/DiagnosticsTests.cpp
===
--- unittests/clangd/DiagnosticsTests.cpp
+++ unittests/clangd/DiagnosticsTests.cpp
@@ -20,6 +20,7 @@
 namespace clangd {
 namespace {
 
+using testing::_;
 using testing::ElementsAre;
 using testing::Field;
 using testing::IsEmpty;
@@ -369,6 +370,8 @@
 $insert[[]]namespace ns {
 void foo() {
   $unqualified1[[X]] x;
+  // No fix if the unresolved type is used as qualifier. (ns::)X::Nested will be
+  // considered the unresolved type.
   $unqualified2[[X]]::Nested n;
 }
 }
@@ -391,10 +394,7 @@
   AllOf(Diag(Test.range("unqualified1"), "unknown type name 'X'"),
 WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
 "Add include \"x.h\" for symbol ns::X"))),
-  AllOf(Diag(Test.range("unqualified2"),
- "use of undeclared identifier 'X'"),
-WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
-"Add include \"x.h\" for symbol ns::X"))),
+  Diag(Test.range("unqualified2"), "use of undeclared identifier 'X'"),
   AllOf(Diag(Test.range("qualified1"),
  "no type named 'X' in namespace 'ns'"),
 WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
@@ -449,6 +449,68 @@
   UnorderedElementsAre(Diag(Test.range(), "no member named 'xy' in 'X'")));
 }
 
+TEST(IncludeFixerTest, UnresolvedNameAsQualifier) {
+  Annotations Test(R"cpp(
+$insert[[]]namespace ns {
+}
+void g() {  ns::$[[scope]]::X_Y();  }
+  )cpp");
+  auto TU = TestTU::withCode(Test.code());
+  auto Index = buildIndexWithSymbol(
+  SymbolWithHeader{"ns::scope::X_Y", "unittest:///x.h", "\"x.h\""});
+  TU.ExternalIndex = Index.get();
+
+  EXPECT_THAT(
+  TU.build().getDiagnostics(),
+  UnorderedElementsAre(AllOf(
+  Diag(Test.range(), "no member named 'scope' in namespace 'ns'"),
+  WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
+  "Add include \"x.h\" for symbol ns::scope::X_Y");
+}
+
+TEST(IncludeFixerTest, UnresolvedQualifierWithSemaCorrection) {
+  Annotations Test(R"cpp(
+$insert[[]]namespace clang {
+void f() {
+  // "clangd::" will be corrected to "clang::" by Sema.
+  $q1[[clangd]]::$x[[X]] x;
+  $q2[[clangd]]::$ns[[ns]]::Y y;
+}
+}
+  )cpp");
+  auto TU = TestTU::withCode(Test.code());
+  auto Index = buildIndexWithSymbol(
+  {SymbolWithHeader{"clang::clangd::X", "unittest:///x.h", "\"x.h\""},
+   SymbolWithHeader{"clang::clangd::ns::Y", "unittest:///y.h", "\"y.h\""}});
+  TU.ExternalIndex = Index.get();
+
+  EXPECT_THAT(
+  TU.build().getDiagnostics(),
+  UnorderedElementsAre(
+  AllOf(
+  Diag(Test.range("q1"), "use of undeclared identifier 'clangd'; "
+ "did you mean 'clang'?"),
+  WithFix(_, // change clangd to clang
+  Fix(Test.range("insert"), "#include \"x.h\"\n",
+  "Add include \"x.h\" for symbol clang::clangd::X"))),
+  AllOf(
+  Diag(Test.range("x"), "no type named 'X' in namespace 'clang'"),
+  WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
+  "Add include \"x.h\" for symbol clang::clangd::X"))),
+  AllOf(
+  Diag(Test.range("q2"), "use of undeclared identifier 'clangd'; "
+ "did you mean 'clang'?"),
+  WithFix(
+  _, // change clangd to clangd
+  Fix(Test.range("insert"), "#include \"y.h\"\n",
+  "Add include \"y.h\" for symbol clang::clangd::ns::Y"))),
+  AllOf(Diag(Test.range("ns"),
+ "no member named 'ns' in namespace 'clang'"),
+WithFix(Fix(
+Test.range("insert"), "#include \"y.h\"\n",
+"Add include \"y.h\" for symbol clang::clangd::ns::Y");
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang
Index: clangd/IncludeFixer.cpp
===
--- clangd/IncludeFixer.cpp
+++ clangd/IncludeFixer.cpp
@@ -19,6 +19,7 @@
 #include "clang/AST/Type.h"
 #include "clang/Basic/Diagnostic.h"
 #include "clang/Basic/DiagnosticSema.h"
+#include "clang/Basic/SourceLocation.h"
 #include "clang/Sema/DeclSpec.h"
 #include "clang/Sema/Lookup.h"
 #include "clang/Sema/Scope.h"
@@ -27,6 +28,8 @@
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/None.h"
+#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/StringR

[PATCH] D58185: [clangd] Handle unresolved scope specifier when fixing includes.

2019-02-13 Thread Eric Liu via Phabricator via cfe-commits
ioeric created this revision.
ioeric added a reviewer: sammccall.
Herald added subscribers: cfe-commits, jdoerfert, kadircet, arphaman, jkorous, 
MaskRay, ilya-biryukov.
Herald added a project: clang.
ioeric updated this revision to Diff 186674.
ioeric added a comment.
ioeric edited the summary of this revision.

- Remove unintended change.


In the following examples, "clangd" is unresolved, and the fixer will try to fix
include for `clang::clangd`; however, clang::clangd::X is usually intended. So
when handling a qualifier that is unresolved, we change the unresolved name and
scopes so that the fixer will fix "clang::clangd::X" in the following example.

namespace clang {
clangd::X
~~
}
// or
clang::clangd::X
   ~~


Repository:
  rCTE Clang Tools Extra

https://reviews.llvm.org/D58185

Files:
  clangd/IncludeFixer.cpp
  unittests/clangd/DiagnosticsTests.cpp

Index: unittests/clangd/DiagnosticsTests.cpp
===
--- unittests/clangd/DiagnosticsTests.cpp
+++ unittests/clangd/DiagnosticsTests.cpp
@@ -20,6 +20,7 @@
 namespace clangd {
 namespace {
 
+using testing::_;
 using testing::ElementsAre;
 using testing::Field;
 using testing::IsEmpty;
@@ -369,6 +370,8 @@
 $insert[[]]namespace ns {
 void foo() {
   $unqualified1[[X]] x;
+  // No fix if the unresolved type is used as qualifier. (ns::)X::Nested will be
+  // considered the unresolved type.
   $unqualified2[[X]]::Nested n;
 }
 }
@@ -391,10 +394,7 @@
   AllOf(Diag(Test.range("unqualified1"), "unknown type name 'X'"),
 WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
 "Add include \"x.h\" for symbol ns::X"))),
-  AllOf(Diag(Test.range("unqualified2"),
- "use of undeclared identifier 'X'"),
-WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
-"Add include \"x.h\" for symbol ns::X"))),
+  Diag(Test.range("unqualified2"), "use of undeclared identifier 'X'"),
   AllOf(Diag(Test.range("qualified1"),
  "no type named 'X' in namespace 'ns'"),
 WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
@@ -449,6 +449,68 @@
   UnorderedElementsAre(Diag(Test.range(), "no member named 'xy' in 'X'")));
 }
 
+TEST(IncludeFixerTest, UnresolvedNameAsQualifier) {
+  Annotations Test(R"cpp(
+$insert[[]]namespace ns {
+}
+void g() {  ns::$[[scope]]::X_Y();  }
+  )cpp");
+  auto TU = TestTU::withCode(Test.code());
+  auto Index = buildIndexWithSymbol(
+  SymbolWithHeader{"ns::scope::X_Y", "unittest:///x.h", "\"x.h\""});
+  TU.ExternalIndex = Index.get();
+
+  EXPECT_THAT(
+  TU.build().getDiagnostics(),
+  UnorderedElementsAre(AllOf(
+  Diag(Test.range(), "no member named 'scope' in namespace 'ns'"),
+  WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
+  "Add include \"x.h\" for symbol ns::scope::X_Y");
+}
+
+TEST(IncludeFixerTest, UnresolvedQualifierWithSemaCorrection) {
+  Annotations Test(R"cpp(
+$insert[[]]namespace clang {
+void f() {
+  // "clangd::" will be corrected to "clang::" by Sema.
+  $q1[[clangd]]::$x[[X]] x;
+  $q2[[clangd]]::$ns[[ns]]::Y y;
+}
+}
+  )cpp");
+  auto TU = TestTU::withCode(Test.code());
+  auto Index = buildIndexWithSymbol(
+  {SymbolWithHeader{"clang::clangd::X", "unittest:///x.h", "\"x.h\""},
+   SymbolWithHeader{"clang::clangd::ns::Y", "unittest:///y.h", "\"y.h\""}});
+  TU.ExternalIndex = Index.get();
+
+  EXPECT_THAT(
+  TU.build().getDiagnostics(),
+  UnorderedElementsAre(
+  AllOf(
+  Diag(Test.range("q1"), "use of undeclared identifier 'clangd'; "
+ "did you mean 'clang'?"),
+  WithFix(_, // change clangd to clang
+  Fix(Test.range("insert"), "#include \"x.h\"\n",
+  "Add include \"x.h\" for symbol clang::clangd::X"))),
+  AllOf(
+  Diag(Test.range("x"), "no type named 'X' in namespace 'clang'"),
+  WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
+  "Add include \"x.h\" for symbol clang::clangd::X"))),
+  AllOf(
+  Diag(Test.range("q2"), "use of undeclared identifier 'clangd'; "
+ "did you mean 'clang'?"),
+  WithFix(
+  _, // change clangd to clangd
+  Fix(Test.range("insert"), "#include \"y.h\"\n",
+  "Add include \"y.h\" for symbol clang::clangd::ns::Y"))),
+  AllOf(Diag(Test.range("ns"),
+ "no member named 'ns' in namespace 'clang'"),
+WithFix(Fix(
+Test.range("insert"), "#include \"y.h\"\n",
+"Add include \"y.h\" for symbol clang::clangd::ns::Y");
+}
+
 } // namespace
 } // namespace clang