Skip to content

Commit

Permalink
fix(util): handle known UFCS corner cases (#506)
Browse files Browse the repository at this point in the history
* fix(util): handle known UFCS corner cases

Provide transparent SFINAE.
Forward `noexcept`.
Accept object with unparenthesized comma like `v<a, b>`.
Disable UFCS in `f := t().f();`.
Do not capture in signatures.

Incidentially, merge the UFCS macros.

* refactor: regenerate `reflect.h`

* refactor(util): use new UFCS macros

* test: add unit tests for fixed UFCS corner cases

* test: regenerate regression tests

* refactor(util): remove old UFCS branch

* refactor(util): comment the need for `CPP2_UFCS_IS_NOTHROW`

* refactor(to_cpp1): split name lookup from `ufcs_possible`

* refactor(to_cpp1): clarify name of `ufcs_possible`

* refactor(to_cpp1): rename `stack` variables to `guard`

* refactor(to_cpp1): add comment on added `stack` functions

* refactor(to_cpp1): invert meaning of result to match rename

* fix(to_cpp1): a using declaration doesn't name a variable

* fix(to_cpp1): do not capture in UFCS of type scope alias

* fix(to_cpp1): do not capture in UFCS of type scope alias

* refactor(to_cpp1): regroup conditions more naturally

* test: regenerate test-results

* fix(to_cpp1): do capture in UFCS of contract

* test: regenerate test-results

* fix(to_cpp1): emit qualified UFCS template call correctly

* fix(to_cpp1): emit qualified UFCS template call correctly

* fix(util): workaround MSVC bug for UFCS of 'F' in member 'F'

* test: disable the simpler test case due to the GCC bug

* test: disable test cases now failing on MSVC

* Results of testing the PR incl. with MSVC 2022

And minor code tweaks

* refactor(to_cpp1): apply review comment and rename name lookup

* test: regenerate test-results

* test: add case for the happy path

* test: regenerate UFCS tests

---------

Co-authored-by: Herb Sutter <[email protected]>
  • Loading branch information
JohelEGP and hsutter authored Dec 3, 2023
1 parent b9e73e0 commit a7ab29e
Show file tree
Hide file tree
Showing 102 changed files with 1,698 additions and 526 deletions.
171 changes: 86 additions & 85 deletions include/cpp2util.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
// in our -pure-cpp2 "import std;" simulation mode... if you need this,
// use mixed mode (not -pure-cpp2) and #include all the headers you need
// including this one
//
//
// #include <execution>
#ifdef __cpp_lib_expected
#include <expected>
Expand Down Expand Up @@ -526,7 +526,7 @@ template<typename T>
auto Typeid() -> decltype(auto) {
#ifdef CPP2_NO_RTTI
Type.expects(
!"'any' dynamic casting is disabled with -fno-rtti", // more likely to appear on console
!"'any' dynamic casting is disabled with -fno-rtti", // more likely to appear on console
"'any' dynamic casting is disabled with -fno-rtti" // make message available to hooked handlers
);
#else
Expand Down Expand Up @@ -575,7 +575,7 @@ struct {
template<typename T>
[[nodiscard]] auto cpp2_new(auto&& ...args) const -> std::shared_ptr<T> {
// Prefer { } to ( ) as noted for unique.new
//
//
// Note this does mean we don't get the make_shared optimization a lot
// of the time -- we can restore that as soon as make_shared improves to
// allow list initialization. But the make_shared optimization isn't a
Expand Down Expand Up @@ -745,13 +745,22 @@ class out {
//
//-----------------------------------------------------------------------
//
// Workaround <https://github.com/llvm/llvm-project/issues/70556>.
#define CPP2_FORCE_INLINE_LAMBDA_CLANG /* empty */

#if defined(_MSC_VER) && !defined(__clang_major__)
#define CPP2_FORCE_INLINE __forceinline
#define CPP2_FORCE_INLINE_LAMBDA [[msvc::forceinline]]
#define CPP2_FORCE_INLINE __forceinline
#define CPP2_FORCE_INLINE_LAMBDA [[msvc::forceinline]]
#define CPP2_LAMBDA_NO_DISCARD
#else
#define CPP2_FORCE_INLINE __attribute__((always_inline))
#define CPP2_FORCE_INLINE_LAMBDA __attribute__((always_inline))
#define CPP2_FORCE_INLINE __attribute__((always_inline))
#if defined(__clang__)
#define CPP2_FORCE_INLINE_LAMBDA /* empty */
#undef CPP2_FORCE_INLINE_LAMBDA_CLANG
#define CPP2_FORCE_INLINE_LAMBDA_CLANG __attribute__((always_inline))
#else
#define CPP2_FORCE_INLINE_LAMBDA __attribute__((always_inline))
#endif

#if defined(__clang_major__)
// Also check __cplusplus, only to satisfy Clang -pedantic-errors
Expand All @@ -776,85 +785,77 @@ class out {
#endif
#endif


// Note: [&] is because a nested UFCS might be viewed as trying to capture 'this'

#define CPP2_UFCS(FUNCNAME,PARAM1,...) \
[&] CPP2_LAMBDA_NO_DISCARD (auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
if constexpr (requires{ CPP2_FORWARD(obj).FUNCNAME(CPP2_FORWARD(params)...); }) { \
return CPP2_FORWARD(obj).FUNCNAME(CPP2_FORWARD(params)...); \
} else { \
return FUNCNAME(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
} \
}(PARAM1, __VA_ARGS__)

#define CPP2_UFCS_0(FUNCNAME,PARAM1) \
[&] CPP2_LAMBDA_NO_DISCARD (auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
if constexpr (requires{ CPP2_FORWARD(obj).FUNCNAME(); }) { \
return CPP2_FORWARD(obj).FUNCNAME(); \
} else { \
return FUNCNAME(CPP2_FORWARD(obj)); \
} \
}(PARAM1)

#define CPP2_UFCS_REMPARENS(...) __VA_ARGS__

#define CPP2_UFCS_TEMPLATE(FUNCNAME,TEMPARGS,PARAM1,...) \
[&] CPP2_LAMBDA_NO_DISCARD (auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
if constexpr (requires{ CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(params)...); }) { \
return CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(params)...); \
} else { \
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
} \
}(PARAM1, __VA_ARGS__)

#define CPP2_UFCS_TEMPLATE_0(FUNCNAME,TEMPARGS,PARAM1) \
[&] CPP2_LAMBDA_NO_DISCARD (auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
if constexpr (requires{ CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); }) { \
return CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); \
} else { \
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(obj)); \
} \
}(PARAM1)


// But for non-local lambdas [&] is not allowed

#define CPP2_UFCS_NONLOCAL(FUNCNAME,PARAM1,...) \
[] CPP2_LAMBDA_NO_DISCARD (auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
if constexpr (requires{ CPP2_FORWARD(obj).FUNCNAME(CPP2_FORWARD(params)...); }) { \
return CPP2_FORWARD(obj).FUNCNAME(CPP2_FORWARD(params)...); \
} else { \
return FUNCNAME(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
} \
}(PARAM1, __VA_ARGS__)
// Ideally, the expression `CPP2_UFCS_IS_NOTHROW` expands to
// is in the _noexcept-specifier_ of the UFCS lambda, but without 'std::declval'.
// To workaround [GCC bug 101043](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101043),
// we instead make it a template parameter of the UFCS lambda.
// But using a template parameter, Clang also ICEs on an application.
// So we use these `NOTHROW` macros to fall back to the ideal for when not using GCC.
#define CPP2_UFCS_IS_NOTHROW(QUALID,TEMPKW,...) \
requires { requires requires { std::declval<Obj>().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval<Params>()...); }; \
requires noexcept(std::declval<Obj>().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval<Params>()...)); } \
|| requires { requires !requires { std::declval<Obj>().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval<Params>()...); }; \
requires noexcept(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__(std::declval<Obj>(), std::declval<Params>()...)); }
#define CPP2_UFCS_IS_NOTHROW_PARAM(...) /*empty*/
#define CPP2_UFCS_IS_NOTHROW_ARG(QUALID,TEMPKW,...) CPP2_UFCS_IS_NOTHROW(QUALID,TEMPKW,__VA_ARGS__)
#if defined(__GNUC__) && !defined(__clang__)
#undef CPP2_UFCS_IS_NOTHROW_PARAM
#undef CPP2_UFCS_IS_NOTHROW_ARG
#define CPP2_UFCS_IS_NOTHROW_PARAM(QUALID,TEMPKW,...) , bool IsNothrow = CPP2_UFCS_IS_NOTHROW(QUALID,TEMPKW,__VA_ARGS__)
#define CPP2_UFCS_IS_NOTHROW_ARG(...) IsNothrow
#if __GNUC__ < 11
#undef CPP2_UFCS_IS_NOTHROW_PARAM
#undef CPP2_UFCS_IS_NOTHROW_ARG
#define CPP2_UFCS_IS_NOTHROW_PARAM(...) /*empty*/
#define CPP2_UFCS_IS_NOTHROW_ARG(...) false // GCC 10 UFCS is always potentially-throwing.
#endif
#endif

#define CPP2_UFCS_0_NONLOCAL(FUNCNAME,PARAM1) \
[] CPP2_LAMBDA_NO_DISCARD (auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
if constexpr (requires{ CPP2_FORWARD(obj).FUNCNAME(); }) { \
return CPP2_FORWARD(obj).FUNCNAME(); \
} else { \
return FUNCNAME(CPP2_FORWARD(obj)); \
} \
}(PARAM1)
// Ideally, the expression `CPP2_UFCS_CONSTRAINT_ARG` expands to
// is in the _requires-clause_ of the UFCS lambda.
// To workaround an MSVC bug within a member function 'F' where UFCS is also for 'F'
// (<https://github.com/hsutter/cppfront/pull/506#issuecomment-1826086952>),
// we instead make it a template parameter of the UFCS lambda.
// But using a template parameter, Clang also ICEs and GCC rejects a local 'F'.
// Also, Clang rejects the SFINAE test case when using 'std::declval'.
// So we use these `CONSTRAINT` macros to fall back to the ideal for when not using MSVC.
#define CPP2_UFCS_CONSTRAINT_PARAM(...) /*empty*/
#define CPP2_UFCS_CONSTRAINT_ARG(QUALID,TEMPKW,...) \
requires { CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); } \
|| requires { CPP2_UFCS_REMPARENS QUALID __VA_ARGS__(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); }
#if defined(_MSC_VER)
#undef CPP2_UFCS_CONSTRAINT_PARAM
#undef CPP2_UFCS_CONSTRAINT_ARG
#define CPP2_UFCS_CONSTRAINT_PARAM(QUALID,TEMPKW,...) , bool IsViable = \
requires { std::declval<Obj>().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval<Params>()...); } \
|| requires { CPP2_UFCS_REMPARENS QUALID __VA_ARGS__(std::declval<Obj>(), std::declval<Params>()...); }
#define CPP2_UFCS_CONSTRAINT_ARG(...) IsViable
#endif

