Skip to content

Commit a7ab29e

Browse files
JohelEGPhsutter
andauthored
fix(util): handle known UFCS corner cases (#506)
* 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 <herb.sutter@gmail.com>
1 parent b9e73e0 commit a7ab29e

File tree

102 files changed

+1698
-526
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+1698
-526
lines changed

include/cpp2util.h

+86-85
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@
116116
// in our -pure-cpp2 "import std;" simulation mode... if you need this,
117117
// use mixed mode (not -pure-cpp2) and #include all the headers you need
118118
// including this one
119-
//
119+
//
120120
// #include <execution>
121121
#ifdef __cpp_lib_expected
122122
#include <expected>
@@ -526,7 +526,7 @@ template<typename T>
526526
auto Typeid() -> decltype(auto) {
527527
#ifdef CPP2_NO_RTTI
528528
Type.expects(
529-
!"'any' dynamic casting is disabled with -fno-rtti", // more likely to appear on console
529+
!"'any' dynamic casting is disabled with -fno-rtti", // more likely to appear on console
530530
"'any' dynamic casting is disabled with -fno-rtti" // make message available to hooked handlers
531531
);
532532
#else
@@ -575,7 +575,7 @@ struct {
575575
template<typename T>
576576
[[nodiscard]] auto cpp2_new(auto&& ...args) const -> std::shared_ptr<T> {
577577
// Prefer { } to ( ) as noted for unique.new
578-
//
578+
//
579579
// Note this does mean we don't get the make_shared optimization a lot
580580
// of the time -- we can restore that as soon as make_shared improves to
581581
// allow list initialization. But the make_shared optimization isn't a
@@ -745,13 +745,22 @@ class out {
745745
//
746746
//-----------------------------------------------------------------------
747747
//
748+
// Workaround <https://github.com/llvm/llvm-project/issues/70556>.
749+
#define CPP2_FORCE_INLINE_LAMBDA_CLANG /* empty */
750+
748751
#if defined(_MSC_VER) && !defined(__clang_major__)
749-
#define CPP2_FORCE_INLINE __forceinline
750-
#define CPP2_FORCE_INLINE_LAMBDA [[msvc::forceinline]]
752+
#define CPP2_FORCE_INLINE __forceinline
753+
#define CPP2_FORCE_INLINE_LAMBDA [[msvc::forceinline]]
751754
#define CPP2_LAMBDA_NO_DISCARD
752755
#else
753-
#define CPP2_FORCE_INLINE __attribute__((always_inline))
754-
#define CPP2_FORCE_INLINE_LAMBDA __attribute__((always_inline))
756+
#define CPP2_FORCE_INLINE __attribute__((always_inline))
757+
#if defined(__clang__)
758+
#define CPP2_FORCE_INLINE_LAMBDA /* empty */
759+
#undef CPP2_FORCE_INLINE_LAMBDA_CLANG
760+
#define CPP2_FORCE_INLINE_LAMBDA_CLANG __attribute__((always_inline))
761+
#else
762+
#define CPP2_FORCE_INLINE_LAMBDA __attribute__((always_inline))
763+
#endif
755764

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

779-
780-
// Note: [&] is because a nested UFCS might be viewed as trying to capture 'this'
781-
782-
#define CPP2_UFCS(FUNCNAME,PARAM1,...) \
783-
[&] CPP2_LAMBDA_NO_DISCARD (auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
784-
if constexpr (requires{ CPP2_FORWARD(obj).FUNCNAME(CPP2_FORWARD(params)...); }) { \
785-
return CPP2_FORWARD(obj).FUNCNAME(CPP2_FORWARD(params)...); \
786-
} else { \
787-
return FUNCNAME(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
788-
} \
789-
}(PARAM1, __VA_ARGS__)
790-
791-
#define CPP2_UFCS_0(FUNCNAME,PARAM1) \
792-
[&] CPP2_LAMBDA_NO_DISCARD (auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
793-
if constexpr (requires{ CPP2_FORWARD(obj).FUNCNAME(); }) { \
794-
return CPP2_FORWARD(obj).FUNCNAME(); \
795-
} else { \
796-
return FUNCNAME(CPP2_FORWARD(obj)); \
797-
} \
798-
}(PARAM1)
799-
800788
#define CPP2_UFCS_REMPARENS(...) __VA_ARGS__
801789

802-
#define CPP2_UFCS_TEMPLATE(FUNCNAME,TEMPARGS,PARAM1,...) \
803-
[&] CPP2_LAMBDA_NO_DISCARD (auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
804-
if constexpr (requires{ CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(params)...); }) { \
805-
return CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(params)...); \
806-
} else { \
807-
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
808-
} \
809-
}(PARAM1, __VA_ARGS__)
810-
811-
#define CPP2_UFCS_TEMPLATE_0(FUNCNAME,TEMPARGS,PARAM1) \
812-
[&] CPP2_LAMBDA_NO_DISCARD (auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
813-
if constexpr (requires{ CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); }) { \
814-
return CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); \
815-
} else { \
816-
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(obj)); \
817-
} \
818-
}(PARAM1)
819-
820-
821-
// But for non-local lambdas [&] is not allowed
822-
823-
#define CPP2_UFCS_NONLOCAL(FUNCNAME,PARAM1,...) \
824-
[] CPP2_LAMBDA_NO_DISCARD (auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
825-
if constexpr (requires{ CPP2_FORWARD(obj).FUNCNAME(CPP2_FORWARD(params)...); }) { \
826-
return CPP2_FORWARD(obj).FUNCNAME(CPP2_FORWARD(params)...); \
827-
} else { \
828-
return FUNCNAME(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
829-
} \
830-
}(PARAM1, __VA_ARGS__)
790+
// Ideally, the expression `CPP2_UFCS_IS_NOTHROW` expands to
791+
// is in the _noexcept-specifier_ of the UFCS lambda, but without 'std::declval'.
792+
// To workaround [GCC bug 101043](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101043),
793+
// we instead make it a template parameter of the UFCS lambda.
794+
// But using a template parameter, Clang also ICEs on an application.
795+
// So we use these `NOTHROW` macros to fall back to the ideal for when not using GCC.
796+
#define CPP2_UFCS_IS_NOTHROW(QUALID,TEMPKW,...) \
797+
requires { requires requires { std::declval<Obj>().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval<Params>()...); }; \
798+
requires noexcept(std::declval<Obj>().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval<Params>()...)); } \
799+
|| requires { requires !requires { std::declval<Obj>().CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(std::declval<Params>()...); }; \
800+
requires noexcept(CPP2_UFCS_REMPARENS QUALID __VA_ARGS__(std::declval<Obj>(), std::declval<Params>()...)); }
801+
#define CPP2_UFCS_IS_NOTHROW_PARAM(...) /*empty*/
802+
#define CPP2_UFCS_IS_NOTHROW_ARG(QUALID,TEMPKW,...) CPP2_UFCS_IS_NOTHROW(QUALID,TEMPKW,__VA_ARGS__)
803+
#if defined(__GNUC__) && !defined(__clang__)
804+
#undef CPP2_UFCS_IS_NOTHROW_PARAM
805+
#undef CPP2_UFCS_IS_NOTHROW_ARG
806+
#define CPP2_UFCS_IS_NOTHROW_PARAM(QUALID,TEMPKW,...) , bool IsNothrow = CPP2_UFCS_IS_NOTHROW(QUALID,TEMPKW,__VA_ARGS__)
807+
#define CPP2_UFCS_IS_NOTHROW_ARG(...) IsNothrow
808+
#if __GNUC__ < 11
809+
#undef CPP2_UFCS_IS_NOTHROW_PARAM
810+
#undef CPP2_UFCS_IS_NOTHROW_ARG
811+
#define CPP2_UFCS_IS_NOTHROW_PARAM(...) /*empty*/
812+
#define CPP2_UFCS_IS_NOTHROW_ARG(...) false // GCC 10 UFCS is always potentially-throwing.
813+
#endif
814+
#endif
831815

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

