forked from OSchip/llvm-project
[clangd] Implementation of auto type expansion.
Add a tweak for clangd to replace an auto keyword to the deduced type. This way a user can declare something with auto and then have the IDE/clangd replace auto with whatever type clangd thinks it is. In case of long/complext types this makes is reduces writing effort for the user. The functionality is similar to the hover over the auto keyword. Example (from the header): ``` /// Before: /// auto x = Something(); /// ^^^^ /// After: /// MyClass x = Something(); /// ^^^^^^^ ``` Patch by kuhnel! (Christian Kühnel) Differential Revision: https://reviews.llvm.org/D62855 llvm-svn: 365792
This commit is contained in:
parent
5cc7c9ab93
commit
9470142ca5
|
@ -169,5 +169,35 @@ llvm::Optional<SymbolID> getSymbolID(const IdentifierInfo &II,
|
|||
return SymbolID(USR);
|
||||
}
|
||||
|
||||
std::string shortenNamespace(const llvm::StringRef OriginalName,
|
||||
const llvm::StringRef CurrentNamespace) {
|
||||
llvm::SmallVector<llvm::StringRef, 8> OriginalParts;
|
||||
llvm::SmallVector<llvm::StringRef, 8> CurrentParts;
|
||||
llvm::SmallVector<llvm::StringRef, 8> Result;
|
||||
OriginalName.split(OriginalParts, "::");
|
||||
CurrentNamespace.split(CurrentParts, "::");
|
||||
auto MinLength = std::min(CurrentParts.size(), OriginalParts.size());
|
||||
|
||||
unsigned DifferentAt = 0;
|
||||
while (DifferentAt < MinLength &&
|
||||
CurrentParts[DifferentAt] == OriginalParts[DifferentAt]) {
|
||||
DifferentAt++;
|
||||
}
|
||||
|
||||
for (u_int i = DifferentAt; i < OriginalParts.size(); ++i) {
|
||||
Result.push_back(OriginalParts[i]);
|
||||
}
|
||||
return join(Result, "::");
|
||||
}
|
||||
|
||||
std::string printType(const QualType QT, const DeclContext & Context){
|
||||
PrintingPolicy PP(Context.getParentASTContext().getPrintingPolicy());
|
||||
PP.SuppressTagKeyword = 1;
|
||||
return shortenNamespace(
|
||||
QT.getAsString(PP),
|
||||
printNamespaceScope(Context) );
|
||||
}
|
||||
|
||||
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
|
|
@ -67,6 +67,22 @@ llvm::Optional<SymbolID> getSymbolID(const IdentifierInfo &II,
|
|||
const MacroInfo *MI,
|
||||
const SourceManager &SM);
|
||||
|
||||
/// Returns a QualType as string.
|
||||
std::string printType(const QualType QT, const DeclContext & Context);
|
||||
|
||||
/// Try to shorten the OriginalName by removing namespaces from the left of
|
||||
/// the string that are redundant in the CurrentNamespace. This way the type
|
||||
/// idenfier become shorter and easier to read.
|
||||
/// Limitation: It only handles the qualifier of the type itself, not that of
|
||||
/// templates.
|
||||
/// FIXME: change type of parameter CurrentNamespace to DeclContext ,
|
||||
/// take in to account using directives etc
|
||||
/// Example: shortenNamespace("ns1::MyClass<ns1::OtherClass>", "ns1")
|
||||
/// --> "MyClass<ns1::OtherClass>"
|
||||
std::string shortenNamespace(const llvm::StringRef OriginalName,
|
||||
const llvm::StringRef CurrentNamespace);
|
||||
|
||||
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
||||
|
|
|
@ -364,5 +364,18 @@ const Node *SelectionTree::commonAncestor() const {
|
|||
return Ancestor;
|
||||
}
|
||||
|
||||
const DeclContext& SelectionTree::Node::getDeclContext() const {
|
||||
for (const Node* CurrentNode = this; CurrentNode != nullptr;
|
||||
CurrentNode = CurrentNode->Parent) {
|
||||
if (const Decl* Current = CurrentNode->ASTNode.get<Decl>()) {
|
||||
if (CurrentNode != this)
|
||||
if (auto *DC = dyn_cast<DeclContext>(Current))
|
||||
return *DC;
|
||||
return *Current->getDeclContext();
|
||||
}
|
||||
}
|
||||
llvm_unreachable("A tree must always be rooted at TranslationUnitDecl.");
|
||||
}
|
||||
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
|
|
@ -93,6 +93,9 @@ public:
|
|||
ast_type_traits::DynTypedNode ASTNode;
|
||||
// The extent to which this node is covered by the selection.
|
||||
Selection Selected;
|
||||
// Walk up the AST to get the DeclContext of this Node,
|
||||
// which is not the node itself.
|
||||
const DeclContext& getDeclContext() const;
|
||||
};
|
||||
|
||||
// The most specific common ancestor of all the selected nodes.
|
||||
|
|
|
@ -870,7 +870,9 @@ public:
|
|||
} // namespace
|
||||
|
||||
/// Retrieves the deduced type at a given location (auto, decltype).
|
||||
bool hasDeducedType(ParsedAST &AST, SourceLocation SourceLocationBeg) {
|
||||
/// SourceLocationBeg must point to the first character of the token
|
||||
llvm::Optional<QualType> getDeducedType(ParsedAST &AST,
|
||||
SourceLocation SourceLocationBeg) {
|
||||
Token Tok;
|
||||
auto &ASTCtx = AST.getASTContext();
|
||||
// Only try to find a deduced type if the token is auto or decltype.
|
||||
|
@ -878,12 +880,20 @@ bool hasDeducedType(ParsedAST &AST, SourceLocation SourceLocationBeg) {
|
|||
Lexer::getRawToken(SourceLocationBeg, Tok, ASTCtx.getSourceManager(),
|
||||
ASTCtx.getLangOpts(), false) ||
|
||||
!Tok.is(tok::raw_identifier)) {
|
||||
return false;
|
||||
return {};
|
||||
}
|
||||
AST.getPreprocessor().LookUpIdentifierInfo(Tok);
|
||||
if (!(Tok.is(tok::kw_auto) || Tok.is(tok::kw_decltype)))
|
||||
return false;
|
||||
return true;
|
||||
return {};
|
||||
|
||||
DeducedTypeVisitor V(SourceLocationBeg);
|
||||
V.TraverseAST(AST.getASTContext());
|
||||
return V.DeducedType;
|
||||
}
|
||||
|
||||
/// Retrieves the deduced type at a given location (auto, decltype).
|
||||
bool hasDeducedType(ParsedAST &AST, SourceLocation SourceLocationBeg) {
|
||||
return (bool) getDeducedType(AST, SourceLocationBeg);
|
||||
}
|
||||
|
||||
llvm::Optional<HoverInfo> getHover(ParsedAST &AST, Position Pos,
|
||||
|
|
|
@ -141,6 +141,16 @@ llvm::Optional<TypeHierarchyItem> getTypeHierarchy(
|
|||
ParsedAST &AST, Position Pos, int Resolve, TypeHierarchyDirection Direction,
|
||||
const SymbolIndex *Index = nullptr, PathRef TUPath = PathRef{});
|
||||
|
||||
/// Retrieves the deduced type at a given location (auto, decltype).
|
||||
/// Retuns None unless SourceLocationBeg starts an auto/decltype token.
|
||||
/// It will return the underlying type.
|
||||
llvm::Optional<QualType> getDeducedType(ParsedAST &AST,
|
||||
SourceLocation SourceLocationBeg);
|
||||
|
||||
/// Check if there is a deduced type at a given location (auto, decltype).
|
||||
/// SourceLocationBeg must point to the first character of the token
|
||||
bool hasDeducedType(ParsedAST &AST, SourceLocation SourceLocationBeg);
|
||||
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ add_clang_library(clangDaemonTweaks OBJECT
|
|||
RawStringLiteral.cpp
|
||||
SwapIfBranches.cpp
|
||||
ExtractVariable.cpp
|
||||
ExpandAutoType.cpp
|
||||
|
||||
LINK_LIBS
|
||||
clangAST
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
//===--- ReplaceAutoType.cpp -------------------------------------*- C++-*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
#include "refactor/Tweak.h"
|
||||
|
||||
#include "Logger.h"
|
||||
#include "clang/AST/Type.h"
|
||||
#include "clang/AST/TypeLoc.h"
|
||||
#include "clang/Basic/LLVM.h"
|
||||
#include "llvm/ADT/None.h"
|
||||
#include "llvm/ADT/Optional.h"
|
||||
#include "llvm/Support/Debug.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
#include <climits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <AST.h>
|
||||
#include "XRefs.h"
|
||||
#include "llvm/ADT/StringExtras.h"
|
||||
|
||||
namespace clang {
|
||||
namespace clangd {
|
||||
|
||||
/// Expand the "auto" type to the derived type
|
||||
/// Before:
|
||||
/// auto x = Something();
|
||||
/// ^^^^
|
||||
/// After:
|
||||
/// MyClass x = Something();
|
||||
/// ^^^^^^^
|
||||
/// FIXME: Handle decltype as well
|
||||
class ExpandAutoType : public Tweak {
|
||||
public:
|
||||
const char *id() const final;
|
||||
Intent intent() const override { return Intent::Refactor;}
|
||||
bool prepare(const Selection &Inputs) override;
|
||||
Expected<Effect> apply(const Selection &Inputs) override;
|
||||
std::string title() const override;
|
||||
|
||||
private:
|
||||
/// Cache the AutoTypeLoc, so that we do not need to search twice.
|
||||
llvm::Optional<clang::AutoTypeLoc> CachedLocation;
|
||||
|
||||
/// Create an error message with filename and line number in it
|
||||
llvm::Error createErrorMessage(const std::string& Message,
|
||||
const Selection &Inputs);
|
||||
|
||||
};
|
||||
|
||||
REGISTER_TWEAK(ExpandAutoType)
|
||||
|
||||
std::string ExpandAutoType::title() const { return "expand auto type"; }
|
||||
|
||||
bool ExpandAutoType::prepare(const Selection& Inputs) {
|
||||
CachedLocation = llvm::None;
|
||||
if (auto *Node = Inputs.ASTSelection.commonAncestor()) {
|
||||
if (auto *TypeNode = Node->ASTNode.get<TypeLoc>()) {
|
||||
if (const AutoTypeLoc Result = TypeNode->getAs<AutoTypeLoc>()) {
|
||||
CachedLocation = Result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (bool) CachedLocation;
|
||||
}
|
||||
|
||||
Expected<Tweak::Effect> ExpandAutoType::apply(const Selection& Inputs) {
|
||||
auto& SrcMgr = Inputs.AST.getASTContext().getSourceManager();
|
||||
|
||||
llvm::Optional<clang::QualType> DeducedType =
|
||||
getDeducedType(Inputs.AST, CachedLocation->getBeginLoc());
|
||||
|
||||
// if we can't resolve the type, return an error message
|
||||
if (DeducedType == llvm::None || DeducedType->isNull()) {
|
||||
return createErrorMessage("Could not deduce type for 'auto' type", Inputs);
|
||||
}
|
||||
|
||||
// if it's a lambda expression, return an error message
|
||||
if (isa<RecordType>(*DeducedType) and
|
||||
dyn_cast<RecordType>(*DeducedType)->getDecl()->isLambda()) {
|
||||
return createErrorMessage("Could not expand type of lambda expression",
|
||||
Inputs);
|
||||
}
|
||||
|
||||
// if it's a function expression, return an error message
|
||||
// naively replacing 'auto' with the type will break declarations.
|
||||
// FIXME: there are other types that have similar problems
|
||||
if (DeducedType->getTypePtr()->isFunctionPointerType()) {
|
||||
return createErrorMessage("Could not expand type of function pointer",
|
||||
Inputs);
|
||||
}
|
||||
|
||||
std::string PrettyTypeName = printType(*DeducedType,
|
||||
Inputs.ASTSelection.commonAncestor()->getDeclContext());
|
||||
|
||||
tooling::Replacement
|
||||
Expansion(SrcMgr, CharSourceRange(CachedLocation->getSourceRange(), true),
|
||||
PrettyTypeName);
|
||||
|
||||
return Tweak::Effect::applyEdit(tooling::Replacements(Expansion));
|
||||
}
|
||||
|
||||
llvm::Error ExpandAutoType::createErrorMessage(const std::string& Message,
|
||||
const Selection& Inputs) {
|
||||
auto& SrcMgr = Inputs.AST.getASTContext().getSourceManager();
|
||||
std::string ErrorMessage =
|
||||
Message + ": " +
|
||||
SrcMgr.getFilename(Inputs.Cursor).str() + " Line " +
|
||||
std::to_string(SrcMgr.getExpansionLineNumber(Inputs.Cursor));
|
||||
|
||||
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
||||
ErrorMessage.c_str());
|
||||
}
|
||||
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
|
@ -0,0 +1,70 @@
|
|||
# RUN: clangd -log=verbose -lit-test < %s | FileCheck -strict-whitespace %s
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"auto i = 0;"}}}
|
||||
---
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "textDocument/codeAction",
|
||||
"params": {
|
||||
"textDocument": {
|
||||
"uri": "test:///main.cpp"
|
||||
},
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 4
|
||||
}
|
||||
},
|
||||
"context": {
|
||||
"diagnostics": []
|
||||
}
|
||||
}
|
||||
}
|
||||
# CHECK: "id": 1,
|
||||
# CHECK-NEXT: "jsonrpc": "2.0",
|
||||
# CHECK-NEXT: "result": [
|
||||
# CHECK-NEXT: {
|
||||
# CHECK-NEXT: "arguments": [
|
||||
# CHECK-NEXT: {
|
||||
# CHECK-NEXT: "file": "file:///clangd-test/main.cpp",
|
||||
# CHECK-NEXT: "selection": {
|
||||
# CHECK-NEXT: "end": {
|
||||
# CHECK-NEXT: "character": 4,
|
||||
# CHECK-NEXT: "line": 0
|
||||
# CHECK-NEXT: },
|
||||
# CHECK-NEXT: "start": {
|
||||
# CHECK-NEXT: "character": 0,
|
||||
# CHECK-NEXT: "line": 0
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: },
|
||||
# CHECK-NEXT: "tweakID": "ExpandAutoType"
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: ],
|
||||
# CHECK-NEXT: "command": "clangd.applyTweak",
|
||||
# CHECK-NEXT: "title": "expand auto type"
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: ]
|
||||
---
|
||||
{"jsonrpc":"2.0","id":4,"method":"workspace/executeCommand","params":{"command":"clangd.applyTweak","arguments":[{"file":"file:///clangd-test/main.cpp","selection":{"end":{"character":4,"line":0},"start":{"character":0,"line":0}},"tweakID":"ExpandAutoType"}]}}
|
||||
# CHECK: "newText": "int",
|
||||
# CHECK-NEXT: "range": {
|
||||
# CHECK-NEXT: "end": {
|
||||
# CHECK-NEXT: "character": 4,
|
||||
# CHECK-NEXT: "line": 0
|
||||
# CHECK-NEXT: },
|
||||
# CHECK-NEXT: "start": {
|
||||
# CHECK-NEXT: "character": 0,
|
||||
# CHECK-NEXT: "line": 0
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: }
|
||||
---
|
||||
{"jsonrpc":"2.0","id":4,"method":"shutdown"}
|
||||
---
|
||||
{"jsonrpc":"2.0","method":"exit"}
|
||||
---
|
|
@ -0,0 +1,42 @@
|
|||
//===-- ASTTests.cpp --------------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "AST.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace clang {
|
||||
namespace clangd {
|
||||
namespace {
|
||||
|
||||
TEST(ExpandAutoType, ShortenNamespace) {
|
||||
ASSERT_EQ("TestClass", shortenNamespace("TestClass", ""));
|
||||
|
||||
ASSERT_EQ("TestClass", shortenNamespace(
|
||||
"testnamespace::TestClass", "testnamespace"));
|
||||
|
||||
ASSERT_EQ(
|
||||
"namespace1::TestClass",
|
||||
shortenNamespace("namespace1::TestClass", "namespace2"));
|
||||
|
||||
ASSERT_EQ("TestClass",
|
||||
shortenNamespace("testns1::testns2::TestClass",
|
||||
"testns1::testns2"));
|
||||
|
||||
ASSERT_EQ(
|
||||
"testns2::TestClass",
|
||||
shortenNamespace("testns1::testns2::TestClass", "testns1"));
|
||||
|
||||
ASSERT_EQ("TestClass<testns1::OtherClass>",
|
||||
shortenNamespace(
|
||||
"testns1::TestClass<testns1::OtherClass>", "testns1"));
|
||||
}
|
||||
|
||||
|
||||
} // namespace
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
|
@ -23,6 +23,7 @@ endif()
|
|||
add_custom_target(ClangdUnitTests)
|
||||
add_unittest(ClangdUnitTests ClangdTests
|
||||
Annotations.cpp
|
||||
ASTTests.cpp
|
||||
BackgroundIndexTests.cpp
|
||||
CancellationTests.cpp
|
||||
CanonicalIncludesTests.cpp
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "TestTU.h"
|
||||
#include "refactor/Tweak.h"
|
||||
#include "clang/AST/Expr.h"
|
||||
#include "clang/Basic/LLVM.h"
|
||||
#include "clang/Rewrite/Core/Rewriter.h"
|
||||
#include "clang/Tooling/Core/Replacement.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
|
@ -126,6 +127,19 @@ void checkTransform(llvm::StringRef ID, llvm::StringRef Input,
|
|||
EXPECT_EQ(Output, std::string(*Result)) << Input;
|
||||
}
|
||||
|
||||
/// Check if apply returns an error and that the @ErrorMessage is contained
|
||||
/// in that error
|
||||
void checkApplyContainsError(llvm::StringRef ID, llvm::StringRef Input,
|
||||
const std::string& ErrorMessage) {
|
||||
auto Result = apply(ID, Input);
|
||||
ASSERT_FALSE(Result) << "expected error message:\n " << ErrorMessage <<
|
||||
"\non input:" << Input;
|
||||
EXPECT_NE(std::string::npos,
|
||||
llvm::toString(Result.takeError()).find(ErrorMessage))
|
||||
<< "Wrong error message:\n " << llvm::toString(Result.takeError())
|
||||
<< "\nexpected:\n " << ErrorMessage;
|
||||
}
|
||||
|
||||
TEST(TweakTest, SwapIfBranches) {
|
||||
llvm::StringLiteral ID = "SwapIfBranches";
|
||||
|
||||
|
@ -517,6 +531,140 @@ int a = 123 EMPTY_FN(1) ;
|
|||
)cpp");
|
||||
}
|
||||
|
||||
TEST(TweakTest, ExpandAutoType) {
|
||||
llvm::StringLiteral ID = "ExpandAutoType";
|
||||
|
||||
checkAvailable(ID, R"cpp(
|
||||
^a^u^t^o^ i = 0;
|
||||
)cpp");
|
||||
|
||||
checkNotAvailable(ID, R"cpp(
|
||||
auto ^i^ ^=^ ^0^;^
|
||||
)cpp");
|
||||
|
||||
llvm::StringLiteral Input = R"cpp(
|
||||
[[auto]] i = 0;
|
||||
)cpp";
|
||||
llvm::StringLiteral Output = R"cpp(
|
||||
int i = 0;
|
||||
)cpp";
|
||||
checkTransform(ID, Input, Output);
|
||||
|
||||
// check primitive type
|
||||
Input = R"cpp(
|
||||
au^to i = 0;
|
||||
)cpp";
|
||||
Output = R"cpp(
|
||||
int i = 0;
|
||||
)cpp";
|
||||
checkTransform(ID, Input, Output);
|
||||
|
||||
// check classes and namespaces
|
||||
Input = R"cpp(
|
||||
namespace testns {
|
||||
class TestClass {
|
||||
class SubClass {};
|
||||
};
|
||||
}
|
||||
^auto C = testns::TestClass::SubClass();
|
||||
)cpp";
|
||||
Output = R"cpp(
|
||||
namespace testns {
|
||||
class TestClass {
|
||||
class SubClass {};
|
||||
};
|
||||
}
|
||||
testns::TestClass::SubClass C = testns::TestClass::SubClass();
|
||||
)cpp";
|
||||
checkTransform(ID, Input, Output);
|
||||
|
||||
// check that namespaces are shortened
|
||||
Input = R"cpp(
|
||||
namespace testns {
|
||||
class TestClass {
|
||||
};
|
||||
void func() { ^auto C = TestClass(); }
|
||||
}
|
||||
)cpp";
|
||||
Output = R"cpp(
|
||||
namespace testns {
|
||||
class TestClass {
|
||||
};
|
||||
void func() { TestClass C = TestClass(); }
|
||||
}
|
||||
)cpp";
|
||||
checkTransform(ID, Input, Output);
|
||||
|
||||
// unknown types in a template should not be replaced
|
||||
Input = R"cpp(
|
||||
template <typename T> void x() {
|
||||
^auto y = T::z();
|
||||
}
|
||||
)cpp";
|
||||
checkApplyContainsError(ID, Input, "Could not deduce type for 'auto' type");
|
||||
|
||||
// undefined functions should not be replaced
|
||||
Input = R"cpp(
|
||||
a^uto x = doesnt_exist();
|
||||
)cpp";
|
||||
checkApplyContainsError(ID, Input, "Could not deduce type for 'auto' type");
|
||||
|
||||
// function pointers should not be replaced
|
||||
Input = R"cpp(
|
||||
int foo();
|
||||
au^to x = &foo;
|
||||
)cpp";
|
||||
checkApplyContainsError(ID, Input,
|
||||
"Could not expand type of function pointer");
|
||||
|
||||
// lambda types are not replaced
|
||||
Input = R"cpp(
|
||||
au^to x = []{};
|
||||
)cpp";
|
||||
checkApplyContainsError(ID, Input,
|
||||
"Could not expand type of lambda expression");
|
||||
|
||||
// inline namespaces
|
||||
Input = R"cpp(
|
||||
inline namespace x {
|
||||
namespace { struct S; }
|
||||
}
|
||||
au^to y = S();
|
||||
)cpp";
|
||||
Output = R"cpp(
|
||||
inline namespace x {
|
||||
namespace { struct S; }
|
||||
}
|
||||
S y = S();
|
||||
)cpp";
|
||||
|
||||
// local class
|
||||
Input = R"cpp(
|
||||
namespace x {
|
||||
void y() {
|
||||
struct S{};
|
||||
a^uto z = S();
|
||||
}}
|
||||
)cpp";
|
||||
Output = R"cpp(
|
||||
namespace x {
|
||||
void y() {
|
||||
struct S{};
|
||||
S z = S();
|
||||
}}
|
||||
)cpp";
|
||||
checkTransform(ID, Input, Output);
|
||||
|
||||
// replace array types
|
||||
Input = R"cpp(
|
||||
au^to x = "test";
|
||||
)cpp";
|
||||
Output = R"cpp(
|
||||
const char * x = "test";
|
||||
)cpp";
|
||||
checkTransform(ID, Input, Output);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
|
|
@ -2139,6 +2139,28 @@ TEST(FindReferences, NoQueryForLocalSymbols) {
|
|||
}
|
||||
}
|
||||
|
||||
TEST(GetDeducedType, KwAutoExpansion) {
|
||||
struct Test {
|
||||
StringRef AnnotatedCode;
|
||||
const char *DeducedType;
|
||||
} Tests[] = {
|
||||
{"^auto i = 0;", "int"},
|
||||
{"^auto f(){ return 1;};", "int"}
|
||||
};
|
||||
for (Test T : Tests) {
|
||||
Annotations File(T.AnnotatedCode);
|
||||
auto AST = TestTU::withCode(File.code()).build();
|
||||
ASSERT_TRUE(AST.getDiagnostics().empty()) << AST.getDiagnostics().begin()->Message;
|
||||
SourceManagerForFile SM("foo.cpp", File.code());
|
||||
|
||||
for (Position Pos : File.points()) {
|
||||
auto Location = sourceLocationInMainFile(SM.get(), Pos);
|
||||
auto DeducedType = getDeducedType(AST, *Location);
|
||||
EXPECT_EQ(DeducedType->getAsString(), T.DeducedType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
|
Loading…
Reference in New Issue