Implement __builtin_LINE() et. al. to support source location capture.
Summary:
This patch implements the source location builtins `__builtin_LINE(), `__builtin_FUNCTION()`, `__builtin_FILE()` and `__builtin_COLUMN()`. These builtins are needed to implement [`std::experimental::source_location`](https://rawgit.com/cplusplus/fundamentals-ts/v2/main.html#reflection.src_loc.creation).
With the exception of `__builtin_COLUMN`, GCC also implements these builtins, and Clangs behavior is intended to match as closely as possible.
Reviewers: rsmith, joerg, aaron.ballman, bogner, majnemer, shafik, martong
Reviewed By: rsmith
Subscribers: rnkovacs, loskutov, riccibruno, mgorny, kunitoki, alexr, majnemer, hfinkel, cfe-commits
Differential Revision: https://reviews.llvm.org/D37035
llvm-svn: 360937
diff --git a/clang/test/SemaCXX/source_location.cpp b/clang/test/SemaCXX/source_location.cpp
new file mode 100644
index 0000000..b312f12
--- /dev/null
+++ b/clang/test/SemaCXX/source_location.cpp
@@ -0,0 +1,590 @@
+// 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 {
+namespace experimental {
+struct source_location {
+private:
+ unsigned int __m_line = 0;
+ unsigned int __m_col = 0;
+ const char *__m_file = nullptr;
+ const char *__m_func = nullptr;
+public:
+ static constexpr source_location current(
+ const char *__file = __builtin_FILE(),
+ const char *__func = __builtin_FUNCTION(),
+ unsigned int __line = __builtin_LINE(),
+ unsigned int __col = __builtin_COLUMN()) noexcept {
+ source_location __loc;
+ __loc.__m_line = __line;
+ __loc.__m_col = __col;
+ __loc.__m_file = __file;
+ __loc.__m_func = __func;
+ return __loc;
+ }
+ constexpr source_location() = default;
+ constexpr source_location(source_location const &) = default;
+ constexpr unsigned int line() const noexcept { return __m_line; }
+ constexpr unsigned int column() const noexcept { return __m_col; }
+ constexpr const char *file() const noexcept { return __m_file; }
+ constexpr const char *function() const noexcept { return __m_func; }
+};
+} // namespace experimental
+} // namespace std
+
+using SL = std::experimental::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 *>);
+
+// test noexcept
+static_assert(noexcept(__builtin_LINE()));
+static_assert(noexcept(__builtin_COLUMN()));
+static_assert(noexcept(__builtin_FILE()));
+static_assert(noexcept(__builtin_FUNCTION()));
+
+//===----------------------------------------------------------------------===//
+// __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(__func__, 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_assert(!is_equal(Default.info.function(), __func__));
+ static_assert(is_equal(Default.info.function(), "TestCtor"));
+ static_assert(is_equal(Template.info.function(), "TestCtor"));
+ static_assert(is_equal(Template.ctor_info.function(), __func__));
+}
+
+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(), "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());
+}