841-
#define CPP2_UFCS_TEMPLATE_NONLOCAL(FUNCNAME,TEMPARGS,PARAM1,...) \
842-
[] CPP2_LAMBDA_NO_DISCARD (auto&& obj, auto&& ...params) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
843-
if constexpr (requires{ CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(params)...); }) { \
844-
return CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(params)...); \
837+
#define CPP2_UFCS_(LAMBDADEFCAPT,QUALID,TEMPKW,...) \
838+
[LAMBDADEFCAPT]< \
839+
typename Obj, typename... Params \
840+
CPP2_UFCS_IS_NOTHROW_PARAM(QUALID,TEMPKW,__VA_ARGS__) \
841+
CPP2_UFCS_CONSTRAINT_PARAM(QUALID,TEMPKW,__VA_ARGS__) \
842+
> \
843+
CPP2_LAMBDA_NO_DISCARD (Obj&& obj, Params&& ...params) CPP2_FORCE_INLINE_LAMBDA_CLANG \
844+
noexcept(CPP2_UFCS_IS_NOTHROW_ARG(QUALID,TEMPKW,__VA_ARGS__)) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) \
845+
requires CPP2_UFCS_CONSTRAINT_ARG(QUALID,TEMPKW,__VA_ARGS__) { \
846+
if constexpr (requires{ CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); }) { \
847+
return CPP2_FORWARD(obj).CPP2_UFCS_REMPARENS QUALID TEMPKW __VA_ARGS__(CPP2_FORWARD(params)...); \
845848
} else { \
846-
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
849+
return CPP2_UFCS_REMPARENS QUALID __VA_ARGS__(CPP2_FORWARD(obj), CPP2_FORWARD(params)...); \
847850
} \
848-
}(PARAM1, __VA_ARGS__)
851+
}
849852

