Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(util): handle known UFCS corner cases #506

Merged
merged 29 commits into from
Dec 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6e01f9a
fix(util): handle known UFCS corner cases
JohelEGP Aug 7, 2023
e423bab
refactor: regenerate `reflect.h`
JohelEGP Sep 14, 2023
50fd850
refactor(util): use new UFCS macros
JohelEGP Sep 14, 2023
c0e5cc3
test: add unit tests for fixed UFCS corner cases
JohelEGP Sep 14, 2023
0c4329c
test: regenerate regression tests
JohelEGP Sep 14, 2023
e60f076
refactor(util): remove old UFCS branch
JohelEGP Sep 14, 2023
f998496
refactor(util): comment the need for `CPP2_UFCS_IS_NOTHROW`
JohelEGP Nov 21, 2023
1fe5037
refactor(to_cpp1): split name lookup from `ufcs_possible`
JohelEGP Nov 21, 2023
38bc541
refactor(to_cpp1): clarify name of `ufcs_possible`
JohelEGP Nov 21, 2023
ecae575
refactor(to_cpp1): rename `stack` variables to `guard`
JohelEGP Nov 21, 2023
cb588c1
refactor(to_cpp1): add comment on added `stack` functions
JohelEGP Nov 21, 2023
1561fd3
refactor(to_cpp1): invert meaning of result to match rename
JohelEGP Nov 21, 2023
fc7e1fd
fix(to_cpp1): a using declaration doesn't name a variable
JohelEGP Nov 21, 2023
783d68d
fix(to_cpp1): do not capture in UFCS of type scope alias
JohelEGP Nov 23, 2023
b90cdfb
fix(to_cpp1): do not capture in UFCS of type scope alias
JohelEGP Nov 24, 2023
9643874
refactor(to_cpp1): regroup conditions more naturally
JohelEGP Nov 24, 2023
8ea0166
test: regenerate test-results
JohelEGP Nov 24, 2023
99a755a
fix(to_cpp1): do capture in UFCS of contract
JohelEGP Nov 24, 2023
295a886
test: regenerate test-results
JohelEGP Nov 24, 2023
0c8fa21
fix(to_cpp1): emit qualified UFCS template call correctly
JohelEGP Nov 24, 2023
55710ba
fix(to_cpp1): emit qualified UFCS template call correctly
JohelEGP Nov 24, 2023
811b5e8
fix(util): workaround MSVC bug for UFCS of 'F' in member 'F'
JohelEGP Nov 25, 2023
33cb7d5
test: disable the simpler test case due to the GCC bug
JohelEGP Nov 25, 2023
f7517ef
test: disable test cases now failing on MSVC
JohelEGP Nov 25, 2023
7f49d99
Results of testing the PR incl. with MSVC 2022
hsutter Nov 25, 2023
73d4b62
refactor(to_cpp1): apply review comment and rename name lookup
JohelEGP Nov 26, 2023
5302db0
test: regenerate test-results
JohelEGP Nov 26, 2023
d167f2b
test: add case for the happy path
JohelEGP Nov 30, 2023
8ad1335
test: regenerate UFCS tests
JohelEGP Dec 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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