#define CPP2_UFCS_TEMPLATE_NONLOCAL(FUNCNAME,TEMPARGS,PARAM1,...) \
[] CPP2_LAMBDA_NO_DISCARD (auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
if constexpr (requires{ CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(params)...); }) { \
return CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(params)...); \
#define CPP2_UFCS_(LAMBDADEFCAPT,QUALID,TEMPKW,...) \
[LAMBDADEFCAPT]< \
typename Obj, typename... Params \
CPP2_UFCS_IS_NOTHROW_PARAM(QUALID,TEMPKW,__VA_ARGS__) \
CPP2_UFCS_CONSTRAINT_PARAM(QUALID,TEMPKW,__VA_ARGS__) \
> \
CPP2_LAMBDA_NO_DISCARD (Obj&& obj, Params&& ...params) CPP2_FORCE_INLINE_LAMBDA_CLANG \
noexcept(CPP2_UFCS_IS_NOTHROW_ARG(QUALID,TEMPKW,__VA_ARGS__)) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) \
requires CPP2_UFCS_CONSTRAINT_ARG(QUALID,TEMPKW,__VA_ARGS__) { \
if constexpr (requires{ CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); }) { \
return CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); \
} else { \
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
return CPP2_UFCS_REMPARENS QUALID __VA_ARGS__(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
} \
}(PARAM1, __VA_ARGS__)
}

