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

Use reflect_invoke metafunction to invoke a member function #79

Merged
merged 24 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d2b3a59
initial implementation
BaLiKfromUA Jul 23, 2024
cda6611
Merge branch 'p2996' into clang-p2996/issues/5-refined
BaLiKfromUA Jul 31, 2024
618dcf4
format
BaLiKfromUA Jul 31, 2024
cd8adb6
refactoring to simplify if statements
BaLiKfromUA Aug 1, 2024
e7899d2
support of template member functions
BaLiKfromUA Aug 1, 2024
dc8003c
add diagnostic
BaLiKfromUA Aug 2, 2024
81a4dd7
wording
BaLiKfromUA Aug 2, 2024
a8b7a4a
more optimal exclusion of object argument
BaLiKfromUA Aug 9, 2024
8d32747
hack with wrapping decl reference into splice expr to prevent lookup …
BaLiKfromUA Aug 12, 2024
6479e09
fix bug + add automated tests for diagnostics
BaLiKfromUA Aug 13, 2024
16e63c7
comment
BaLiKfromUA Aug 13, 2024
22a2108
clean up
BaLiKfromUA Aug 21, 2024
c416cfb
test refactoring
BaLiKfromUA Aug 21, 2024
2dd9246
apply different small review remarks
BaLiKfromUA Aug 23, 2024
fe85f72
add support for using methods of base classes
BaLiKfromUA Aug 23, 2024
ff458eb
Merge branch 'p2996' into clang-p2996/issues/5-refined
BaLiKfromUA Aug 26, 2024
7a47147
migrate existing tests to new syntax
BaLiKfromUA Aug 27, 2024
10fff74
test existing functionality related to function pointers
BaLiKfromUA Aug 27, 2024
3c5d8f3
support of pointer to non-static method
BaLiKfromUA Aug 29, 2024
d95299b
test for object with static storage duration holding a pointer to a c…
BaLiKfromUA Aug 29, 2024
4974bfa
address misunderstanding about static pointers + fix bug related to it
BaLiKfromUA Sep 4, 2024
3bab1b1
comments
BaLiKfromUA Sep 4, 2024
562137e
add requested tests related to template functions -- passed
BaLiKfromUA Sep 16, 2024
b4ec538
refactor logic of getting CXXMethodDecl from function pointer
BaLiKfromUA Sep 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions clang/include/clang/Basic/DiagnosticMetafnKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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<
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 @@ -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<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 @@ -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<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 @@ -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<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
Loading