850-
#define CPP2_UFCS_TEMPLATE_0_NONLOCAL(FUNCNAME,TEMPARGS,PARAM1) \
851-
[] CPP2_LAMBDA_NO_DISCARD (auto&& obj) CPP2_FORCE_INLINE_LAMBDA -> decltype(auto) { \
852-
if constexpr (requires{ CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); }) { \
853-
return CPP2_FORWARD(obj).template FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (); \
854-
} else { \
855-
return FUNCNAME CPP2_UFCS_REMPARENS TEMPARGS (CPP2_FORWARD(obj)); \
856-
} \
857-
}(PARAM1)
853+
#define CPP2_UFCS(...) CPP2_UFCS_(&,(),,__VA_ARGS__)
854+
#define CPP2_UFCS_TEMPLATE(...) CPP2_UFCS_(&,(),template,__VA_ARGS__)
855+
#define CPP2_UFCS_QUALIFIED_TEMPLATE(QUALID,...) CPP2_UFCS_(&,QUALID,template,__VA_ARGS__)
856+
#define CPP2_UFCS_NONLOCAL(...) CPP2_UFCS_(,(),,__VA_ARGS__)
857+
#define CPP2_UFCS_TEMPLATE_NONLOCAL(...) CPP2_UFCS_(,(),template,__VA_ARGS__)
858+
#define CPP2_UFCS_QUALIFIED_TEMPLATE_NONLOCAL(QUALID,...) CPP2_UFCS_(,QUALID,template,__VA_ARGS__)
858859

859860

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

