|
@@ -14,6 +14,7 @@
|
|
#include "clang/AST/Decl.h"
|
|
#include "clang/AST/Decl.h"
|
|
#include "clang/AST/DeclCXX.h"
|
|
#include "clang/AST/DeclCXX.h"
|
|
#include "clang/AST/DeclarationName.h"
|
|
#include "clang/AST/DeclarationName.h"
|
|
|
|
+#include "llvm/ADT/SmallVector.h"
|
|
using namespace clang;
|
|
using namespace clang;
|
|
using namespace clang::tooling;
|
|
using namespace clang::tooling;
|
|
|
|
|
|
@@ -114,35 +115,60 @@ static bool isFullyQualified(const NestedNameSpecifier *NNS) {
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
-// Returns true if spelling symbol \p QName as \p Spelling in \p UseContext is
|
|
|
|
-// ambiguous. For example, if QName is "::y::bar" and the spelling is "y::bar"
|
|
|
|
-// in `UseContext` "a" that contains a nested namespace "a::y", then "y::bar"
|
|
|
|
-// can be resolved to ::a::y::bar, which can cause compile error.
|
|
|
|
|
|
+// Adds more scope specifier to the spelled name until the spelling is not
|
|
|
|
+// ambiguous. A spelling is ambiguous if the resolution of the symbol is
|
|
|
|
+// ambiguous. For example, if QName is "::y::bar", the spelling is "y::bar", and
|
|
|
|
+// context contains a nested namespace "a::y", then "y::bar" can be resolved to
|
|
|
|
+// ::a::y::bar in the context, which can cause compile error.
|
|
// FIXME: consider using namespaces.
|
|
// FIXME: consider using namespaces.
|
|
-static bool isAmbiguousNameInScope(StringRef Spelling, StringRef QName,
|
|
|
|
- const DeclContext &UseContext) {
|
|
|
|
|
|
+static std::string disambiguateSpellingInScope(StringRef Spelling,
|
|
|
|
+ StringRef QName,
|
|
|
|
+ const DeclContext &UseContext) {
|
|
assert(QName.startswith("::"));
|
|
assert(QName.startswith("::"));
|
|
|
|
+ assert(QName.endswith(Spelling));
|
|
if (Spelling.startswith("::"))
|
|
if (Spelling.startswith("::"))
|
|
- return false;
|
|
|
|
|
|
+ return Spelling;
|
|
|
|
|
|
- // Lookup the first component of Spelling in all enclosing namespaces and
|
|
|
|
- // check if there is any existing symbols with the same name but in different
|
|
|
|
- // scope.
|
|
|
|
- StringRef Head = Spelling.split("::").first;
|
|
|
|
|
|
+ auto UnspelledSpecifier = QName.drop_back(Spelling.size());
|
|
|
|
+ llvm::SmallVector<llvm::StringRef, 2> UnspelledScopes;
|
|
|
|
+ UnspelledSpecifier.split(UnspelledScopes, "::", /*MaxSplit=*/-1,
|
|
|
|
+ /*KeepEmpty=*/false);
|
|
|
|
|
|
- llvm::SmallVector<const NamespaceDecl *, 4> UseNamespaces =
|
|
|
|
|
|
+ llvm::SmallVector<const NamespaceDecl *, 4> EnclosingNamespaces =
|
|
getAllNamedNamespaces(&UseContext);
|
|
getAllNamedNamespaces(&UseContext);
|
|
auto &AST = UseContext.getParentASTContext();
|
|
auto &AST = UseContext.getParentASTContext();
|
|
StringRef TrimmedQName = QName.substr(2);
|
|
StringRef TrimmedQName = QName.substr(2);
|
|
- for (const auto *NS : UseNamespaces) {
|
|
|
|
- auto LookupRes = NS->lookup(DeclarationName(&AST.Idents.get(Head)));
|
|
|
|
- if (!LookupRes.empty()) {
|
|
|
|
- for (const NamedDecl *Res : LookupRes)
|
|
|
|
- if (!TrimmedQName.startswith(Res->getQualifiedNameAsString()))
|
|
|
|
- return true;
|
|
|
|
|
|
+
|
|
|
|
+ auto IsAmbiguousSpelling = [&EnclosingNamespaces, &AST, &TrimmedQName](
|
|
|
|
+ const llvm::StringRef CurSpelling) {
|
|
|
|
+ if (CurSpelling.startswith("::"))
|
|
|
|
+ return false;
|
|
|
|
+ // Lookup the first component of Spelling in all enclosing namespaces
|
|
|
|
+ // and check if there is any existing symbols with the same name but in
|
|
|
|
+ // different scope.
|
|
|
|
+ StringRef Head = CurSpelling.split("::").first;
|
|
|
|
+ for (const auto *NS : EnclosingNamespaces) {
|
|
|
|
+ auto LookupRes = NS->lookup(DeclarationName(&AST.Idents.get(Head)));
|
|
|
|
+ if (!LookupRes.empty()) {
|
|
|
|
+ for (const NamedDecl *Res : LookupRes)
|
|
|
|
+ if (!TrimmedQName.startswith(Res->getQualifiedNameAsString()))
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return false;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // Add more qualifiers until the spelling is not ambiguous.
|
|
|
|
+ std::string Disambiguated = Spelling;
|
|
|
|
+ while (IsAmbiguousSpelling(Disambiguated)) {
|
|
|
|
+ if (UnspelledScopes.empty()) {
|
|
|
|
+ Disambiguated = "::" + Disambiguated;
|
|
|
|
+ } else {
|
|
|
|
+ Disambiguated = (UnspelledScopes.back() + "::" + Disambiguated).str();
|
|
|
|
+ UnspelledScopes.pop_back();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- return false;
|
|
|
|
|
|
+ return Disambiguated;
|
|
}
|
|
}
|
|
|
|
|
|
std::string tooling::replaceNestedName(const NestedNameSpecifier *Use,
|
|
std::string tooling::replaceNestedName(const NestedNameSpecifier *Use,
|
|
@@ -179,12 +205,6 @@ std::string tooling::replaceNestedName(const NestedNameSpecifier *Use,
|
|
// specific).
|
|
// specific).
|
|
StringRef Suggested = getBestNamespaceSubstr(UseContext, ReplacementString,
|
|
StringRef Suggested = getBestNamespaceSubstr(UseContext, ReplacementString,
|
|
isFullyQualified(Use));
|
|
isFullyQualified(Use));
|
|
- // Use the fully qualified name if the suggested name is ambiguous.
|
|
|
|
- // FIXME: consider re-shortening the name until the name is not ambiguous. We
|
|
|
|
- // are not doing this because ambiguity is pretty bad and we should not try to
|
|
|
|
- // be clever in handling such cases. Making this noticeable to users seems to
|
|
|
|
- // be a better option.
|
|
|
|
- return isAmbiguousNameInScope(Suggested, ReplacementString, *UseContext)
|
|
|
|
- ? ReplacementString
|
|
|
|
- : Suggested;
|
|
|
|
|
|
+
|
|
|
|
+ return disambiguateSpellingInScope(Suggested, ReplacementString, *UseContext);
|
|
}
|
|
}
|