#define CPP2_UFCS_TEMPLATE_0_NONLOCAL(FUNCNAME,TEMPARGS,PARAM1) \
[] CPP2_LAMBDA_NO_DISCARD (auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
if constexpr (requires{ CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); }) { \
return CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); \
} else { \
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(obj)); \
} \
}(PARAM1)
#define CPP2_UFCS(...) CPP2_UFCS_(&,(),,__VA_ARGS__)
#define CPP2_UFCS_TEMPLATE(...) CPP2_UFCS_(&,(),template,__VA_ARGS__)
#define CPP2_UFCS_QUALIFIED_TEMPLATE(QUALID,...) CPP2_UFCS_(&,QUALID,template,__VA_ARGS__)
#define CPP2_UFCS_NONLOCAL(...) CPP2_UFCS_(,(),,__VA_ARGS__)
#define CPP2_UFCS_TEMPLATE_NONLOCAL(...) CPP2_UFCS_(,(),template,__VA_ARGS__)
#define CPP2_UFCS_QUALIFIED_TEMPLATE_NONLOCAL(QUALID,...) CPP2_UFCS_(,QUALID,template,__VA_ARGS__)


//-----------------------------------------------------------------------
Expand Down Expand Up @@ -914,7 +915,7 @@ inline auto to_string(std::string const& s) -> std::string const&

