[clang] [clang][bytecode] Implement support for `Expr::EvaluateWithSubstitution()` (PR #204781)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Fri Jun 26 07:39:35 PDT 2026
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/204781
>From 8e8112a9e0c03dedc43c436fee6537cfe13a4eb5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Thu, 18 Jun 2026 17:57:09 +0200
Subject: [PATCH] puh
---
clang/lib/AST/ByteCode/ByteCodeEmitter.h | 4 +
clang/lib/AST/ByteCode/Compiler.cpp | 118 +++++++++-
clang/lib/AST/ByteCode/Compiler.h | 3 +
clang/lib/AST/ByteCode/Context.cpp | 27 +++
clang/lib/AST/ByteCode/Context.h | 6 +
clang/lib/AST/ByteCode/EvalEmitter.cpp | 17 ++
clang/lib/AST/ByteCode/EvalEmitter.h | 9 +
clang/lib/AST/ExprConstant.cpp | 11 +
.../test/AST/ByteCode/builtin-constant-p.cpp | 5 +-
clang/test/AST/ByteCode/enable_if.c | 202 ++++++++++++++++++
...-nested-call-with-valuedependent-param.cpp | 1 +
clang/test/SemaCXX/enable_if.cpp | 2 +
12 files changed, 400 insertions(+), 5 deletions(-)
create mode 100644 clang/test/AST/ByteCode/enable_if.c
diff --git a/clang/lib/AST/ByteCode/ByteCodeEmitter.h b/clang/lib/AST/ByteCode/ByteCodeEmitter.h
index 4b42b7eb4063b..34342e53837b9 100644
--- a/clang/lib/AST/ByteCode/ByteCodeEmitter.h
+++ b/clang/lib/AST/ByteCode/ByteCodeEmitter.h
@@ -50,6 +50,10 @@ class ByteCodeEmitter {
virtual bool visitDeclAndReturn(const VarDecl *VD, const Expr *Init,
bool ConstantContext) = 0;
virtual bool visitDtorCall(const VarDecl *VD, const APValue &) = 0;
+ virtual bool visitWithSubstitutions(const FunctionDecl *Callee,
+ ArrayRef<const Expr *> Args,
+ const Expr *This,
+ const Expr *Condition) = 0;
virtual bool visit(const Expr *E) = 0;
virtual bool emitBool(bool V, const Expr *E) = 0;
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 20b110b38ff78..53f1876266c7d 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -16,6 +16,7 @@
#include "PrimType.h"
#include "Program.h"
#include "clang/AST/Attr.h"
+#include "clang/AST/DynamicRecursiveASTVisitor.h"
#include "llvm/Support/SaveAndRestore.h"
using namespace clang;
@@ -5424,6 +5425,115 @@ bool Compiler<Emitter>::visitDtorCall(const VarDecl *VD, const APValue &Value) {
return this->emitDestructionPop(D, VD);
}
+class ParamFinder : public ConstDynamicRecursiveASTVisitor {
+public:
+ llvm::SmallPtrSet<const ParmVarDecl *, 1> FoundParams;
+ explicit ParamFinder() {}
+
+ bool VisitDeclRefExpr(const DeclRefExpr *E) override {
+ if (const auto *P = dyn_cast<ParmVarDecl>(E->getDecl()))
+ FoundParams.insert(P);
+ return true;
+ }
+};
+
+/// Evaluate the \p Condition as if it was in the body of \p Callee.
+/// Specifically, all the parameters of the callee are available to use
+/// for the condition, and their values are given by \p Args (and \p This).
+///
+// Since this is a somewhat niche feature, we're abusing a few other mechanisms
+// to implement this.
+//
+// We don't create an actual function frame but instead register the parameters
+// as local variables.
+//
+// So we evaluate something like:
+//
+// bool thisfunc() {
+// auto Arg0 = Args[0];
+// ...
+// return Condition;
+// }
+//
+template <class Emitter>
+bool Compiler<Emitter>::visitWithSubstitutions(const FunctionDecl *Callee,
+ ArrayRef<const Expr *> Args,
+ const Expr *This,
+ const Expr *Condition) {
+ // Instead of evaluating all parameters and trying to ignore failure,
+ // we collect all the parameters used in the condition and only evaluate
+ // those. Note that we still ignore failure in the loop below because the
+ // failure might be inconsequential in the end,
+ // e.g. in the case of `true || x`.
+ ParamFinder PF;
+ PF.TraverseStmt(Condition);
+
+ LocalScope<Emitter> ArgScope(this);
+ for (const ParmVarDecl *PVD : PF.FoundParams) {
+ unsigned ParamIndex = 0;
+ for (const ParmVarDecl *P : Callee->parameters()) {
+ if (P == PVD)
+ break;
+ ++ParamIndex;
+ }
+
+ const Expr *Arg = Args[ParamIndex];
+ const ParmVarDecl *Param = Callee->getParamDecl(ParamIndex);
+ if (OptPrimType ParamT = classify(Param->getType())) {
+ unsigned ArgOffset =
+ allocateLocalPrimitive(Param, *ParamT, /*IsConst=*/true);
+ if (!this->visit(Arg))
+ continue;
+ if (!this->emitSetLocal(*ParamT, ArgOffset, Arg))
+ return false;
+ } else {
+ UnsignedOrNone ArgOffset = this->allocateLocal(Param, Param->getType());
+ if (!ArgOffset)
+ return false;
+ if (!this->emitGetPtrLocal(*ArgOffset, Arg))
+ return false;
+ if (!this->visitInitializerPop(Arg))
+ continue;
+ }
+ }
+
+ if (This) {
+ // We abuse the init stack for this and tell it to use
+ // either a local variable or another decl for the This pointer.
+ this->InitStackActive = true;
+
+ if (This->getType()->isPointerType()) {
+ // Nothing to do here, the evaluation will fail if the instance
+ // pointer is used.
+ } else if (const auto *DRE = dyn_cast<DeclRefExpr>(This)) {
+ InitStack.push_back(InitLink::Decl(DRE->getDecl()));
+ } else {
+ assert(!canClassify(This->getType()));
+ UnsignedOrNone ArgOffset = this->allocateLocal(This, This->getType());
+ if (!ArgOffset)
+ return false;
+ if (!this->emitGetPtrLocal(*ArgOffset, This))
+ return false;
+ if (!this->visitInitializerPop(This))
+ return false;
+ this->InitStack.push_back(InitLink::Temp(*ArgOffset));
+ }
+ }
+
+ // Destruction of the argument values is part of the callee frame,
+ // so we simply ignore them here.
+ this->VarScope = nullptr;
+
+ LocalScope<Emitter> RetScope(this);
+ if (!this->visit(Condition))
+ return false;
+ if (!RetScope.destroyLocals())
+ return false;
+
+ // Result of the condition should be on the stack.
+ return this->emitRet(PT_Bool, Condition);
+}
+
template <class Emitter>
bool Compiler<Emitter>::visitAPValue(const APValue &Val, PrimType ValType,
SourceInfo Info) {
@@ -6064,7 +6174,10 @@ bool Compiler<Emitter>::VisitCXXThisExpr(const CXXThisExpr *E) {
if (StartIndex == 0 && EndIndex == 0)
EndIndex = InitStack.size() - 1;
- assert(StartIndex < EndIndex);
+ // NOTE: This could be StartIndex < EndIndex, but we're also abusing the
+ // InitStack mechanism in visitWithSubstitutions to have the This pointer
+ // _just_ be a local variable.
+ assert(StartIndex <= EndIndex);
// Emit the instructions.
for (unsigned I = StartIndex; I != (EndIndex + 1); ++I) {
@@ -7735,10 +7848,11 @@ bool Compiler<Emitter>::visitDeclRef(const ValueDecl *D, const Expr *E) {
return this->emitGetPtrParam(It->second.Index, E);
}
- if (!Ctx.getLangOpts().CPlusPlus23 && IsReference)
+ if (!Ctx.getLangOpts().CPlusPlus23 && IsReference && !Locals.contains(D))
return this->emitInvalidDeclRef(cast<DeclRefExpr>(E),
/*InitializerFailed=*/false, E);
}
+
// Local variables.
if (auto It = Locals.find(D); It != Locals.end()) {
const unsigned Offset = It->second.Offset;
diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h
index 85105d9d42520..0a2e75887711d 100644
--- a/clang/lib/AST/ByteCode/Compiler.h
+++ b/clang/lib/AST/ByteCode/Compiler.h
@@ -260,6 +260,9 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
bool visitDeclAndReturn(const VarDecl *VD, const Expr *Init,
bool ConstantContext) override;
bool visitDtorCall(const VarDecl *VD, const APValue &Value) override;
+ bool visitWithSubstitutions(const FunctionDecl *Callee,
+ ArrayRef<const Expr *> Args, const Expr *This,
+ const Expr *Condition) override;
protected:
/// Emits scope cleanup instructions.
diff --git a/clang/lib/AST/ByteCode/Context.cpp b/clang/lib/AST/ByteCode/Context.cpp
index 40cf29efcfb4f..bd04b18867321 100644
--- a/clang/lib/AST/ByteCode/Context.cpp
+++ b/clang/lib/AST/ByteCode/Context.cpp
@@ -389,6 +389,33 @@ Context::tryEvaluateObjectSize(State &Parent, const Expr *E, unsigned Kind) {
return Result;
}
+std::optional<bool>
+Context::evaluateWithSubstitution(State &Parent, const FunctionDecl *Callee,
+ ArrayRef<const Expr *> Args, const Expr *This,
+ const Expr *Condition) {
+ if (OptPrimType ConditionT = classify(Condition);
+ !ConditionT || ConditionT != PT_Bool) {
+ return std::nullopt;
+ }
+
+ assert(Stk.empty());
+ Compiler<EvalEmitter> C(*this, *P, Parent, Stk);
+ std::optional<bool> Result =
+ C.interpretWithSubstitutions(Callee, Args, This, Condition);
+
+ // This is somewhat of a special case here. We don't allow
+ // evaluateWithSubstitution to recurse (see the Stk.empty() assertion above),
+ // BUT we allow the args to fail evaluation, which means they can leave some
+ // garbage on the stack. So we always clear() here, not only if the evaluation
+ // failed.
+ Stk.clear();
+ if (!Result) {
+ C.cleanup();
+ return std::nullopt;
+ }
+ return Result;
+}
+
const LangOptions &Context::getLangOpts() const { return Ctx.getLangOpts(); }
static PrimType integralTypeToPrimTypeS(unsigned BitWidth) {
diff --git a/clang/lib/AST/ByteCode/Context.h b/clang/lib/AST/ByteCode/Context.h
index 789f72ae34f73..47821a3e3a7f3 100644
--- a/clang/lib/AST/ByteCode/Context.h
+++ b/clang/lib/AST/ByteCode/Context.h
@@ -97,6 +97,12 @@ class Context final {
std::optional<uint64_t> tryEvaluateObjectSize(State &Parent, const Expr *E,
unsigned Kind);
+ std::optional<bool> evaluateWithSubstitution(State &Parent,
+ const FunctionDecl *Callee,
+ ArrayRef<const Expr *> Args,
+ const Expr *This,
+ const Expr *Condition);
+
/// Returns the AST context.
ASTContext &getASTContext() const { return Ctx; }
/// Returns the language options.
diff --git a/clang/lib/AST/ByteCode/EvalEmitter.cpp b/clang/lib/AST/ByteCode/EvalEmitter.cpp
index 83dac57353944..63c14fcc48fef 100644
--- a/clang/lib/AST/ByteCode/EvalEmitter.cpp
+++ b/clang/lib/AST/ByteCode/EvalEmitter.cpp
@@ -129,6 +129,23 @@ bool EvalEmitter::interpretCall(const FunctionDecl *FD, const Expr *E) {
return this->visitExpr(E, /*DestroyToplevelScope=*/false);
}
+std::optional<bool> EvalEmitter::interpretWithSubstitutions(
+ const FunctionDecl *Callee, ArrayRef<const Expr *> Args, const Expr *This,
+ const Expr *Condition) {
+
+ if (!this->visitWithSubstitutions(Callee, Args, This, Condition))
+ return std::nullopt;
+
+ if (EvalResult.empty() || EvalResult.isInvalid())
+ return false;
+
+ assert(!EvalResult.empty());
+ APValue Result = EvalResult.stealAPValue();
+
+ assert(Result.isInt());
+ return Result.getInt().getBoolValue();
+}
+
void EvalEmitter::emitLabel(LabelTy Label) { CurrentLabel = Label; }
EvalEmitter::LabelTy EvalEmitter::getLabel() { return NextLabel++; }
diff --git a/clang/lib/AST/ByteCode/EvalEmitter.h b/clang/lib/AST/ByteCode/EvalEmitter.h
index 6fd50da8cad76..b1080c993ab8c 100644
--- a/clang/lib/AST/ByteCode/EvalEmitter.h
+++ b/clang/lib/AST/ByteCode/EvalEmitter.h
@@ -48,6 +48,11 @@ class EvalEmitter : public SourceMapper {
/// function, i.e. the parameters of the function are available for use.
bool interpretCall(const FunctionDecl *FD, const Expr *E);
+ std::optional<bool> interpretWithSubstitutions(const FunctionDecl *Callee,
+ ArrayRef<const Expr *> Args,
+ const Expr *This,
+ const Expr *Condition);
+
/// Clean up all resources.
void cleanup();
@@ -67,6 +72,10 @@ class EvalEmitter : public SourceMapper {
virtual bool visitDeclAndReturn(const VarDecl *VD, const Expr *Init,
bool ConstantContext) = 0;
virtual bool visitDtorCall(const VarDecl *VD, const APValue &Value) = 0;
+ virtual bool visitWithSubstitutions(const FunctionDecl *Callee,
+ ArrayRef<const Expr *> Args,
+ const Expr *This,
+ const Expr *Condition) = 0;
virtual bool visitFunc(const FunctionDecl *F) = 0;
virtual bool visit(const Expr *E) = 0;
virtual bool emitBool(bool V, const Expr *E) = 0;
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 1d359339b9104..810c1c9fa1750 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -16410,6 +16410,7 @@ static bool determineEndOffset(EvalInfo &Info, SourceLocation ExprLoc,
static std::optional<uint64_t>
tryEvaluateBuiltinObjectSize(const Expr *E, unsigned Type, EvalInfo &Info,
bool IsDynamic = false) {
+
// Determine the denoted object.
LValue LVal;
{
@@ -22443,6 +22444,16 @@ bool Expr::EvaluateWithSubstitution(APValue &Value, ASTContext &Ctx,
EvalInfo Info(Ctx, Status, EvaluationMode::ConstantExpressionUnevaluated);
Info.InConstantContext = true;
+ if (Info.EnableNewConstInterp) {
+ if (std::optional<bool> BoolResult =
+ Info.Ctx.getInterpContext().evaluateWithSubstitution(
+ Info, Callee, Args, This, this)) {
+ Value = APValue(APSInt(APInt(1, static_cast<uint64_t>(*BoolResult))));
+ return true;
+ }
+ return false;
+ }
+
LValue ThisVal;
const LValue *ThisPtr = nullptr;
if (This) {
diff --git a/clang/test/AST/ByteCode/builtin-constant-p.cpp b/clang/test/AST/ByteCode/builtin-constant-p.cpp
index c6b074e403bed..1738ed1e9328d 100644
--- a/clang/test/AST/ByteCode/builtin-constant-p.cpp
+++ b/clang/test/AST/ByteCode/builtin-constant-p.cpp
@@ -141,12 +141,11 @@ void test17(void) {
// both-note {{use array indexing}}
}
-/// FIXME
-static void foo(int i) __attribute__((__diagnose_if__(!__builtin_constant_p(i), "not constant", "error"))) // expected-note {{from}}
+static void foo(int i) __attribute__((__diagnose_if__(!__builtin_constant_p(i), "not constant", "error")))
{
}
static void bar(int i) {
- foo(15); // expected-error {{not constant}}
+ foo(15);
}
namespace Inactive {
diff --git a/clang/test/AST/ByteCode/enable_if.c b/clang/test/AST/ByteCode/enable_if.c
new file mode 100644
index 0000000000000..8148db2719449
--- /dev/null
+++ b/clang/test/AST/ByteCode/enable_if.c
@@ -0,0 +1,202 @@
+// RUN: %clang_cc1 -verify=ref,both %s
+// RUN: %clang_cc1 -verify=expected,both %s -fexperimental-new-constant-interpreter
+
+// %clang_cc1 %s -DCODEGEN -emit-llvm -o - | FileCheck %s
+// %clang_cc1 %s -DCODEGEN -emit-llvm -o - -fexperimental-new-constant-interpreter | FileCheck %s
+
+/// This is the same file we have in test/Sema/, but there is one test that doesn't yet pass with the bytecode interpreter.
+/// TODO: Delete this file and add an appropriate RUN line to the file in test/Sema/ instead.
+///
+/// The problem is related to a wrongly computed value in the __builtin_object_size implementation.
+
+#define O_CREAT 0x100
+typedef int mode_t;
+typedef unsigned long size_t;
+
+enum { TRUE = 1 };
+
+int open(const char *pathname, int flags) __attribute__((enable_if(!(flags & O_CREAT), "must specify mode when using O_CREAT"))) __attribute__((overloadable)); // both-note{{candidate disabled: must specify mode when using O_CREAT}}
+int open(const char *pathname, int flags, mode_t mode) __attribute__((overloadable)); // both-note{{candidate function not viable: requires 3 arguments, but 2 were provided}}
+
+void test1(void) {
+#ifndef CODEGEN
+ open("path", O_CREAT); // both-error{{no matching function for call to 'open'}}
+#endif
+ open("path", O_CREAT, 0660);
+ open("path", 0);
+ open("path", 0, 0);
+}
+
+size_t __strnlen_chk(const char *s, size_t requested_amount, size_t s_len);
+
+size_t strnlen(const char *s, size_t maxlen)
+ __attribute__((overloadable))
+ __asm__("strnlen_real1");
+
+__attribute__((always_inline))
+inline size_t strnlen(const char *s, size_t maxlen)
+ __attribute__((overloadable))
+ __attribute__((enable_if(__builtin_object_size(s, 0) != -1,
+ "chosen when target buffer size is known")))
+{
+ return __strnlen_chk(s, maxlen, __builtin_object_size(s, 0));
+}
+
+size_t strnlen(const char *s, size_t maxlen)
+ __attribute__((overloadable))
+ __attribute__((enable_if(__builtin_object_size(s, 0) != -1,
+ "chosen when target buffer size is known")))
+ __attribute__((enable_if(maxlen <= __builtin_object_size(s, 0),
+ "chosen when 'maxlen' is known to be less than or equal to the buffer size")))
+ __asm__("strnlen_real2");
+
+size_t strnlen(const char *s, size_t maxlen) // ref-note {{'strnlen' has been explicitly marked unavailable here}}
+ __attribute__((overloadable))
+ __attribute__((enable_if(__builtin_object_size(s, 0) != -1,
+ "chosen when target buffer size is known")))
+ __attribute__((enable_if(maxlen > __builtin_object_size(s, 0),
+ "chosen when 'maxlen' is larger than the buffer size")))
+ __attribute__((unavailable("'maxlen' is larger than the buffer size")));
+
+void test2(const char *s, int i) {
+// CHECK: define {{.*}}void @test2
+ const char c[123] = { 0 };
+ strnlen(s, i);
+// CHECK: call {{.*}}strnlen_real1
+ strnlen(s, 999);
+// CHECK: call {{.*}}strnlen_real1
+ strnlen(c, 1);
+// CHECK: call {{.*}}strnlen_real2
+ strnlen(c, i);
+// CHECK: call {{.*}}strnlen_chk
+#ifndef CODEGEN
+ strnlen(c, 999); // ref-error{{'strnlen' is unavailable: 'maxlen' is larger than the buffer size}}
+#endif
+}
+
+int isdigit(int c) __attribute__((overloadable));
+int isdigit(int c) __attribute__((overloadable)) // both-note {{'isdigit' has been explicitly marked unavailable here}}
+ __attribute__((enable_if(c <= -1 || c > 255, "'c' must have the value of an unsigned char or EOF")))
+ __attribute__((unavailable("'c' must have the value of an unsigned char or EOF")));
+
+void test3(int c) {
+ isdigit(c); // both-warning{{ignoring return value of function declared with pure attribute}}
+ isdigit(10); // both-warning{{ignoring return value of function declared with pure attribute}}
+#ifndef CODEGEN
+ isdigit(-10); // both-error{{'isdigit' is unavailable: 'c' must have the value of an unsigned char or EOF}}
+#endif
+}
+
+// Verify that the alternate spelling __enable_if__ works as well.
+int isdigit2(int c) __attribute__((overloadable));
+int isdigit2(int c) __attribute__((overloadable)) // both-note {{'isdigit2' has been explicitly marked unavailable here}}
+ __attribute__((__enable_if__(c <= -1 || c > 255, "'c' must have the value of an unsigned char or EOF")))
+ __attribute__((unavailable("'c' must have the value of an unsigned char or EOF")));
+
+void test4(int c) {
+ isdigit2(c);
+ isdigit2(10);
+#ifndef CODEGEN
+ isdigit2(-10); // both-error{{'isdigit2' is unavailable: 'c' must have the value of an unsigned char or EOF}}
+#endif
+}
+
+void test5(void) {
+ int (*p1)(int) = &isdigit2;
+ int (*p2)(int) = isdigit2;
+ void *p3 = (void *)&isdigit2;
+ void *p4 = (void *)isdigit2;
+}
+
+#ifndef CODEGEN
+__attribute__((enable_if(n == 0, "chosen when 'n' is zero"))) void f1(int n); // both-error{{use of undeclared identifier 'n'}}
+
+int n __attribute__((enable_if(1, "always chosen"))); // both-warning{{'enable_if' attribute only applies to functions}}
+
+void f(int n) __attribute__((enable_if("chosen when 'n' is zero", n == 0))); // both-error{{expected string literal as argument of 'enable_if' attribute}}
+
+void f(int n) __attribute__((enable_if())); // both-error{{'enable_if' attribute requires exactly 2 arguments}}
+
+void f(int n) __attribute__((enable_if(unresolvedid, "chosen when 'unresolvedid' is non-zero"))); // both-error{{use of undeclared identifier 'unresolvedid'}}
+
+int global;
+void f(int n) __attribute__((enable_if(global == 0, "chosen when 'global' is zero"))); // both-error{{'enable_if' attribute expression never produces a constant expression}} \
+ // both-note{{subexpression not valid in a constant expression}}
+
+enum { cst = 7 };
+void return_cst(void) __attribute__((overloadable)) __attribute__((enable_if(cst == 7, "chosen when 'cst' is 7")));
+void test_return_cst(void) { return_cst(); }
+
+void f2(void) __attribute__((overloadable)) __attribute__((enable_if(1, "always chosen"))); // #f2_1
+void f2(void) __attribute__((overloadable)) __attribute__((enable_if(0, "never chosen"))); // #f2_2
+void f2(void) __attribute__((overloadable)) __attribute__((enable_if(TRUE, "always chosen #2"))); // #f2_3
+void test6(void) {
+ void (*p1)(void) = &f2; // both-error {{initializing 'void (*)(void)' with an expression of incompatible type '<overloaded function type>'}} \
+ // both-note@#f2_1 {{candidate function}} \
+ // both-note@#f2_2 {{candidate function made ineligible by enable_if}} \
+ // both-note@#f2_3 {{candidate function}}
+ void (*p2)(void) = f2; // both-error {{initializing 'void (*)(void)' with an expression of incompatible type '<overloaded function type>'}} \
+ // both-note@#f2_1 {{candidate function}} \
+ // both-note@#f2_2 {{candidate function made ineligible by enable_if}} \
+ // both-note@#f2_3 {{candidate function}}
+ void *p3 = (void*)&f2; // both-error {{address of overloaded function 'f2' is ambiguous}} \
+ // both-note@#f2_1 {{candidate function}} \
+ // both-note@#f2_2 {{candidate function made ineligible by enable_if}} \
+ // both-note@#f2_3 {{candidate function}}
+ void *p4 = (void*)f2; // both-error {{address of overloaded function 'f2' is ambiguous}} \
+ // both-note@#f2_1 {{candidate function}} \
+ // both-note@#f2_2 {{candidate function made ineligible by enable_if}} \
+ // both-note@#f2_3 {{candidate function}}
+}
+
+void f3(int m) __attribute__((overloadable)) __attribute__((enable_if(m >= 0, "positive"))); // #f3_1
+void f3(int m) __attribute__((overloadable)) __attribute__((enable_if(m < 0, "negative"))); // #f3_2
+void test7(void) {
+ void (*p1)(int) = &f3; // both-error {{initializing 'void (*)(int)' with an expression of incompatible type '<overloaded function type>'}} \
+ // both-note@#f3_1 {{candidate function made ineligible by enable_if}} \
+ // both-note@#f3_2 {{candidate function made ineligible by enable_if}}
+ void (*p2)(int) = f3; // both-error {{initializing 'void (*)(int)' with an expression of incompatible type '<overloaded function type>'}} \
+ // both-note@#f3_1 {{candidate function made ineligible by enable_if}} \
+ // both-note@#f3_2 {{candidate function made ineligible by enable_if}}
+ void *p3 = (void*)&f3; // both-error {{address of overloaded function 'f3' does not match required type 'void'}} \
+ // both-note@#f3_1 {{candidate function made ineligible by enable_if}} \
+ // both-note@#f3_2 {{candidate function made ineligible by enable_if}}
+ void *p4 = (void*)f3; // both-error {{address of overloaded function 'f3' does not match required type 'void'}} \
+ // both-note@#f3_1 {{candidate function made ineligible by enable_if}} \
+ // both-note@#f3_2 {{candidate function made ineligible by enable_if}}
+}
+
+void f4(int m) __attribute__((enable_if(0, "")));
+void test8(void) {
+ void (*p1)(int) = &f4; // both-error{{cannot take address of function 'f4' because it has one or more non-tautological enable_if conditions}}
+ void (*p2)(int) = f4; // both-error{{cannot take address of function 'f4' because it has one or more non-tautological enable_if conditions}}
+}
+
+void regular_enable_if(int a) __attribute__((enable_if(a, ""))); // both-note 3{{declared here}}
+void PR27122_ext(void) {
+ regular_enable_if(0, 2); // both-error{{too many arguments}}
+ regular_enable_if(1, 2); // both-error{{too many arguments}}
+ regular_enable_if(); // both-error{{too few arguments}}
+}
+
+// We had a bug where we'd crash upon trying to evaluate varargs.
+void variadic_enable_if(int a, ...) __attribute__((enable_if(a, ""))); // both-note 6 {{disabled}}
+void variadic_test(void) {
+ variadic_enable_if(1);
+ variadic_enable_if(1, 2);
+ variadic_enable_if(1, "c", 3);
+
+ variadic_enable_if(0); // both-error{{no matching}}
+ variadic_enable_if(0, 2); // both-error{{no matching}}
+ variadic_enable_if(0, "c", 3); // both-error{{no matching}}
+
+ int m;
+ variadic_enable_if(1);
+ variadic_enable_if(1, m);
+ variadic_enable_if(1, m, "c");
+
+ variadic_enable_if(0); // both-error{{no matching}}
+ variadic_enable_if(0, m); // both-error{{no matching}}
+ variadic_enable_if(0, m, 3); // both-error{{no matching}}
+}
+#endif
diff --git a/clang/test/SemaCXX/enable_if-nested-call-with-valuedependent-param.cpp b/clang/test/SemaCXX/enable_if-nested-call-with-valuedependent-param.cpp
index 998f2ccf92534..31a49e9ae3d91 100644
--- a/clang/test/SemaCXX/enable_if-nested-call-with-valuedependent-param.cpp
+++ b/clang/test/SemaCXX/enable_if-nested-call-with-valuedependent-param.cpp
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only %s -std=c++14
+// RUN: %clang_cc1 -fsyntax-only %s -std=c++14 -fexperimental-new-constant-interpreter
// Checks that Clang doesn't crash/assert on the nested call to "kaboom"
// in "bar()".
diff --git a/clang/test/SemaCXX/enable_if.cpp b/clang/test/SemaCXX/enable_if.cpp
index 9b35bf2ac0c8d..a34b87064b49d 100644
--- a/clang/test/SemaCXX/enable_if.cpp
+++ b/clang/test/SemaCXX/enable_if.cpp
@@ -1,5 +1,7 @@
// RUN: %clang_cc1 -std=c++11 -verify %s
+// RUN: %clang_cc1 -std=c++11 -verify %s -fexperimental-new-constant-interpreter
// RUN: %clang_cc1 -std=c++2a -verify %s
+// RUN: %clang_cc1 -std=c++2a -verify %s -fexperimental-new-constant-interpreter
typedef int (*fp)(int);
int surrogate(int);
More information about the cfe-commits
mailing list