915916
template<typename T>
916917
inline auto to_string(T const& sv) -> std::string
917-
requires (std::is_convertible_v<T, std::string_view>
918+
requires (std::is_convertible_v<T, std::string_view>
918919
&& !std::is_convertible_v<T, const char*>)
919920
{
920921
return std::string{sv};
@@ -1054,17 +1055,17 @@ auto is( X const& ) -> bool {
10541055

10551056
template< typename C, typename X >
10561057
requires (
1057-
( std::is_base_of_v<X, C> ||
1058-
( std::is_polymorphic_v<C> && std::is_polymorphic_v<X>)
1058+
( std::is_base_of_v<X, C> ||
1059+
( std::is_polymorphic_v<C> && std::is_polymorphic_v<X>)
10591060
) && !std::is_same_v<C,X>)
10601061
auto is( X const& x ) -> bool {
10611062
return Dynamic_cast<C const*>(&x) != nullptr;
10621063
}
10631064

10641065
template< typename C, typename X >
10651066
requires (
1066-
( std::is_base_of_v<X, C> ||
1067-
( std::is_polymorphic_v<C> && std::is_polymorphic_v<X>)
1067+
( std::is_base_of_v<X, C> ||
1068+
( std::is_polymorphic_v<C> && std::is_polymorphic_v<X>)
10681069
) && !std::is_same_v<C,X>)
10691070
auto is( X const* x ) -> bool {
10701071
return Dynamic_cast<C const*>(x) != nullptr;
@@ -1726,7 +1727,7 @@ constexpr auto unsafe_narrow( X&& x ) noexcept -> decltype(auto)
17261727
// Returns a function object that takes a 'value' of the same type as
17271728
// 'flags', and evaluates to true if and only if 'value' has set all of
17281729
// the bits set in 'flags'
1729-
//
1730+
//
17301731
//-----------------------------------------------------------------------
17311732
//
17321733
template <typename T>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
namespace ns {
2+
3+
template<bool> struct t { };
4+
constexpr bool f(const t<true>&) { return true; }
5+
constexpr t<true> o{};
6+
7+
} // namespace ns
8+
9+
ns: namespace = {
10+
11+
// Variables.
12+
13+
v0: <_: t<o.f()>> bool == false; // Fails on GCC ([GCC109781][]) and Clang 12 (a lambda expression cannot appear in this context)
14+
15+
v1: t<o.f()> == t<true>(); // Fails on Clang 12 (lambda in unevaluated context).
16+
17+
v2: bool == o.f();
18+
19+
// Functions.
20+
21+
g: <_: t<o.f()>> () = { } // Fails on GCC ([GCC109781][]) and Clang 12 (a lambda expression cannot appear in this context)
22+
23+
g: (_: t<o.f()>) = { } // Fails on Clang 12 (lambda in unevaluated context).
24+
25+
g: () pre(o.f()) = { }
26+
27+
h: () -> t<o.f()> = o; // Fails on Clang 12 (lambda in unevaluated context).
28+
29+
// Aliases.
30+
31+
a: <_: t<o.f()>> type == bool; // Fails on GCC ([GCC109781][]) and Clang 12 (a lambda expression cannot appear in this context)
32+
33+
b: <_: t<o.f()>> _ == false; // Fails on GCC ([GCC109781][]).
34+
35+
c: type == t<o.f()>; // Fails on Clang 12 (lambda in unevaluated context) and Clang 12 (a lambda expression cannot appear in this context)
36+
37+
d: _ == t<o.f()>(); // Fails on Clang 12 (lambda in unevaluated context).
38+
39+
u: @struct type = {
40+
b: bool == o.f();
41+
c: bool == :(x: std::type_identity_t<decltype(o.f())>) x;(true); // Fails on Clang 12 (lambda in unevaluated context).
42+
g: (s, sz) pre(s.sz() != 0) = { }
43+
}
44+
45+
} // namespace ns
46+
47+
main: () = { }
48+
49+
// [GCC109781]: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109781
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
print_res: (x: i32) -> i32 = {
2+
std::cout << x;
3+
if x == 9 { std::cout << '\n'; }
4+
return x;
5+
}
6+
7+
t: @struct type = {
8+
f: (inout this) -> i32 = print_res(0);
9+
f: (inout this, _) -> i32 = print_res(1);
10+
f: <_> (inout this) -> i32 = print_res(2);
11+
f: <_> (inout this, _) -> i32 = print_res(3);
12+
f: <_, U> (inout this, _, _) -> i32 = print_res(4);
13+
}
14+
15+
f: (_: t) -> i32 = print_res(5);
16+
f: (_: t, _) -> i32 = print_res(6);
17+
f: <_> (_: t) -> i32 = print_res(7);
18+
f: <_> (_: t, _) -> i32 = print_res(8);
19+
f: <_, U> (_: t, _, _) -> i32 = print_res(9);
20+
21+
m: t = ();
22+
n: const t = ();
23+
a: <_, U> _ == n;
24+
25+
_: i32 = m.f();
26+
_: i32 = m.f(0);
27+
_: i32 = m.f<t>();
28+
_: i32 = m.f<t>(0);
29+
_: i32 = m.f<t, t>(0, 0);
30+
_: i32 = n.f();
31+
_: i32 = n.f(0);
32+
_: i32 = n.f<t>();
33+
_: i32 = n.f<t>(0);
34+
_: i32 = n.f<t, t>(0, 0);
35+
_: i32 = a<t, t>.f<t, t>(0, 0);
36+
37+
main: () = {
38+
_ = m.f();
39+
_ = m.f(0);
40+
_ = m.f<t>();
41+
_ = m.f<t>(0);
42+
_ = m.f<t, t>(0, 0);
43+
_ = n.f();
44+
_ = n.f(0);
45+
_ = n.f<t>();
46+
_ = n.f<t>(0);
47+
_ = n.f<t, t>(0, 0);
48+
_ = a<t, t>.f<t, t>(0, 0);
49+
50+
_ = :(a, f) = { _ = a.f(a).f(); };
51+
// _ = 0.std::min<int>(0);
52+
_ = 0.ns::t<0, 0>::f<0>();
53+
}
54+
55+
// _: i32 = 0.std::min<int>(0);
56+
_: i32 = 0.ns::t<0, 0>::f<0>();
57+
58+
ns: namespace = {
59+
t: @struct <T: int, U: int> type = {
60+
f: <V: int> (_: int) -> i32 = 0;
61+
}
62+
} // namespace ns
63+
64+
A: @struct type = {
65+
f: (this) = { }
66+
}
67+
68+
B: @struct type = {
69+
m: A;
70+
f: (this) = m.f();
71+
}

0 commit comments

Comments
 (0)