template<typename T>
inline auto to_string(T const& sv) -> std::string
requires (std::is_convertible_v<T, std::string_view>
requires (std::is_convertible_v<T, std::string_view>
&& !std::is_convertible_v<T, const char*>)
{
return std::string{sv};
Expand Down Expand Up @@ -1054,17 +1055,17 @@ auto is( X const& ) -> bool {

template< typename C, typename X >
requires (
( std::is_base_of_v<X, C> ||
( std::is_polymorphic_v<C> && std::is_polymorphic_v<X>)
( std::is_base_of_v<X, C> ||
( std::is_polymorphic_v<C> && std::is_polymorphic_v<X>)
) && !std::is_same_v<C,X>)
auto is( X const& x ) -> bool {
return Dynamic_cast<C const*>(&x) != nullptr;
}

template< typename C, typename X >
requires (
( std::is_base_of_v<X, C> ||
( std::is_polymorphic_v<C> && std::is_polymorphic_v<X>)
( std::is_base_of_v<X, C> ||
( std::is_polymorphic_v<C> && std::is_polymorphic_v<X>)
) && !std::is_same_v<C,X>)
auto is( X const* x ) -> bool {
return Dynamic_cast<C const*>(x) != nullptr;
Expand Down Expand Up @@ -1726,7 +1727,7 @@ constexpr auto unsafe_narrow( X&& x ) noexcept -> decltype(auto)
// Returns a function object that takes a 'value' of the same type as
// 'flags', and evaluates to true if and only if 'value' has set all of
// the bits set in 'flags'
//
//
//-----------------------------------------------------------------------
//
template <typename T>
Expand Down
49 changes: 49 additions & 0 deletions regression-tests/mixed-bugfix-for-ufcs-non-local.cpp2
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
namespace ns {

template<bool> struct t { };
constexpr bool f(const t<true>&) { return true; }
constexpr t<true> o{};

} // namespace ns

ns: namespace = {

// Variables.

v0: <_: t<o.f()>> bool == false; // Fails on GCC ([GCC109781][]) and Clang 12 (a lambda expression cannot appear in this context)

v1: t<o.f()> == t<true>(); // Fails on Clang 12 (lambda in unevaluated context).

v2: bool == o.f();

// Functions.

g: <_: t<o.f()>> () = { } // Fails on GCC ([GCC109781][]) and Clang 12 (a lambda expression cannot appear in this context)

g: (_: t<o.f()>) = { } // Fails on Clang 12 (lambda in unevaluated context).

g: () pre(o.f()) = { }

h: () -> t<o.f()> = o; // Fails on Clang 12 (lambda in unevaluated context).

// Aliases.

a: <_: t<o.f()>> type == bool; // Fails on GCC ([GCC109781][]) and Clang 12 (a lambda expression cannot appear in this context)

b: <_: t<o.f()>> _ == false; // Fails on GCC ([GCC109781][]).

c: type == t<o.f()>; // Fails on Clang 12 (lambda in unevaluated context) and Clang 12 (a lambda expression cannot appear in this context)

d: _ == t<o.f()>(); // Fails on Clang 12 (lambda in unevaluated context).

u: @struct type = {
b: bool == o.f();
c: bool == :(x: std::type_identity_t<decltype(o.f())>) x;(true); // Fails on Clang 12 (lambda in unevaluated context).
g: (s, sz) pre(s.sz() != 0) = { }
}

} // namespace ns

main: () = { }

// [GCC109781]: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109781
71 changes: 71 additions & 0 deletions regression-tests/pure2-bugfix-for-ufcs-arguments.cpp2
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
print_res: (x: i32) -> i32 = {
std::cout << x;
if x == 9 { std::cout << '\n'; }
return x;
}

t: @struct type = {
f: (inout this) -> i32 = print_res(0);
f: (inout this, _) -> i32 = print_res(1);
f: <_> (inout this) -> i32 = print_res(2);
f: <_> (inout this, _) -> i32 = print_res(3);
f: <_, U> (inout this, _, _) -> i32 = print_res(4);
}

f: (_: t) -> i32 = print_res(5);
f: (_: t, _) -> i32 = print_res(6);
f: <_> (_: t) -> i32 = print_res(7);
f: <_> (_: t, _) -> i32 = print_res(8);
f: <_, U> (_: t, _, _) -> i32 = print_res(9);

m: t = ();
n: const t = ();
a: <_, U> _ == n;

_: i32 = m.f();
_: i32 = m.f(0);
_: i32 = m.f<t>();
_: i32 = m.f<t>(0);
_: i32 = m.f<t, t>(0, 0);
_: i32 = n.f();
_: i32 = n.f(0);
_: i32 = n.f<t>();
_: i32 = n.f<t>(0);
_: i32 = n.f<t, t>(0, 0);
_: i32 = a<t, t>.f<t, t>(0, 0);

main: () = {
_ = m.f();
_ = m.f(0);
_ = m.f<t>();
_ = m.f<t>(0);
_ = m.f<t, t>(0, 0);
_ = n.f();
_ = n.f(0);
_ = n.f<t>();
_ = n.f<t>(0);
_ = n.f<t, t>(0, 0);
_ = a<t, t>.f<t, t>(0, 0);

_ = :(a, f) = { _ = a.f(a).f(); };
// _ = 0.std::min<int>(0);
_ = 0.ns::t<0, 0>::f<0>();
}

// _: i32 = 0.std::min<int>(0);
_: i32 = 0.ns::t<0, 0>::f<0>();

ns: namespace = {
t: @struct <T: int, U: int> type = {
f: <V: int> (_: int) -> i32 = 0;
}
} // namespace ns

A: @struct type = {
f: (this) = { }
}

B: @struct type = {
m: A;
f: (this) = m.f();
}
Loading

0 comments on commit a7ab29e

Please sign in to comment.