llvm-project/clang/test/SemaCXX/source_location.cpp
James Y Knight d614874900 [Clang] Implement __builtin_source_location.
This builtin returns the address of a global instance of the
`std::source_location::__impl` type, which must be defined (with an
appropriate shape) before calling the builtin.

It will be used to implement std::source_location in libc++ in a
future change. The builtin is compatible with GCC's implementation,
and libstdc++'s usage. An intentional divergence is that GCC declares
the builtin's return type to be `const void*` (for
ease-of-implementation reasons), while Clang uses the actual type,
`const std::source_location::__impl*`.

In order to support this new functionality, I've also added a new
'UnnamedGlobalConstantDecl'. This artificial Decl is modeled after
MSGuidDecl, and is used to represent a generic concept of an lvalue
constant with global scope, deduplicated by its value. It's possible
that MSGuidDecl itself, or some of the other similar sorts of things
in Clang might be able to be refactored onto this more-generic
concept, but there's enough special-case weirdness in MSGuidDecl that
I gave up attempting to share code there, at least for now.

Finally, for compatibility with libstdc++'s <source_location> header,
I've added a second exception to the "cannot cast from void* to T* in
constant evaluation" rule. This seems a bit distasteful, but feels
like the best available option.

Reviewers: aaron.ballman, erichkeane

Differential Revision: https://reviews.llvm.org/D120159
2022-03-28 18:29:02 -04:00

596 lines
17 KiB
C++

// RUN: %clang_cc1 -std=c++1z -fcxx-exceptions -fexceptions -verify %s
// expected-no-diagnostics
#define assert(...) ((__VA_ARGS__) ? ((void)0) : throw 42)
#define CURRENT_FROM_MACRO() SL::current()
#define FORWARD(...) __VA_ARGS__
template <unsigned>
struct Printer;
namespace std {
class source_location {
struct __impl;
public:
static constexpr source_location current(const __impl *__p = __builtin_source_location()) noexcept {
source_location __loc;
__loc.__m_impl = __p;
return __loc;
}
constexpr source_location() = default;
constexpr source_location(source_location const &) = default;
constexpr unsigned int line() const noexcept { return __m_impl ? __m_impl->_M_line : 0; }
constexpr unsigned int column() const noexcept { return __m_impl ? __m_impl->_M_column : 0; }
constexpr const char *file() const noexcept { return __m_impl ? __m_impl->_M_file_name : ""; }
constexpr const char *function() const noexcept { return __m_impl ? __m_impl->_M_function_name : ""; }
private:
// Note: The type name "std::source_location::__impl", and its constituent
// field-names are required by __builtin_source_location().
struct __impl {
const char *_M_file_name;
const char *_M_function_name;
unsigned _M_line;
unsigned _M_column;
};
const __impl *__m_impl = nullptr;
public:
using public_impl_alias = __impl;
};
} // namespace std
using SL = std::source_location;
#include "Inputs/source-location-file.h"
namespace SLF = source_location_file;
constexpr bool is_equal(const char *LHS, const char *RHS) {
while (*LHS != 0 && *RHS != 0) {
if (*LHS != *RHS)
return false;
++LHS;
++RHS;
}
return *LHS == 0 && *RHS == 0;
}
template <class T>
constexpr T identity(T t) {
return t;
}
template <class T, class U>
struct Pair {
T first;
U second;
};
template <class T, class U>
constexpr bool is_same = false;
template <class T>
constexpr bool is_same<T, T> = true;
// test types
static_assert(is_same<decltype(__builtin_LINE()), unsigned>);
static_assert(is_same<decltype(__builtin_COLUMN()), unsigned>);
static_assert(is_same<decltype(__builtin_FILE()), const char *>);
static_assert(is_same<decltype(__builtin_FUNCTION()), const char *>);
static_assert(is_same<decltype(__builtin_source_location()), const std::source_location::public_impl_alias *>);
// test noexcept
static_assert(noexcept(__builtin_LINE()));
static_assert(noexcept(__builtin_COLUMN()));
static_assert(noexcept(__builtin_FILE()));
static_assert(noexcept(__builtin_FUNCTION()));
static_assert(noexcept(__builtin_source_location()));
//===----------------------------------------------------------------------===//
// __builtin_LINE()
//===----------------------------------------------------------------------===//
namespace test_line {
static_assert(SL::current().line() == __LINE__);
static_assert(SL::current().line() == CURRENT_FROM_MACRO().line());
static constexpr SL GlobalS = SL::current();
static_assert(GlobalS.line() == __LINE__ - 2);
// clang-format off
constexpr bool test_line_fn() {
constexpr SL S = SL::current();
static_assert(S.line() == (__LINE__ - 1), "");
// The start of the call expression to `current()` begins at the token `SL`
constexpr int ExpectLine = __LINE__ + 3;
constexpr SL S2
=
SL // Call expression starts here
::
current
(
)
;
static_assert(S2.line() == ExpectLine, "");
static_assert(
FORWARD(
__builtin_LINE
(
)
)
== __LINE__ - 1, "");
static_assert(\
\
__builtin_LINE()\
\
== __LINE__ - 2, "");
static_assert(\
_\
_builtin_LINE()
== __LINE__ - 2, "");
return true;
}
// clang-format on
static_assert(test_line_fn());
static_assert(__builtin_LINE() == __LINE__, "");
constexpr int baz() { return 101; }
constexpr int test_line_fn_simple(int z = baz(), int x = __builtin_LINE()) {
return x;
}
void bar() {
static_assert(test_line_fn_simple() == __LINE__, "");
static_assert(test_line_fn_simple() == __LINE__, "");
}
struct CallExpr {
constexpr int operator()(int x = __builtin_LINE()) const { return x; }
};
constexpr CallExpr get_call() { return CallExpr{}; }
static_assert(get_call()() == __LINE__, "");
template <class T>
constexpr bool test_line_fn_template(T Expect, int L = __builtin_LINE()) {
return Expect == L;
}
static_assert(test_line_fn_template(__LINE__));
struct InMemInit {
constexpr bool check(int expect) const {
return info.line() == expect;
}
SL info = SL::current();
InMemInit() = default;
constexpr InMemInit(int) {}
};
static_assert(InMemInit{}.check(__LINE__ - 3), "");
static_assert(InMemInit{42}.check(__LINE__ - 3), "");
template <class T, class U = SL>
struct InMemInitTemplate {
constexpr bool check(int expect) const {
return info.line() == expect;
}
U info = U::current();
InMemInitTemplate() = default;
constexpr InMemInitTemplate(T) {}
constexpr InMemInitTemplate(T, T) : info(U::current()) {}
template <class V = U> constexpr InMemInitTemplate(T, T, T, V info = U::current())
: info(info) {}
};
void test_mem_init_template() {
constexpr int line_offset = 8;
static_assert(InMemInitTemplate<int>{}.check(__LINE__ - line_offset), "");
static_assert(InMemInitTemplate<unsigned>{42}.check(__LINE__ - line_offset), "");
static_assert(InMemInitTemplate<unsigned>{42, 42}.check(__LINE__ - line_offset), "");
static_assert(InMemInitTemplate<unsigned>{42, 42, 42}.check(__LINE__), "");
}
struct AggInit {
int x;
int y = __builtin_LINE();
constexpr bool check(int expect) const {
return y == expect;
}
};
constexpr AggInit AI{42};
static_assert(AI.check(__LINE__ - 1), "");
template <class T, class U = SL>
struct AggInitTemplate {
constexpr bool check(int expect) const {
return expect == info.line();
}
T x;
U info = U::current();
};
template <class T, class U = SL>
constexpr U test_fn_template(T, U u = U::current()) {
return u;
}
void fn_template_tests() {
static_assert(test_fn_template(42).line() == __LINE__, "");
}
struct TestMethodTemplate {
template <class T, class U = SL, class U2 = SL>
constexpr U get(T, U u = U::current(), U2 u2 = identity(U2::current())) const {
assert(u.line() == u2.line());
return u;
}
};
void method_template_tests() {
static_assert(TestMethodTemplate{}.get(42).line() == __LINE__, "");
}
struct InStaticInit {
static constexpr int LINE = __LINE__;
static constexpr const int x1 = __builtin_LINE();
static constexpr const int x2 = identity(__builtin_LINE());
static const int x3;
const int x4 = __builtin_LINE();
int x5 = __builtin_LINE();
};
const int InStaticInit::x3 = __builtin_LINE();
static_assert(InStaticInit::x1 == InStaticInit::LINE + 1, "");
static_assert(InStaticInit::x2 == InStaticInit::LINE + 2, "");
template <class T, int N = __builtin_LINE(), int Expect = -1>
constexpr void check_fn_template_param(T) {
constexpr int RealExpect = Expect == -1 ? __LINE__ - 2 : Expect;
static_assert(N == RealExpect);
}
template void check_fn_template_param(int);
template void check_fn_template_param<long, 42, 42>(long);
#line 100
struct AggBase {
#line 200
int x = __builtin_LINE();
int y = __builtin_LINE();
int z = __builtin_LINE();
};
#line 300
struct AggDer : AggBase {
};
#line 400
static_assert(AggDer{}.x == 400, "");
struct ClassBase {
#line 400
int x = __builtin_LINE();
int y = 0;
int z = 0;
#line 500
ClassBase() = default;
constexpr ClassBase(int yy, int zz = __builtin_LINE())
: y(yy), z(zz) {}
};
struct ClassDer : ClassBase {
#line 600
ClassDer() = default;
constexpr ClassDer(int yy) : ClassBase(yy) {}
constexpr ClassDer(int yy, int zz) : ClassBase(yy, zz) {}
};
#line 700
static_assert(ClassDer{}.x == 500, "");
static_assert(ClassDer{42}.x == 501, "");
static_assert(ClassDer{42}.z == 601, "");
static_assert(ClassDer{42, 42}.x == 501, "");
struct ClassAggDer : AggBase {
#line 800
ClassAggDer() = default;
constexpr ClassAggDer(int, int x = __builtin_LINE()) : AggBase{x} {}
};
static_assert(ClassAggDer{}.x == 100, "");
} // namespace test_line
//===----------------------------------------------------------------------===//
// __builtin_FILE()
//===----------------------------------------------------------------------===//
namespace test_file {
constexpr const char *test_file_simple(const char *__f = __builtin_FILE()) {
return __f;
}
void test_function() {
#line 900
static_assert(is_equal(test_file_simple(), __FILE__));
static_assert(is_equal(SLF::test_function().file(), __FILE__), "");
static_assert(is_equal(SLF::test_function_template(42).file(), __FILE__), "");
static_assert(is_equal(SLF::test_function_indirect().file(), SLF::global_info.file()), "");
static_assert(is_equal(SLF::test_function_template_indirect(42).file(), SLF::global_info.file()), "");
static_assert(test_file_simple() != nullptr);
static_assert(!is_equal(test_file_simple(), "source_location.cpp"));
}
void test_class() {
#line 315
using SLF::TestClass;
constexpr TestClass Default;
constexpr TestClass InParam{42};
constexpr TestClass Template{42, 42};
constexpr auto *F = Default.info.file();
constexpr auto Char = F[0];
static_assert(is_equal(Default.info.file(), SLF::FILE), "");
static_assert(is_equal(InParam.info.file(), SLF::FILE), "");
static_assert(is_equal(InParam.ctor_info.file(), __FILE__), "");
}
void test_aggr_class() {
using Agg = SLF::AggrClass<>;
constexpr Agg Default{};
constexpr Agg InitOne{42};
static_assert(is_equal(Default.init_info.file(), __FILE__), "");
static_assert(is_equal(InitOne.init_info.file(), __FILE__), "");
}
} // namespace test_file
//===----------------------------------------------------------------------===//
// __builtin_FUNCTION()
//===----------------------------------------------------------------------===//
namespace test_func {
constexpr const char *test_func_simple(const char *__f = __builtin_FUNCTION()) {
return __f;
}
constexpr const char *get_function() {
return __func__;
}
constexpr bool test_function() {
return is_equal(__func__, test_func_simple()) &&
!is_equal(get_function(), test_func_simple());
}
static_assert(test_function());
template <class T, class U = SL>
constexpr Pair<U, U> test_func_template(T, U u = U::current()) {
static_assert(is_equal(__PRETTY_FUNCTION__, U::current().function()));
return {u, U::current()};
}
template <class T>
void func_template_tests() {
constexpr auto P = test_func_template(42);
//static_assert(is_equal(P.first.function(), __func__), "");
//static_assert(!is_equal(P.second.function(), __func__), "");
}
template void func_template_tests<int>();
template <class = int, class T = SL>
struct TestCtor {
T info = T::current();
T ctor_info;
TestCtor() = default;
template <class U = SL>
constexpr TestCtor(int, U u = U::current()) : ctor_info(u) {}
};
void ctor_tests() {
constexpr TestCtor<> Default;
constexpr TestCtor<> Template{42};
static const char *XYZZY = Template.info.function();
static_assert(is_equal(Default.info.function(), "test_func::TestCtor<>::TestCtor() [T = std::source_location]"));
static_assert(is_equal(Default.ctor_info.function(), ""));
static_assert(is_equal(Template.info.function(), "test_func::TestCtor<>::TestCtor(int, U) [T = std::source_location, U = std::source_location]"));
static_assert(is_equal(Template.ctor_info.function(), __PRETTY_FUNCTION__));
}
constexpr SL global_sl = SL::current();
static_assert(is_equal(global_sl.function(), ""));
} // namespace test_func
//===----------------------------------------------------------------------===//
// __builtin_COLUMN()
//===----------------------------------------------------------------------===//
namespace test_column {
// clang-format off
constexpr bool test_column_fn() {
constexpr SL S = SL::current();
static_assert(S.line() == (__LINE__ - 1), "");
constexpr int Indent = 4;
{
// The start of the call expression to `current()` begins at the token `SL`
constexpr int ExpectCol = Indent + 3;
constexpr SL S2
=
SL // Call expression starts here
::
current
(
)
;
static_assert(S2.column() == ExpectCol, "");
}
{
constexpr int ExpectCol = 2;
constexpr int C =
__builtin_COLUMN // Expect call expression to start here
();
static_assert(C == ExpectCol);
}
return true;
}
#line 420
static_assert(test_column_fn());
// Test that the column matches the start of the call expression 'SL::current()'
static_assert(SL::current().column() == __builtin_strlen("static_assert(S"));
struct TestClass {
int x = __builtin_COLUMN();
TestClass() = default; /* indented to 3 spaces for testing */
constexpr TestClass(int, int o = __builtin_COLUMN()) : x(o) {}
};
struct TestAggClass {
int x = __builtin_COLUMN();
};
constexpr bool test_class() {
auto check = [](int V, const char* S, int indent = 4) {
assert(V == (__builtin_strlen(S) + indent));
};
{
TestClass t{};
check(t.x, " T", 0); // Start of default constructor decl.
}
{
TestClass t1
{42};
check(t1.x, "TestClass t"); // Start of variable being constructed.
}
{
TestAggClass t { };
check(t.x, "TestAggClass t { }");
}
{
TestAggClass t = { };
check(t.x, "TestAggClass t = { }");
}
return true;
}
static_assert(test_class());
// clang-format on
} // namespace test_column
// Test [reflection.src_loc.creation]p2
// > The value should be affected by #line (C++14 16.4) in the same manner as
// > for __LINE__ and __FILE__.
namespace test_pragma_line {
constexpr int StartLine = 42;
#line 42
static_assert(__builtin_LINE() == StartLine);
static_assert(__builtin_LINE() == StartLine + 1);
static_assert(SL::current().line() == StartLine + 2);
#line 44 "test_file.c"
static_assert(is_equal("test_file.c", __FILE__));
static_assert(is_equal("test_file.c", __builtin_FILE()));
static_assert(is_equal("test_file.c", SL::current().file()));
static_assert(is_equal("test_file.c", SLF::test_function().file()));
static_assert(is_equal(SLF::FILE, SLF::test_function_indirect().file()));
} // end namespace test_pragma_line
namespace test_out_of_line_init {
#line 4000 "test_out_of_line_init.cpp"
constexpr unsigned get_line(unsigned n = __builtin_LINE()) { return n; }
constexpr const char *get_file(const char *f = __builtin_FILE()) { return f; }
constexpr const char *get_func(const char *f = __builtin_FUNCTION()) { return f; }
#line 4100 "A.cpp"
struct A {
int n = __builtin_LINE();
int n2 = get_line();
const char *f = __builtin_FILE();
const char *f2 = get_file();
const char *func = __builtin_FUNCTION();
const char *func2 = get_func();
SL info = SL::current();
};
#line 4200 "B.cpp"
struct B {
A a = {};
};
#line 4300 "test_passed.cpp"
constexpr B b = {};
static_assert(b.a.n == 4300, "");
static_assert(b.a.n2 == 4300, "");
static_assert(b.a.info.line() == 4300, "");
static_assert(is_equal(b.a.f, "test_passed.cpp"));
static_assert(is_equal(b.a.f2, "test_passed.cpp"));
static_assert(is_equal(b.a.info.file(), "test_passed.cpp"));
static_assert(is_equal(b.a.func, ""));
static_assert(is_equal(b.a.func2, ""));
static_assert(is_equal(b.a.info.function(), ""));
constexpr bool test_in_func() {
#line 4400 "test_func_passed.cpp"
constexpr B b = {};
static_assert(b.a.n == 4400, "");
static_assert(b.a.n2 == 4400, "");
static_assert(b.a.info.line() == 4400, "");
static_assert(is_equal(b.a.f, "test_func_passed.cpp"));
static_assert(is_equal(b.a.f2, "test_func_passed.cpp"));
static_assert(is_equal(b.a.info.file(), "test_func_passed.cpp"));
static_assert(is_equal(b.a.func, "test_in_func"));
static_assert(is_equal(b.a.func2, "test_in_func"));
static_assert(is_equal(b.a.info.function(), "bool test_out_of_line_init::test_in_func()"));
return true;
}
static_assert(test_in_func());
} // end namespace test_out_of_line_init
namespace test_global_scope {
#line 5000 "test_global_scope.cpp"
constexpr unsigned get_line(unsigned n = __builtin_LINE()) { return n; }
constexpr const char *get_file(const char *f = __builtin_FILE()) { return f; }
constexpr const char *get_func(const char *f = __builtin_FUNCTION()) { return f; }
#line 5100
struct InInit {
unsigned l = get_line();
const char *f = get_file();
const char *func = get_func();
#line 5200 "in_init.cpp"
constexpr InInit() {}
};
#line 5300
constexpr InInit II;
static_assert(II.l == 5200, "");
static_assert(is_equal(II.f, "in_init.cpp"));
static_assert(is_equal(II.func, "InInit"));
#line 5400
struct AggInit {
unsigned l = get_line();
const char *f = get_file();
const char *func = get_func();
};
#line 5500 "brace_init.cpp"
constexpr AggInit AI = {};
static_assert(AI.l == 5500);
static_assert(is_equal(AI.f, "brace_init.cpp"));
static_assert(is_equal(AI.func, ""));
} // namespace test_global_scope
namespace TestFuncInInit {
#line 6000 "InitClass.cpp"
struct Init {
SL info;
#line 6100 "InitCtor.cpp"
constexpr Init(SL info = SL::current()) : info(info) {}
};
#line 6200 "InitGlobal.cpp"
constexpr Init I;
static_assert(I.info.line() == 6200);
static_assert(is_equal(I.info.file(), "InitGlobal.cpp"));
} // namespace TestFuncInInit
namespace TestConstexprContext {
#line 7000 "TestConstexprContext.cpp"
constexpr const char* foo() { return __builtin_FILE(); }
#line 7100 "Bar.cpp"
constexpr const char* bar(const char* x = foo()) { return x; }
constexpr bool test() {
static_assert(is_equal(bar(), "TestConstexprContext.cpp"));
return true;
}
static_assert(test());
}