diff --git a/clang/include/clang/Basic/DiagnosticMetafnKinds.td b/clang/include/clang/Basic/DiagnosticMetafnKinds.td index 2e1c4a77901829..fed3c6d67bce4f 100644 --- a/clang/include/clang/Basic/DiagnosticMetafnKinds.td +++ b/clang/include/clang/Basic/DiagnosticMetafnKinds.td @@ -59,6 +59,15 @@ def metafn_returns_non_structural_type : Note< "represented as a reflection">; def metafn_invocation_not_constant_expr : Note< "invocation is not a constant expression">; +def metafn_first_argument_is_not_object : Note< + "expected related object reflection as a first argument for invoking " + "non-static member function">; +def metafn_function_is_not_member_of_object : Note< + "method is not a member of " + "given object reflection">; +def metafn_function_returns_void : Note< + "cannot invoke reflection of void-returning function">; + // Extraction. def metafn_cannot_extract : Note< diff --git a/clang/lib/Sema/Metafunctions.cpp b/clang/lib/Sema/Metafunctions.cpp index 9214ab80773262..991dcd959bcdd7 100644 --- a/clang/lib/Sema/Metafunctions.cpp +++ b/clang/lib/Sema/Metafunctions.cpp @@ -4480,6 +4480,63 @@ bool reflect_result(APValue &Result, Sema &S, EvalFn Evaluator, return SetAndSucceed(Result, Arg.Lift(Args[1]->getType())); } +bool is_nonstatic_member_function(ValueDecl *FD) { + if (!FD) { + return false; + } + + if (dyn_cast(FD)) { + return false; + } + + auto *MD = dyn_cast(FD); + if (!MD) { + // might be a pointer to member function + QualType QT = FD->getType(); + // check if the type is a pointer to a member + if (const MemberPointerType *MPT = QT->getAs()) { + QualType PT = MPT->getPointeeType(); + // check if the pointee type is a function type + if (const FunctionProtoType *FPT = PT->getAs()) { + return true; + } + } + } else { + return !MD->isStatic(); + } + + return false; +} + +CXXMethodDecl *getCXXMethodDeclFromDeclRefExpr(DeclRefExpr *DRE, Sema &S) { + ValueDecl *VD = DRE->getDecl(); + + if (auto *MD = dyn_cast(VD)) { + // method declaration + return MD; + } else { + // pointer to non-static method + // validation was done in is_nonstatic_member_function + Expr::EvalResult ER; + if (!DRE->EvaluateAsRValue(ER, S.Context)) { + return nullptr; + } + + APValue Result = ER.Val; + if (!Result.isMemberPointer()) { + return nullptr; + } + + const ValueDecl *MemberDecl = Result.getMemberPointerDecl(); + if (const CXXMethodDecl *MethodDecl = dyn_cast(MemberDecl)) { + // get non-const version + return const_cast(MethodDecl); + } + } + + return nullptr; +} + bool reflect_invoke(APValue &Result, Sema &S, EvalFn Evaluator, DiagFn Diagnoser, QualType ResultTy, SourceRange Range, ArrayRef Args) { @@ -4633,10 +4690,17 @@ bool reflect_invoke(APValue &Result, Sema &S, EvalFn Evaluator, { sema::TemplateDeductionInfo Info(Args[0]->getExprLoc(), FTD->getTemplateDepth()); + + bool exclude_first_arg = + is_nonstatic_member_function(FTD->getTemplatedDecl()) && + ArgExprs.size() > 0; + TemplateDeductionResult Result = S.DeduceTemplateArguments( - FTD, &ExplicitTAListInfo, ArgExprs, Specialization, Info, false, - true, QualType(), Expr::Classification(), - [](ArrayRef) { return false; }); + FTD, &ExplicitTAListInfo, + ArrayRef(ArgExprs.begin() + (exclude_first_arg ? 1 : 0), + ArgExprs.end()), + Specialization, Info, false, true, QualType(), Expr::Classification(), + [](ArrayRef) { return false; }); if (Result != TemplateDeductionResult::Success) return Diagnoser(Range.getBegin(), diag::metafn_no_specialization_found) << FTD << Range; @@ -4656,20 +4720,106 @@ bool reflect_invoke(APValue &Result, Sema &S, EvalFn Evaluator, ExprResult ER; { EnterExpressionEvaluationContext Context( - S, Sema::ExpressionEvaluationContext::ConstantEvaluated); - if (auto *DRE = dyn_cast(FnRefExpr); - DRE && dyn_cast(DRE->getDecl())) { - auto *CtorD = cast(DRE->getDecl()); + S, Sema::ExpressionEvaluationContext::ConstantEvaluated); + auto *DRE = dyn_cast(FnRefExpr); + if (DRE && dyn_cast(DRE->getDecl())) { + auto *CtorD = cast(DRE->getDecl()); ER = S.BuildCXXConstructExpr( - Range.getBegin(), QualType(CtorD->getParent()->getTypeForDecl(), 0), - CtorD, false, ArgExprs, false, false, false, false, - CXXConstructionKind::Complete, Range); + Range.getBegin(), QualType(CtorD->getParent()->getTypeForDecl(), 0), + CtorD, false, ArgExprs, false, false, false, false, + CXXConstructionKind::Complete, Range); } else { - ER = S.ActOnCallExpr(S.getCurScope(), FnRefExpr, Range.getBegin(), - ArgExprs, Range.getEnd(), /*ExecConfig=*/nullptr); + Expr *FnExpr = FnRefExpr; + bool handle_member_func = + DRE && is_nonstatic_member_function(DRE->getDecl()); + + if (handle_member_func) { + + if (ArgExprs.size() < 1) { + // need to have object as a first argument + return Diagnoser(Range.getBegin(), + diag::metafn_first_argument_is_not_object) + << Range; + } + + Expr *ObjExpr = ArgExprs[0]; + QualType ObjType = ObjExpr->getType(); + + if (ObjType->isPointerType()) { + ObjType = ObjType->getPointeeType(); + // convert lvalue to rvalue if needed + // since Sema::BuildMemberExpr inside Sema::ActOnMemberAccessExpr + // expects prvalue + ObjExpr = S.DefaultFunctionArrayLvalueConversion(ObjExpr).get(); + } + + if (!ObjType->getAsCXXRecordDecl()) { + // first argument is not an object + return Diagnoser(Range.getBegin(), + diag::metafn_first_argument_is_not_object) + << Range; + } + + CXXMethodDecl *MD = getCXXMethodDeclFromDeclRefExpr(DRE, S); + if (!MD) { + // most likely, non-constexpr pointer to method was passed + return true; + } + + // this call is needed to make + // CXXSpliceExpr work with pointers to non-static methods + // (we unwrap pointer in getCXXMethodDeclFromDeclRefExpr(DRE) function) + // for non-pointer setDecl(MD) call is no-op + DRE->setDecl(MD); + + auto ObjClass = ObjType->getAsCXXRecordDecl(); + // check that method belongs to class + bool IsMethodFromClassOrParent = (MD->getParent() == ObjClass) || + ObjClass->isDerivedFrom(MD->getParent()); + if (!IsMethodFromClassOrParent) { + return Diagnoser(Range.getBegin(), + diag::metafn_function_is_not_member_of_object) + << Range; + } + + if (MD->getReturnType()->isVoidType()) { + // void return type is not supported + return Diagnoser(Range.getBegin(), diag::metafn_function_returns_void) + << Range; + } + + SourceLocation PlaceholderLoc; + // Hack below is needed to prevent lookup or overload resolution of + // given method reflection. Because this problem has been solved before + // for splice expressions, wrap our decl ref into splice expr and reuse + // specific overload of Sema::ActOnMemberAccessExpr + auto MethodAsSpliceExpr = CXXSpliceExpr::Create( + S.Context, DRE->getValueKind(), PlaceholderLoc, PlaceholderLoc, DRE, + PlaceholderLoc, &ExplicitTAListInfo, + /* this arg is not used */ false); + + SourceLocation ObjLoc = ObjExpr->getExprLoc(); + ExprResult MemberAccessResult = S.ActOnMemberAccessExpr( + S.getCurScope(), ObjExpr, ObjLoc, + ObjExpr->getType()->isPointerType() ? tok::arrow : tok::period, + MethodAsSpliceExpr, PlaceholderLoc); + + if (MemberAccessResult.isInvalid()) { + return true; + } + + FnExpr = MemberAccessResult.get(); + } + + ER = S.ActOnCallExpr( + S.getCurScope(), FnExpr, Range.getBegin(), + MutableArrayRef(ArgExprs.begin() + (handle_member_func ? 1 : 0), + ArgExprs.end()), + Range.getEnd(), /*ExecConfig=*/nullptr); } } + if (ER.isInvalid()) return Diagnoser(Range.getBegin(), diag::metafn_invalid_call_expr) << Range; Expr *ResultExpr = ER.get(); diff --git a/libcxx/test/std/experimental/reflection/reflect-invoke.pass.cpp b/libcxx/test/std/experimental/reflection/reflect-invoke.pass.cpp index 3e3b838431a4c5..9a5b5b640dbf52 100644 --- a/libcxx/test/std/experimental/reflection/reflect-invoke.pass.cpp +++ b/libcxx/test/std/experimental/reflection/reflect-invoke.pass.cpp @@ -64,9 +64,9 @@ static_assert([:reflect_invoke(^^Cls::fn, // With reflection of constexpr variable as an argument. static constexpr int five = 5; + static_assert([:reflect_invoke(^^fn1, {^^five}):] == 47); -// TODO(P2996): Support nonstatic member functions. } // namespace basic_functions // ================= @@ -244,4 +244,143 @@ static_assert( std::views::transform(std::meta::reflect_value))); } // namespace with_non_contiguous_ranges +namespace non_static_member_functions { + +struct Number { +public: + consteval Number(int v) : value(v) {} + + consteval int plus(int a) const { return plus_impl(a); } + + constexpr int get_value() const { return value; } + + consteval Number operator+(int num) const { return Number(plus_impl(num)); } + + template + consteval T multiply(T x) const { + return value * x; + } + + const int value; + +private: + consteval int plus_impl(int a) const { return value + a; } +}; + +constexpr Number num{42}; + +// member function with input arguments +static_assert(std::meta::reflect_value(84) == + reflect_invoke(^^Number::plus, + {^^num, std::meta::reflect_value(42)})); + +// operator overload +static_assert(std::meta::reflect_value(84) == + reflect_invoke(^^Number::get_value, + {reflect_invoke(^^Number::operator+, + {^^num, std::meta::reflect_value(42)})})); + +// member function without input arguments +static_assert(std::meta::reflect_value(42) == + reflect_invoke(^^Number::get_value, + {^^num})); + +// member function called with object reference +constexpr auto num_ref = # +static_assert(std::meta::reflect_value(42) == + reflect_invoke(^^Number::get_value, + {^^num_ref})); + +// template member function +static_assert(std::meta::reflect_value(84) == + reflect_invoke(^^Number::multiply, + {^^int}, + {^^num, std::meta::reflect_value(2)})); + +// template member function + template argument deduction +static_assert(std::meta::reflect_value(84) == + reflect_invoke(^^Number::multiply, + {^^num, std::meta::reflect_value(2)})); + +// Invoking Base::fn() with an object of type Child +struct IsReal { + consteval IsReal(bool v): value(v){} + + consteval bool is_real() const { + return value; + } + + const bool value; +}; + +struct FloatNumber : public Number, IsReal{ + consteval FloatNumber(int v) : Number(v), IsReal(true) {} +}; + +constexpr FloatNumber childNumber{42}; +static_assert(std::meta::reflect_value(42) == + reflect_invoke(^^Number::get_value, + {^^childNumber})); +static_assert(std::meta::reflect_value(true) == + reflect_invoke(^^IsReal::is_real, + {^^childNumber})); + +} // namespace non_static_member_functions + +namespace function_pointer { +// pointer to simple function +constexpr int foo(int a) { + return a + 42; +} + +constexpr int (*foo_pointer)(int) = &foo; +static_assert(reflect_invoke(^^foo_pointer, {std::meta::reflect_value(0)}) + == std::meta::reflect_value(42)); + +constexpr static int (*foo_static_pointer)(int) = &foo; +static_assert(reflect_invoke(^^foo_static_pointer, {std::meta::reflect_value(2)}) + == std::meta::reflect_value(44)); + +// pointer to template function +template +constexpr T bar(T a) { + return a + 42; +} + +constexpr int (*bar_pointer)(int) = &bar; +static_assert(reflect_invoke(^^bar_pointer, {std::meta::reflect_value(1)}) + == std::meta::reflect_value(43)); +static_assert(reflect_invoke(std::meta::reflect_value(bar_pointer), {std::meta::reflect_value(1)}) == + std::meta::reflect_value(43)); +static_assert(reflect_invoke(std::meta::reflect_object(bar_pointer), {std::meta::reflect_value(1)}) == + std::meta::reflect_value(43)); + +// pointer to method +struct Cls { +public: + constexpr Cls(int data) : data(data) {} + + static constexpr int fn(int p) { return p * p; } + + constexpr int get() const { return data; } + + const int data; +}; + +// pointer to static method +constexpr int (*fn_pointer)(int) = &Cls::fn; +static_assert(reflect_invoke(^^fn_pointer, {std::meta::reflect_value(2)}) + == std::meta::reflect_value(4)); + +// pointer to non-static method +constexpr Cls data(42); +constexpr int (Cls::*get_pointer)() const = &Cls::get; +static_assert(reflect_invoke(^^get_pointer, {^^data}) == std::meta::reflect_value(42)); + +// object with static storage duration holding a pointer to a constexpr function +constexpr static int (Cls::*get_static_pointer)() const = &Cls::get; +static_assert(reflect_invoke(^^get_static_pointer, {^^data}) == std::meta::reflect_value(42)); + +} // namespace function_pointer + int main() { } diff --git a/libcxx/test/std/experimental/reflection/reflect-invoke.verify.cpp b/libcxx/test/std/experimental/reflection/reflect-invoke.verify.cpp new file mode 100644 index 00000000000000..9001de9ee8d182 --- /dev/null +++ b/libcxx/test/std/experimental/reflection/reflect-invoke.verify.cpp @@ -0,0 +1,73 @@ +//===----------------------------------------------------------------------===// +// +// Copyright 2024 Bloomberg Finance L.P. +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03 || c++11 || c++14 || c++17 || c++20 +// ADDITIONAL_COMPILE_FLAGS: -freflection -freflection-new-syntax +// ADDITIONAL_COMPILE_FLAGS: -Wno-unneeded-internal-declaration -Wno-unused-variable -Wno-unused-value + +// +// +// [reflection] + +#include + +namespace NS { +struct A { + consteval int fn() const { return 24; } +}; +} // namespace NS + +struct A { + constexpr int fn() const { return 42; } + + consteval void void_fn() const { + // no-op + }; +}; + +struct B {}; + +int main() { + // ====================== + // non-static member functions + // ====================== + constexpr A expectedClass{}; + reflect_invoke(^^A::fn, {^^expectedClass}); // ok + + reflect_invoke(^^A::void_fn, {^^expectedClass}); + // expected-error-re@-1 {{call to consteval function 'std::meta::reflect_invoke<{{.*}}>' is not a constant expression}} + // expected-note@-2 {{cannot invoke reflection of void-returning function}} + + reflect_invoke(^^A::fn, {}); + // expected-error-re@-1 {{call to consteval function 'std::meta::reflect_invoke<{{.*}}>' is not a constant expression}} + // expected-note@-2 {{expected related object reflection as a first argument for invoking non-static member function}} + + reflect_invoke(^^A::fn, {std::meta::reflect_value(42)}); + // expected-error-re@-1 {{call to consteval function 'std::meta::reflect_invoke<{{.*}}>' is not a constant expression}} + // expected-note@-2 {{expected related object reflection as a first argument for invoking non-static member function}} + + constexpr B differentClass{}; + reflect_invoke(^^A::fn, {^^differentClass}); + // expected-error-re@-1 {{call to consteval function 'std::meta::reflect_invoke<{{.*}}>' is not a constant expression}} + // expected-note@-2 {{method is not a member of given object reflection}} + + constexpr NS::A differentNamespaceClass{}; + reflect_invoke(^^A::fn, {^^differentNamespaceClass}); + // expected-error-re@-1 {{call to consteval function 'std::meta::reflect_invoke<{{.*}}>' is not a constant expression}} + // expected-note@-2 {{method is not a member of given object reflection}} + + // test that implementation workaround with getting constexpr method from pointer couldn't be abused + constexpr int (A::*constexpr_pointer)() const = &A::fn; + reflect_invoke(^^constexpr_pointer, {^^expectedClass}); // ok + + int (A::*pointer)() const = &A::fn; + reflect_invoke(^^pointer, {^^expectedClass}); + // expected-error-re@-1 {{call to consteval function 'std::meta::reflect_invoke<{{.*}}>' is not a constant expression}} +}