hsutter / cppfront

A personal experimental C++ Syntax 2 -> Syntax 1 compiler
Other
5.57k stars 249 forks source link

[BUG] UFCS fails during constant evaluation only #788

Open JohelEGP opened 1 year ago

JohelEGP commented 1 year ago

Title: UFCS fails during constant evaluation only.

Description:

Given a parameter named after the function being called, UFCS will fail during constant evaluation only in the case UFCS would call a member function.

Minimal reproducer (https://cpp2.godbolt.org/z/jKWG4boaq, https://compiler-explorer.com/z/zeqW6MEPK):

t: @struct type = {
  f: (this) -> int == 0;
}

g: (f) = {
  [[assert: t().f() == 0]] // OK.
  static_assert(t().f() == 0); // Error: Argument `f` isn't "`constexpr`".
}

main: () = {
  g(0);
  // g(std::identity()); // Might be OK since P2280R4 (unimplemented).
}

Commands: ```bash cppfront main.cpp2 clang++18 -std=c++23 -stdlib=libc++ -lc++abi -pedantic-errors -Wall -Wextra -Wconversion -Werror=unused-result -I . main.cpp ```

Expected result: A well-formed program that calls the member function.

Actual result and error:

Cpp2 lowered to Cpp1: ```C++ //=== Cpp2 type declarations ==================================================== #include "cpp2util.h" class t; //=== Cpp2 type definitions and function declarations =========================== class t { public: [[nodiscard]] constexpr auto f() const& -> int; }; auto g(auto const& f) -> void; auto main() -> int; //=== Cpp2 function definitions ================================================= [[nodiscard]] constexpr auto t::f() const& -> int { return 0; } auto g(auto const& f) -> void{ cpp2::Default.expects(CPP2_UFCS_0(f, t()) == 0, "");// OK. static_assert(CPP2_UFCS_0(f, t()) == 0);// Error: Argument `f` isn't "`constexpr`". } auto main() -> int{ g(0); // g(std::identity()); // Might be OK since P2280R4 (unimplemented). } ```

Output: ```output main.cpp2:7:17: error: static assertion expression is not an integral constant expression 7 | static_assert(CPP2_UFCS_0(f, t()) == 0);// Error: Argument `f` isn't "`constexpr`". | ^~~~~~~~~~~~~~~~~~~~~~~~ raw.githubusercontent.com/hsutter/cppfront/main/include/cpp2util.h:790:38: note: expanded from macro 'CPP2_UFCS_0' 790 | #define CPP2_UFCS_0(FUNCNAME,PARAM1) \ | ^ main.cpp2:11:3: note: in instantiation of function template specialization 'g' requested here 11 | g(0); | ^ main.cpp2:7:17: note: function parameter 'f' with unknown value cannot be used in a constant expression 7 | static_assert(CPP2_UFCS_0(f, t()) == 0);// Error: Argument `f` isn't "`constexpr`". | ^ raw.githubusercontent.com/hsutter/cppfront/main/include/cpp2util.h:791:2: note: expanded from macro 'CPP2_UFCS_0' 791 | [&] CPP2_LAMBDA_NO_DISCARD (auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \ | ^ main.cpp2:5:20: note: declared here 5 | auto g(auto const& f) -> void{ | ^ 1 error generated. ```
JohelEGP commented 1 year ago

My reading of https://eel.is/c++draft/expr.const#8 tells me that g(0); will continue failing whereas g(std::identity()); would be well-formed.

506's resolution for #550 adds quite some complexity to Cppfront.

The same can be done here to resolve this #788. The alternative is to just embrace #550 and #788 as limitations of Cppfront, and require users to not name such variables like the function in a UFCS call.

JohelEGP commented 1 year ago

I have potential solutions in mind for this one. But they also bring complexity to the code of Cppfront.

Maybe trial constant evaluation can be leveraged for a seamless user experience. An alternative would be to disable UFCS when in a constant expression (so Cppfront has to know about those) and f isn't part of the constant expression (determined by lexical name lookup).

JohelEGP commented 1 year ago

// g(std::identity()); // Might be OK since P2280R4 (unimplemented). Yes, it works: https://cpp2.godbolt.org/z/1vrf534qG.

JohelEGP commented 2 months ago

(Now?) g(0) also works with GCC (https://cpp2.godbolt.org/z/8v13hM44h). I'd expect Clang to work, by choosing the member, since that works, before even trying the object. Maybe there's a Clang bug lurking, or some constant expression quirk where Clang is strict and GCC is loose. It's worth debugging the UFCS macro to see exactly what triggers Clang for further investigation.