forked from OSchip/llvm-project
688 lines
18 KiB
C++
688 lines
18 KiB
C++
//===-- TweakTests.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 "Annotations.h"
|
|
#include "SourceCode.h"
|
|
#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"
|
|
#include "llvm/Support/Error.h"
|
|
#include "llvm/Testing/Support/Error.h"
|
|
#include "gmock/gmock-matchers.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
#include <cassert>
|
|
|
|
using llvm::Failed;
|
|
using llvm::Succeeded;
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
namespace {
|
|
|
|
std::string markRange(llvm::StringRef Code, Range R) {
|
|
size_t Begin = llvm::cantFail(positionToOffset(Code, R.start));
|
|
size_t End = llvm::cantFail(positionToOffset(Code, R.end));
|
|
assert(Begin <= End);
|
|
if (Begin == End) // Mark a single point.
|
|
return (Code.substr(0, Begin) + "^" + Code.substr(Begin)).str();
|
|
// Mark a range.
|
|
return (Code.substr(0, Begin) + "[[" + Code.substr(Begin, End - Begin) +
|
|
"]]" + Code.substr(End))
|
|
.str();
|
|
}
|
|
|
|
void checkAvailable(StringRef ID, llvm::StringRef Input, bool Available) {
|
|
Annotations Code(Input);
|
|
ASSERT_TRUE(0 < Code.points().size() || 0 < Code.ranges().size())
|
|
<< "no points of interest specified";
|
|
TestTU TU;
|
|
TU.Filename = "foo.cpp";
|
|
TU.Code = Code.code();
|
|
|
|
ParsedAST AST = TU.build();
|
|
|
|
auto CheckOver = [&](Range Selection) {
|
|
unsigned Begin = cantFail(positionToOffset(Code.code(), Selection.start));
|
|
unsigned End = cantFail(positionToOffset(Code.code(), Selection.end));
|
|
auto T = prepareTweak(ID, Tweak::Selection(AST, Begin, End));
|
|
if (Available)
|
|
EXPECT_THAT_EXPECTED(T, Succeeded())
|
|
<< "code is " << markRange(Code.code(), Selection);
|
|
else
|
|
EXPECT_THAT_EXPECTED(T, Failed())
|
|
<< "code is " << markRange(Code.code(), Selection);
|
|
};
|
|
for (auto P : Code.points())
|
|
CheckOver(Range{P, P});
|
|
for (auto R : Code.ranges())
|
|
CheckOver(R);
|
|
}
|
|
|
|
/// Checks action is available at every point and range marked in \p Input.
|
|
void checkAvailable(StringRef ID, llvm::StringRef Input) {
|
|
return checkAvailable(ID, Input, /*Available=*/true);
|
|
}
|
|
|
|
/// Same as checkAvailable, but checks the action is not available.
|
|
void checkNotAvailable(StringRef ID, llvm::StringRef Input) {
|
|
return checkAvailable(ID, Input, /*Available=*/false);
|
|
}
|
|
|
|
llvm::Expected<Tweak::Effect> apply(StringRef ID, llvm::StringRef Input) {
|
|
Annotations Code(Input);
|
|
Range SelectionRng;
|
|
if (Code.points().size() != 0) {
|
|
assert(Code.ranges().size() == 0 &&
|
|
"both a cursor point and a selection range were specified");
|
|
SelectionRng = Range{Code.point(), Code.point()};
|
|
} else {
|
|
SelectionRng = Code.range();
|
|
}
|
|
TestTU TU;
|
|
TU.Filename = "foo.cpp";
|
|
TU.Code = Code.code();
|
|
|
|
ParsedAST AST = TU.build();
|
|
unsigned Begin = cantFail(positionToOffset(Code.code(), SelectionRng.start));
|
|
unsigned End = cantFail(positionToOffset(Code.code(), SelectionRng.end));
|
|
Tweak::Selection S(AST, Begin, End);
|
|
|
|
auto T = prepareTweak(ID, S);
|
|
if (!T)
|
|
return T.takeError();
|
|
return (*T)->apply(S);
|
|
}
|
|
|
|
llvm::Expected<std::string> applyEdit(StringRef ID, llvm::StringRef Input) {
|
|
auto Effect = apply(ID, Input);
|
|
if (!Effect)
|
|
return Effect.takeError();
|
|
if (!Effect->ApplyEdit)
|
|
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
|
"No replacements");
|
|
Annotations Code(Input);
|
|
return applyAllReplacements(Code.code(), *Effect->ApplyEdit);
|
|
}
|
|
|
|
std::string getMessage(StringRef ID, llvm::StringRef Input) {
|
|
auto Effect = apply(ID, Input);
|
|
if (!Effect)
|
|
return "error: " + llvm::toString(Effect.takeError());
|
|
return Effect->ShowMessage.getValueOr("no message produced!");
|
|
}
|
|
|
|
void checkTransform(llvm::StringRef ID, llvm::StringRef Input,
|
|
std::string Output) {
|
|
auto Result = applyEdit(ID, Input);
|
|
ASSERT_TRUE(bool(Result)) << llvm::toString(Result.takeError()) << 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_THAT(llvm::toString(Result.takeError()),
|
|
testing::HasSubstr(ErrorMessage))
|
|
<< Input;
|
|
}
|
|
|
|
TEST(TweakTest, SwapIfBranches) {
|
|
llvm::StringLiteral ID = "SwapIfBranches";
|
|
|
|
checkAvailable(ID, R"cpp(
|
|
void test() {
|
|
^i^f^^(^t^r^u^e^) { return 100; } ^e^l^s^e^ { continue; }
|
|
}
|
|
)cpp");
|
|
|
|
checkNotAvailable(ID, R"cpp(
|
|
void test() {
|
|
if (true) {^return ^100;^ } else { ^continue^;^ }
|
|
}
|
|
)cpp");
|
|
|
|
llvm::StringLiteral Input = R"cpp(
|
|
void test() {
|
|
^if (true) { return 100; } else { continue; }
|
|
}
|
|
)cpp";
|
|
llvm::StringLiteral Output = R"cpp(
|
|
void test() {
|
|
if (true) { continue; } else { return 100; }
|
|
}
|
|
)cpp";
|
|
checkTransform(ID, Input, Output);
|
|
|
|
Input = R"cpp(
|
|
void test() {
|
|
^if () { return 100; } else { continue; }
|
|
}
|
|
)cpp";
|
|
Output = R"cpp(
|
|
void test() {
|
|
if () { continue; } else { return 100; }
|
|
}
|
|
)cpp";
|
|
checkTransform(ID, Input, Output);
|
|
|
|
// Available in subexpressions of the condition.
|
|
checkAvailable(ID, R"cpp(
|
|
void test() {
|
|
if(2 + [[2]] + 2) { return 2 + 2 + 2; } else { continue; }
|
|
}
|
|
)cpp");
|
|
// But not as part of the branches.
|
|
checkNotAvailable(ID, R"cpp(
|
|
void test() {
|
|
if(2 + 2 + 2) { return 2 + [[2]] + 2; } else { continue; }
|
|
}
|
|
)cpp");
|
|
// Range covers the "else" token, so available.
|
|
checkAvailable(ID, R"cpp(
|
|
void test() {
|
|
if(2 + 2 + 2) { return 2 + [[2 + 2; } else { continue;]] }
|
|
}
|
|
)cpp");
|
|
// Not available in compound statements in condition.
|
|
checkNotAvailable(ID, R"cpp(
|
|
void test() {
|
|
if([]{return [[true]];}()) { return 2 + 2 + 2; } else { continue; }
|
|
}
|
|
)cpp");
|
|
// Not available if both sides aren't braced.
|
|
checkNotAvailable(ID, R"cpp(
|
|
void test() {
|
|
^if (1) return; else { return; }
|
|
}
|
|
)cpp");
|
|
// Only one if statement is supported!
|
|
checkNotAvailable(ID, R"cpp(
|
|
[[if(1){}else{}if(2){}else{}]]
|
|
)cpp");
|
|
}
|
|
|
|
TEST(TweakTest, RawStringLiteral) {
|
|
llvm::StringLiteral ID = "RawStringLiteral";
|
|
|
|
checkAvailable(ID, R"cpp(
|
|
const char *A = ^"^f^o^o^\^n^";
|
|
const char *B = R"(multi )" ^"token " "str\ning";
|
|
)cpp");
|
|
|
|
checkNotAvailable(ID, R"cpp(
|
|
const char *A = ^"f^o^o^o^"; // no chars need escaping
|
|
const char *B = R"(multi )" ^"token " u8"str\ning"; // not all ascii
|
|
const char *C = ^R^"^(^multi )" "token " "str\ning"; // chunk is raw
|
|
const char *D = ^"token\n" __FILE__; // chunk is macro expansion
|
|
const char *E = ^"a\r\n"; // contains forbidden escape character
|
|
)cpp");
|
|
|
|
const char *Input = R"cpp(
|
|
const char *X = R"(multi
|
|
token)" "\nst^ring\n" "literal";
|
|
}
|
|
)cpp";
|
|
const char *Output = R"cpp(
|
|
const char *X = R"(multi
|
|
token
|
|
string
|
|
literal)";
|
|
}
|
|
)cpp";
|
|
checkTransform(ID, Input, Output);
|
|
}
|
|
|
|
TEST(TweakTest, DumpAST) {
|
|
llvm::StringLiteral ID = "DumpAST";
|
|
|
|
checkAvailable(ID, "^int f^oo() { re^turn 2 ^+ 2; }");
|
|
checkNotAvailable(ID, "/*c^omment*/ int foo() return 2 ^ + 2; }");
|
|
|
|
const char *Input = "int x = 2 ^+ 2;";
|
|
auto Result = getMessage(ID, Input);
|
|
EXPECT_THAT(Result, ::testing::HasSubstr("BinaryOperator"));
|
|
EXPECT_THAT(Result, ::testing::HasSubstr("'+'"));
|
|
EXPECT_THAT(Result, ::testing::HasSubstr("|-IntegerLiteral"));
|
|
EXPECT_THAT(Result,
|
|
::testing::HasSubstr("<col:9> 'int' 2\n`-IntegerLiteral"));
|
|
EXPECT_THAT(Result, ::testing::HasSubstr("<col:13> 'int' 2"));
|
|
}
|
|
|
|
TEST(TweakTest, ShowSelectionTree) {
|
|
llvm::StringLiteral ID = "ShowSelectionTree";
|
|
|
|
checkAvailable(ID, "^int f^oo() { re^turn 2 ^+ 2; }");
|
|
checkNotAvailable(ID, "/*c^omment*/ int foo() return 2 ^ + 2; }");
|
|
|
|
const char *Input = "int fcall(int); int x = fca[[ll(2 +]]2);";
|
|
const char *Output = R"(TranslationUnitDecl
|
|
VarDecl int x = fcall(2 + 2)
|
|
.CallExpr fcall(2 + 2)
|
|
ImplicitCastExpr fcall
|
|
.DeclRefExpr fcall
|
|
.BinaryOperator 2 + 2
|
|
*IntegerLiteral 2
|
|
)";
|
|
EXPECT_EQ(Output, getMessage(ID, Input));
|
|
}
|
|
|
|
TEST(TweakTest, DumpRecordLayout) {
|
|
llvm::StringLiteral ID = "DumpRecordLayout";
|
|
checkAvailable(ID, "^s^truct ^X ^{ int x; ^};");
|
|
checkNotAvailable(ID, "struct X { int ^a; };");
|
|
checkNotAvailable(ID, "struct ^X;");
|
|
checkNotAvailable(ID, "template <typename T> struct ^X { T t; };");
|
|
checkNotAvailable(ID, "enum ^X {};");
|
|
|
|
const char *Input = "struct ^X { int x; int y; }";
|
|
EXPECT_THAT(getMessage(ID, Input), ::testing::HasSubstr("0 | int x"));
|
|
}
|
|
TEST(TweakTest, ExtractVariable) {
|
|
llvm::StringLiteral ID = "ExtractVariable";
|
|
checkAvailable(ID, R"cpp(
|
|
int xyz() {
|
|
// return statement
|
|
return [[1]];
|
|
}
|
|
void f() {
|
|
int a = [[5 +]] [[4 * [[[[xyz]]()]]]];
|
|
// multivariable initialization
|
|
if(1)
|
|
int x = [[1]], y = [[a]] + 1, a = [[1]], z = a + 1;
|
|
// if without else
|
|
if([[1]])
|
|
a = [[1]];
|
|
// if with else
|
|
if(a < [[3]])
|
|
if(a == [[4]])
|
|
a = [[5]];
|
|
else
|
|
a = [[5]];
|
|
else if (a < [[4]])
|
|
a = [[4]];
|
|
else
|
|
a = [[5]];
|
|
// for loop
|
|
for(a = [[1]]; a > [[[[3]] + [[4]]]]; a++)
|
|
a = [[2]];
|
|
// while
|
|
while(a < [[1]])
|
|
[[a]]++;
|
|
// do while
|
|
do
|
|
a = [[1]];
|
|
while(a < [[3]]);
|
|
}
|
|
)cpp");
|
|
// Should not crash.
|
|
checkNotAvailable(ID, R"cpp(
|
|
template<typename T, typename ...Args>
|
|
struct Test<T, Args...> {
|
|
Test(const T &v) :val(^) {}
|
|
T val;
|
|
};
|
|
)cpp");
|
|
checkNotAvailable(ID, R"cpp(
|
|
int xyz(int a = [[1]]) {
|
|
return 1;
|
|
class T {
|
|
T(int a = [[1]]) {};
|
|
int xyz = [[1]];
|
|
};
|
|
}
|
|
// function default argument
|
|
void f(int b = [[1]]) {
|
|
// empty selection
|
|
int a = ^1 ^+ ^2;
|
|
// void expressions
|
|
auto i = new int, j = new int;
|
|
[[[[delete i]], delete j]];
|
|
// if
|
|
if(1)
|
|
int x = 1, y = a + 1, a = 1, z = [[a + 1]];
|
|
if(int a = 1)
|
|
if([[a]] == 4)
|
|
a = [[[[a]] +]] 1;
|
|
// for loop
|
|
for(int a = 1, b = 2, c = 3; [[a]] > [[b + c]]; [[a]]++)
|
|
a = [[a + 1]];
|
|
// lambda
|
|
auto lamb = [&[[a]], &[[b]]](int r = [[1]]) {return 1;}
|
|
}
|
|
)cpp");
|
|
// vector of pairs of input and output strings
|
|
const std::vector<std::pair<llvm::StringLiteral, llvm::StringLiteral>>
|
|
InputOutputs = {
|
|
// extraction from variable declaration/assignment
|
|
{R"cpp(void varDecl() {
|
|
int a = 5 * (4 + (3 [[- 1)]]);
|
|
})cpp",
|
|
R"cpp(void varDecl() {
|
|
auto dummy = (3 - 1); int a = 5 * (4 + dummy);
|
|
})cpp"},
|
|
// FIXME: extraction from switch case
|
|
/*{R"cpp(void f(int a) {
|
|
if(1)
|
|
while(a < 1)
|
|
switch (1) {
|
|
case 1:
|
|
a = [[1 + 2]];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
})cpp",
|
|
R"cpp(void f(int a) {
|
|
auto dummy = 1 + 2; if(1)
|
|
while(a < 1)
|
|
switch (1) {
|
|
case 1:
|
|
a = dummy;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
})cpp"},*/
|
|
// ensure InsertionPoint isn't inside a macro
|
|
{R"cpp(#define LOOP(x) {int a = x + 1;}
|
|
void f(int a) {
|
|
if(1)
|
|
LOOP(5 + [[3]])
|
|
})cpp",
|
|
R"cpp(#define LOOP(x) {int a = x + 1;}
|
|
void f(int a) {
|
|
auto dummy = 3; if(1)
|
|
LOOP(5 + dummy)
|
|
})cpp"},
|
|
// label and attribute testing
|
|
{R"cpp(void f(int a) {
|
|
label: [ [gsl::suppress("type")] ] for (;;) a = [[1]];
|
|
})cpp",
|
|
R"cpp(void f(int a) {
|
|
auto dummy = 1; label: [ [gsl::suppress("type")] ] for (;;) a = dummy;
|
|
})cpp"},
|
|
// FIXME: Doesn't work because bug in selection tree
|
|
/*{R"cpp(#define PLUS(x) x++
|
|
void f(int a) {
|
|
PLUS([[a]]);
|
|
})cpp",
|
|
R"cpp(#define PLUS(x) x++
|
|
void f(int a) {
|
|
auto dummy = a; PLUS(dummy);
|
|
})cpp"},*/
|
|
// FIXME: Doesn't work correctly for \[\[clang::uninitialized\]\] int
|
|
// b = [[1]]; since the attr is inside the DeclStmt and the bounds of
|
|
// DeclStmt don't cover the attribute
|
|
};
|
|
for (const auto &IO : InputOutputs) {
|
|
checkTransform(ID, IO.first, IO.second);
|
|
}
|
|
}
|
|
|
|
TEST(TweakTest, AnnotateHighlightings) {
|
|
llvm::StringLiteral ID = "AnnotateHighlightings";
|
|
checkAvailable(ID, "^vo^id^ ^f(^) {^}^"); // available everywhere.
|
|
const char *Input = "void ^f() {}";
|
|
const char *Output = "void /* entity.name.function.cpp */f() {}";
|
|
checkTransform(ID, Input, Output);
|
|
}
|
|
|
|
TEST(TweakTest, ExpandMacro) {
|
|
llvm::StringLiteral ID = "ExpandMacro";
|
|
|
|
// Available on macro names, not available anywhere else.
|
|
checkAvailable(ID, R"cpp(
|
|
#define FOO 1 2 3
|
|
#define FUNC(X) X+X+X
|
|
^F^O^O^ BAR ^F^O^O^
|
|
^F^U^N^C^(1)
|
|
)cpp");
|
|
checkNotAvailable(ID, R"cpp(
|
|
^#^d^efine^ ^FO^O 1 ^2 ^3^
|
|
FOO ^B^A^R^ FOO ^
|
|
FUNC(^1^)^
|
|
)cpp");
|
|
|
|
// Works as expected on object-like macros.
|
|
checkTransform(ID, R"cpp(
|
|
#define FOO 1 2 3
|
|
^FOO BAR FOO
|
|
)cpp",
|
|
R"cpp(
|
|
#define FOO 1 2 3
|
|
1 2 3 BAR FOO
|
|
)cpp");
|
|
checkTransform(ID, R"cpp(
|
|
#define FOO 1 2 3
|
|
FOO BAR ^FOO
|
|
)cpp",
|
|
R"cpp(
|
|
#define FOO 1 2 3
|
|
FOO BAR 1 2 3
|
|
)cpp");
|
|
|
|
// And function-like macros.
|
|
checkTransform(ID, R"cpp(
|
|
#define FUNC(X) X+X+X
|
|
F^UNC(2)
|
|
)cpp",
|
|
R"cpp(
|
|
#define FUNC(X) X+X+X
|
|
2 + 2 + 2
|
|
)cpp");
|
|
|
|
// Works on empty macros.
|
|
checkTransform(ID, R"cpp(
|
|
#define EMPTY
|
|
int a ^EMPTY;
|
|
)cpp",
|
|
R"cpp(
|
|
#define EMPTY
|
|
int a ;
|
|
)cpp");
|
|
checkTransform(ID, R"cpp(
|
|
#define EMPTY_FN(X)
|
|
int a ^EMPTY_FN(1 2 3);
|
|
)cpp",
|
|
R"cpp(
|
|
#define EMPTY_FN(X)
|
|
int a ;
|
|
)cpp");
|
|
checkTransform(ID, R"cpp(
|
|
#define EMPTY
|
|
#define EMPTY_FN(X)
|
|
int a = 123 ^EMPTY EMPTY_FN(1);
|
|
)cpp",
|
|
R"cpp(
|
|
#define EMPTY
|
|
#define EMPTY_FN(X)
|
|
int a = 123 EMPTY_FN(1);
|
|
)cpp");
|
|
checkTransform(ID, R"cpp(
|
|
#define EMPTY
|
|
#define EMPTY_FN(X)
|
|
int a = 123 ^EMPTY_FN(1) EMPTY;
|
|
)cpp",
|
|
R"cpp(
|
|
#define EMPTY
|
|
#define EMPTY_FN(X)
|
|
int a = 123 EMPTY;
|
|
)cpp");
|
|
checkTransform(ID, R"cpp(
|
|
#define EMPTY
|
|
#define EMPTY_FN(X)
|
|
int a = 123 EMPTY_FN(1) ^EMPTY;
|
|
)cpp",
|
|
R"cpp(
|
|
#define EMPTY
|
|
#define EMPTY_FN(X)
|
|
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);
|
|
|
|
Input = R"cpp(
|
|
namespace {
|
|
class Foo {};
|
|
}
|
|
au^to f = Foo();
|
|
)cpp";
|
|
Output = R"cpp(
|
|
namespace {
|
|
class Foo {};
|
|
}
|
|
Foo f = Foo();
|
|
)cpp";
|
|
checkTransform(ID, Input, Output);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace clangd
|
|
} // namespace clang
|