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

[BUG] Variable with placeholder type can't share name with member call in initializer #550

Closed
JohelEGP opened this issue Jul 18, 2023 · 7 comments · Fixed by #506
Closed
Labels
bug Something isn't working

Comments

@JohelEGP
Copy link
Contributor

Title: Variable with placeholder type can't share name with member call in initializer.

Description:

Something in the UFCS machinery breaks this use case.

My actual use case looks more like this (https://cpp2.godbolt.org/z/sGK36zbe1):

  f := :() = {
    v&$*.f();
  };

It's a function expression that captures.
Using std::function is undesirable,
and currently impossible given #343.

Minimal reproducer (https://cpp2.godbolt.org/z/r7Ef84MKs):

t: type = {
  f: (this) -> i32 = 0;
}
main: () = {
  f := t().f();
}
Commands:
cppfront main.cpp2
clang++17 -std=c++23 -stdlib=libc++ -lc++abi -pedantic-errors -Wall -Wextra -Wconversion -I . main.cpp

Expected result: A working program.

Actual result and error:

Cpp2 lowered to Cpp1:
//=== Cpp2 type declarations ====================================================


#include "cpp2util.h"

class t;
  

//=== Cpp2 type definitions and function declarations ===========================

class t {
  public: [[nodiscard]] auto f() const -> cpp2::i32;

  public: t() = default;
  public: t(t const&) = delete; /* No 'that' constructor, suppress copy */
  public: auto operator=(t const&) -> void = delete;
};
auto main() -> int;
  

//=== Cpp2 function definitions =================================================


  [[nodiscard]] auto t::f() const -> cpp2::i32 { return 0;  }

auto main() -> int{
  auto f {CPP2_UFCS_0(f, t())}; 
}
Output:
main.cpp2:5:23: error: variable 'f' declared with deduced type 'auto' cannot appear in its own initializer
    5 |   auto f {CPP2_UFCS_0(f, t())}; 
      |                       ^
1 error generated.
@JohelEGP JohelEGP added the bug Something isn't working label Jul 18, 2023
@JohelEGP
Copy link
Contributor Author

JohelEGP commented Aug 10, 2023

All compilers reject https://compiler-explorer.com/z/4f3d818hs:

class t { int f(); };
int main() {
  auto f = [](auto obj) {
    if constexpr (requires { obj.f(); }) return obj.f();
    else return f(obj);
  }(t());
}

(Opened https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110978 for GCC's ICE.)
Maybe overload_linearly would work.
No, it doesn't: https://compiler-explorer.com/z/9vnKvveK5.

#include <boost/hana/functional/overload_linearly.hpp>
class t { int f(); };
int main() {
  auto f = boost::hana::overload_linearly(
    [](auto obj) requires requires { obj.f(); } { return obj.f(); },
    [](auto obj) requires requires { f(obj); } { return f(obj); }
  )(t());
}

@JohelEGP
Copy link
Contributor Author

JohelEGP commented Aug 29, 2023

Rewriting Cpp1 auto f {t().f()};
to Cpp2 f := t().f();
can result in ill-formed code.
You'd either need to change the member's name,
or come up with a new name for the local variable.

Maybe there's something from the exploration of #307 I can salvage to solve this issue.
Nope (#307 (comment)):

Even then, with the new branch in the macro, a variable named after a member function breaks UFCS

@JohelEGP
Copy link
Contributor Author

I think the solution is to disable UFCS
if name lookup finds that the function identifier being called
is a variable and we are in its initializer.

@JohelEGP
Copy link
Contributor Author

From #788 (comment):

#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
Copy link
Contributor Author

JohelEGP commented Nov 22, 2023

Here is a data point that suggests Cpp1 code equivalent to Cpp2 𝘮𝘦𝘮 := 𝘷𝘢𝘳.𝘮𝘦𝘮(); is prolific.
There are 12.1 K results precisely for auto size = 𝘷𝘢𝘳.size(); (https://github.com/search?q=lang%3AC%2B%2B+content%3A%2Fauto+size+%3D+%28%3F%3A%5Ba-zA-Z_%5D%2B%29%5C.size%5C%28%5C%29%3B%2F+NOT+is%3Afork&type=code).

@JohelEGP
Copy link
Contributor Author

There is another case that #506 doesn't yet cover (https://cpp2.godbolt.org/z/hzYxv5rn6):

type_declaration: @struct type = {
  require: (this) = { }
}
require_fn: type == std::function<void(bool, std::string_view)>;
array2: (inout t: type_declaration) = {
  require: require_fn = :(args...) t&$*.require(args...);
  _ = require;
}
main: () = { }
main.cpp2:6:91: error: variable 'require' cannot be implicitly captured in a lambda with no capture-default specified
    6 |   require_fn require {[_0 = (&t)](auto const& ...args) mutable -> auto { return CPP2_UFCS(require, (*cpp2::assert_not_null(_0)), args...);  }};
      |                                                                                           ^
main.cpp2:6:14: note: 'require' declared here
    6 |   require_fn require {[_0 = (&t)](auto const& ...args) mutable -> auto { return CPP2_UFCS(require, (*cpp2::assert_not_null(_0)), args...);  }};
      |              ^

require has type require_fn, so it isn't that its type is deduced.
But it still tries to use require without capturing it.
Name lookup finds an object, so it doesn't try a free function, and the object must be captured.
If we're in a context that would need to capture the name, we should not use the UFCS macro.
In the context of a member function, this would happen to work due to #281.


There is another case that #506 doesn't yet cover (https://cpp2.godbolt.org/z/xj1P9cd66):

type_declaration: @struct type = {
  require: (this) = { }
}
require_fn: type == std::function<void(bool, std::string_view)>;
array2: (inout t: type_declaration) = {
  require: std::optional = t.require();
  _ = require;
}
main: () = { }
main.cpp2:6:38: error: variable 'require' declared with deduced type 'std::optional' cannot appear in its own initializer
    6 |   std::optional require {CPP2_UFCS_0(require, t)};
      |                                      ^
main.cpp2:6:17: error: no viable constructor or deduction guide for deduction of template arguments of 'optional'
    6 |   std::optional require {CPP2_UFCS_0(require, t)};
      |                 ^

This is the same as #550 (comment).
Except that we can't easily identify that CTAD is required.
Maybe a nested requirement such as requires { typename std::optional; } might work to detect it.

@JohelEGP
Copy link
Contributor Author

JohelEGP commented Nov 30, 2023

require: require_fn = :(args...) t&$*.require(args...);

For this kind of code, in GitHub,
there are precisely 7 uses with the identifier size
that would break if they were rewritten to Cpp2 and compiled with cppfront:
https://github.com/search?q=lang%3AC%2B%2B+content%3A%2F+size+%3D+%5C%5B.*%5C.size%5C%28%5C%29%3B%2F+NOT+is%3Afork&type=code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant