Skip to content

Commit

Permalink
Use reflect_invoke metafunction to invoke a member function (#79)
Browse files Browse the repository at this point in the history
* initial implementation

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* format

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* refactoring to simplify if statements

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* support of template member functions

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* add diagnostic

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* wording

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* more optimal exclusion of object argument

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* hack with wrapping decl reference into splice expr to prevent lookup and overloadd

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* fix bug + add automated tests for diagnostics

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* comment

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* clean up

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* test refactoring

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* apply different small review remarks

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* add support for using methods of base classes

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* migrate existing tests to new syntax

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* test existing functionality related to function pointers

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* support of pointer to non-static method

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* test for object with static storage duration holding a pointer to a constexpr function

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* address misunderstanding about static pointers + fix bug related to it

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* comments

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* add requested tests related to template functions -- passed

Signed-off-by: Valentyn Yukhymenko <[email protected]>

* refactor logic of getting CXXMethodDecl from function pointer

Signed-off-by: Valentyn Yukhymenko <[email protected]>

---------

Signed-off-by: Valentyn Yukhymenko <[email protected]>
  • Loading branch information
BaLiKfromUA authored Sep 17, 2024
1 parent 25ef189 commit 40b5909
Show file tree
Hide file tree
Showing 4 changed files with 384 additions and 13 deletions.
9 changes: 9 additions & 0 deletions clang/include/clang/Basic/DiagnosticMetafnKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,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<
Expand Down
174 changes: 162 additions & 12 deletions clang/lib/Sema/Metafunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4667,6 +4667,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<CXXConstructorDecl>(FD)) {
return false;
}

auto *MD = dyn_cast<CXXMethodDecl>(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<MemberPointerType>()) {
QualType PT = MPT->getPointeeType();
// check if the pointee type is a function type
if (const FunctionProtoType *FPT = PT->getAs<FunctionProtoType>()) {
return true;
}
}
} else {
return !MD->isStatic();
}

return false;
}

CXXMethodDecl *getCXXMethodDeclFromDeclRefExpr(DeclRefExpr *DRE, Sema &S) {
ValueDecl *VD = DRE->getDecl();

if (auto *MD = dyn_cast<CXXMethodDecl>(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<CXXMethodDecl>(MemberDecl)) {
// get non-const version
return const_cast<CXXMethodDecl *>(MethodDecl);
}
}

return nullptr;
}

bool reflect_invoke(APValue &Result, Sema &S, EvalFn Evaluator,
DiagFn Diagnoser, QualType ResultTy, SourceRange Range,
ArrayRef<Expr *> Args) {
Expand Down Expand Up @@ -4821,10 +4878,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<QualType>) { return false; });
FTD, &ExplicitTAListInfo,
ArrayRef(ArgExprs.begin() + (exclude_first_arg ? 1 : 0),
ArgExprs.end()),
Specialization, Info, false, true, QualType(), Expr::Classification(),
[](ArrayRef<QualType>) { return false; });
if (Result != TemplateDeductionResult::Success)
return Diagnoser(Range.getBegin(), diag::metafn_no_specialization_found)
<< FTD << Range;
Expand All @@ -4844,20 +4908,106 @@ bool reflect_invoke(APValue &Result, Sema &S, EvalFn Evaluator,
ExprResult ER;
{
EnterExpressionEvaluationContext Context(
S, Sema::ExpressionEvaluationContext::ConstantEvaluated);
if (auto *DRE = dyn_cast<DeclRefExpr>(FnRefExpr);
DRE && dyn_cast<CXXConstructorDecl>(DRE->getDecl())) {
auto *CtorD = cast<CXXConstructorDecl>(DRE->getDecl());
S, Sema::ExpressionEvaluationContext::ConstantEvaluated);

auto *DRE = dyn_cast<DeclRefExpr>(FnRefExpr);
if (DRE && dyn_cast<CXXConstructorDecl>(DRE->getDecl())) {
auto *CtorD = cast<CXXConstructorDecl>(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();
Expand Down
141 changes: 140 additions & 1 deletion libcxx/test/std/experimental/reflection/reflect-invoke.pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

// =================
Expand Down Expand Up @@ -244,4 +244,143 @@ static_assert(
std::views::transform(std::meta::reflect_value<int>)));
} // 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 <typename T>
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 = &num;
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 <typename T>
constexpr T bar(T a) {
return a + 42;
}

constexpr int (*bar_pointer)(int) = &bar<int>;
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() { }
Loading

0 comments on commit 40b5909

Please sign in to comment.