patch: new attribute enable_if
Aaron Ballman
aaron at aaronballman.com
Sun Sep 22 05:59:06 PDT 2013
Most of my comments are related to the attribute itself. I've got to
run, but can take another look tomorrow as well.
> Index: include/clang/AST/Expr.h
> ===================================================================
> --- include/clang/AST/Expr.h (revision 191171)
> +++ include/clang/AST/Expr.h (working copy)
> @@ -601,6 +601,14 @@
> const VarDecl *VD,
> SmallVectorImpl<PartialDiagnosticAt> &Notes) const;
>
> + /// EvaluateWithSubstitution - Evaluate an expression as if from the context
> + /// of a call to the given function with the given arguments. Returns true
> + /// if the expression could be folded to a constant, even if the evaluation
> + /// had side-effects or some subexpression could not be evaluated.
> + bool EvaluateWithSubstitution(APValue &Value, ASTContext &Ctx,
> + FunctionDecl *Callee,
> + llvm::ArrayRef<const Expr*> Args) const;
> +
> /// \brief Enumeration used to describe the kind of Null pointer constant
> /// returned from \c isNullPointerConstant().
> enum NullPointerConstantKind {
> Index: include/clang/Basic/Attr.td
> ===================================================================
> --- include/clang/Basic/Attr.td (revision 191171)
> +++ include/clang/Basic/Attr.td (working copy)
> @@ -360,6 +360,12 @@
> let Args = [IntArgument<"Priority", 1>];
> }
>
> +def EnableIf : InheritableAttr {
> + let Spellings = [GNU<"enable_if">];
Is this really a GNU attribute? If not, perhaps since this is related
to overloading, this could have a C++11 style clang:: attribute.
(Reading further, I see tests for C as well, so perhaps not.)
> + let Args = [ExprArgument<"Cond">, StringArgument<"Message">];
> + let TemplateDependent = 1;
Attribute is missing subject information.
> +}
> +
> def ExtVectorType : Attr {
> let Spellings = [GNU<"ext_vector_type">];
> let Args = [ExprArgument<"NumElements">];
> Index: include/clang/Basic/DiagnosticSemaKinds.td
> ===================================================================
> --- include/clang/Basic/DiagnosticSemaKinds.td (revision 191171)
> +++ include/clang/Basic/DiagnosticSemaKinds.td (working copy)
> @@ -2397,6 +2397,10 @@
> "previous overload of function is here">;
> def err_attribute_overloadable_no_prototype : Error<
> "'overloadable' function %0 must have a prototype">;
> +def err_attribute_enable_if_not_function : Error<
> + "'enable_if' attribute can only be applied to a function">;
Please use the existing diagnostic for this (err_attribute_wrong_decl_type)
> +def err_attribute_enable_if_not_string : Error<
> + "second argument to 'enable_if' attribute must be a string literal">;
And this one as well (err_attribute_argument_n_type)
> def warn_ns_attribute_wrong_return_type : Warning<
> "%0 attribute only applies to %select{functions|methods|properties}1 that "
> "return %select{an Objective-C object|a pointer|a non-retainable pointer}2">,
> @@ -2541,6 +2545,8 @@
> "candidate template ignored: substitution failure%0%1">;
> def note_ovl_candidate_disabled_by_enable_if : Note<
> "candidate template ignored: disabled by %0%1">;
> +def note_ovl_candidate_disabled_by_enable_if_attr : Note<
> + "candidate ignored: %0">;
> def note_ovl_candidate_failed_overload_resolution : Note<
> "candidate template ignored: couldn't resolve reference to overloaded "
> "function %0">;
> Index: include/clang/Parse/Parser.h
> ===================================================================
> --- include/clang/Parse/Parser.h (revision 191171)
> +++ include/clang/Parse/Parser.h (working copy)
> @@ -1887,7 +1887,7 @@
> if (Tok.is(tok::kw___attribute)) {
> ParsedAttributes attrs(AttrFactory);
> SourceLocation endLoc;
> - ParseGNUAttributes(attrs, &endLoc, LateAttrs);
> + ParseGNUAttributes(attrs, &endLoc, LateAttrs, &D);
> D.takeAttributes(attrs, endLoc);
> }
> }
> @@ -1899,14 +1899,16 @@
> }
> void ParseGNUAttributes(ParsedAttributes &attrs,
> SourceLocation *endLoc = 0,
> - LateParsedAttrList *LateAttrs = 0);
> + LateParsedAttrList *LateAttrs = 0,
> + Declarator *D = 0);
> void ParseGNUAttributeArgs(IdentifierInfo *AttrName,
> SourceLocation AttrNameLoc,
> ParsedAttributes &Attrs,
> SourceLocation *EndLoc,
> IdentifierInfo *ScopeName,
> SourceLocation ScopeLoc,
> - AttributeList::Syntax Syntax);
> + AttributeList::Syntax Syntax,
> + Declarator *D);
> IdentifierLoc *ParseIdentifierLoc();
>
> void MaybeParseCXX11Attributes(Declarator &D) {
> Index: include/clang/Sema/Overload.h
> ===================================================================
> --- include/clang/Sema/Overload.h (revision 191171)
> +++ include/clang/Sema/Overload.h (working copy)
> @@ -579,7 +579,11 @@
> /// (CUDA) This candidate was not viable because the callee
> /// was not accessible from the caller's target (i.e. host->device,
> /// global->host, device->host).
> - ovl_fail_bad_target
> + ovl_fail_bad_target,
> +
> + /// This candidate function was not viable because an enable_if
> + /// attribute disabled it.
> + ovl_fail_enable_if
> };
>
> /// OverloadCandidate - A single candidate in an overload set (C++ 13.3).
> Index: include/clang/Sema/Sema.h
> ===================================================================
> --- include/clang/Sema/Sema.h (revision 191171)
> +++ include/clang/Sema/Sema.h (working copy)
> @@ -101,6 +101,7 @@
> class DependentDiagnostic;
> class DesignatedInitExpr;
> class Designation;
> + class EnableIfAttr;
> class EnumConstantDecl;
> class Expr;
> class ExtVectorType;
> @@ -2180,6 +2181,11 @@
> // identified by the expression Expr
> void NoteAllOverloadCandidates(Expr* E, QualType DestType = QualType());
>
> + // Check the enable_if expressions on the given function. Returns one of the
> + // failing attributes, or NULL if they were all successful.
Commenting style (should be ///, using markup, etc).
> + EnableIfAttr *CheckEnableIf(FunctionDecl *Function, ArrayRef<Expr *> Args,
> + bool MissingImplicitThis = false);
> +
> // [PossiblyAFunctionType] --> [Return]
> // NonFunctionType --> NonFunctionType
> // R (A) --> R(A)
> Index: include/clang/Sema/TemplateDeduction.h
> ===================================================================
> --- include/clang/Sema/TemplateDeduction.h (revision 191171)
> +++ include/clang/Sema/TemplateDeduction.h (working copy)
> @@ -19,6 +19,7 @@
>
> namespace clang {
>
> +class EnableIfAttr;
> class TemplateArgumentList;
> class Sema;
>
> @@ -208,6 +209,10 @@
> /// if any.
> Expr *getExpr();
>
> + /// When FailureKind is ovl_fail_enable_if, the attribute which caused
> + /// this function to not be viable.
> + EnableIfAttr *EnableIfAttribute;
> +
> /// \brief Free any memory associated with this deduction failure.
> void Destroy();
> };
> Index: lib/AST/ExprConstant.cpp
> ===================================================================
> --- lib/AST/ExprConstant.cpp (revision 191171)
> +++ lib/AST/ExprConstant.cpp (working copy)
> @@ -459,15 +459,21 @@
>
> bool IntOverflowCheckMode;
>
> + /// Some expressions, such as __builtin_object_size(ptr) can be retried in
> + /// the optimizer if we don't solve them here. If set to true, always fold
> + /// immediately since the optimizer will not get a chance to look at it.
> + bool UnevaluatedContext;
> +
> EvalInfo(const ASTContext &C, Expr::EvalStatus &S,
> - bool OverflowCheckMode = false)
> + bool OverflowCheckMode = false, bool UnevaluatedContext = false)
> : Ctx(const_cast<ASTContext&>(C)), EvalStatus(S), CurrentCall(0),
> CallStackDepth(0), NextCallIndex(1),
> StepsLeft(getLangOpts().ConstexprStepLimit),
> BottomFrame(*this, SourceLocation(), 0, 0, 0),
> EvaluatingDecl((const ValueDecl*)0), EvaluatingDeclValue(0),
> HasActiveDiagnostic(false), CheckingPotentialConstantExpression(false),
> - IntOverflowCheckMode(OverflowCheckMode) {}
> + IntOverflowCheckMode(OverflowCheckMode),
> + UnevaluatedContext(UnevaluatedContext) {}
>
> void setEvaluatingDecl(APValue::LValueBase Base, APValue &Value) {
> EvaluatingDecl = Base;
> @@ -976,6 +982,7 @@
> static bool EvaluateInPlace(APValue &Result, EvalInfo &Info,
> const LValue &This, const Expr *E,
> bool AllowNonLiteralTypes = false);
> +static bool EvaluateAsRValue(EvalInfo &Info, const Expr *E, APValue &Result);
> static bool EvaluateLValue(const Expr *E, LValue &Result, EvalInfo &Info);
> static bool EvaluatePointer(const Expr *E, LValue &Result, EvalInfo &Info);
> static bool EvaluateMemberPointer(const Expr *E, MemberPtr &Result,
> @@ -1803,7 +1810,7 @@
> return false;
> }
> Result = &Frame->Arguments[PVD->getFunctionScopeIndex()];
> - return true;
> + return !Result->isUninit();
> }
>
> // If this is a local variable, dig out its value.
> @@ -5789,7 +5796,7 @@
>
> /// EvaluateBuiltinConstantP - Evaluate __builtin_constant_p as similarly to
> /// GCC as we can manage.
> -static bool EvaluateBuiltinConstantP(ASTContext &Ctx, const Expr *Arg) {
> +static bool EvaluateBuiltinConstantP(EvalInfo &Info, const Expr *Arg) {
> QualType ArgType = Arg->getType();
>
> // __builtin_constant_p always has one operand. The rules which gcc follows
> @@ -5803,26 +5810,35 @@
> //
> // Otherwise, it returns 0.
> //
> + // In the former case, when building with -O, GCC will sometimes return 1 if
> + // a constant numeric value is used as an argument to an inline function, and
> + // the corresponding parameter is passed to __builtin_constant_p. In the
> + // latter case, it never will. We pretend -O is always specified when checking
> + // constexpr function parameters.
> + //
> // FIXME: GCC also intends to return 1 for literals of aggregate types, but
> // its support for this does not currently work.
> if (ArgType->isIntegralOrEnumerationType()) {
> - Expr::EvalResult Result;
> - if (!Arg->EvaluateAsRValue(Result, Ctx) || Result.HasSideEffects)
> + llvm::SmallVector<PartialDiagnosticAt, 8> Diag;
> + SpeculativeEvaluationRAII Speculate(Info, &Diag);
> +
> + Info.EvalStatus.HasSideEffects = false;
> + APValue Result;
> + if (!EvaluateAsRValue(Info, Arg, Result) || Info.EvalStatus.HasSideEffects)
> return false;
>
> - APValue &V = Result.Val;
> - if (V.getKind() == APValue::Int)
> + if (Result.getKind() == APValue::Int)
> return true;
>
> - return EvaluateBuiltinConstantPForLValue(V);
> + return EvaluateBuiltinConstantPForLValue(Result);
> } else if (ArgType->isFloatingType() || ArgType->isAnyComplexType()) {
> - return Arg->isEvaluatable(Ctx);
> + return Arg->isEvaluatable(Info.Ctx);
> } else if (ArgType->isPointerType() || Arg->isGLValue()) {
> LValue LV;
> Expr::EvalStatus Status;
> - EvalInfo Info(Ctx, Status);
> - if ((Arg->isGLValue() ? EvaluateLValue(Arg, LV, Info)
> - : EvaluatePointer(Arg, LV, Info)) &&
> + EvalInfo InnerInfo(Info.Ctx, Status);
> + if ((Arg->isGLValue() ? EvaluateLValue(Arg, LV, InnerInfo)
> + : EvaluatePointer(Arg, LV, InnerInfo)) &&
> !Status.HasSideEffects)
> return EvaluateBuiltinConstantPForLValue(LV);
> }
> @@ -5898,7 +5914,7 @@
>
> // Expression had no side effects, but we couldn't statically determine the
> // size of the referenced object.
> - return Error(E);
> + return Info.UnevaluatedContext ? Success(-1ULL, E) : Error(E);
> }
>
> case Builtin::BI__builtin_bswap16:
> @@ -5931,7 +5947,7 @@
> }
>
> case Builtin::BI__builtin_constant_p:
> - return Success(EvaluateBuiltinConstantP(Info.Ctx, E->getArg(0)), E);
> + return Success(EvaluateBuiltinConstantP(Info, E->getArg(0)), E);
>
> case Builtin::BI__builtin_ctz:
> case Builtin::BI__builtin_ctzl:
> @@ -8519,6 +8535,25 @@
> return IsConstExpr;
> }
>
> +bool Expr::EvaluateWithSubstitution(APValue &Value, ASTContext &Ctx,
> + FunctionDecl *Callee,
> + llvm::ArrayRef<const Expr*> Args) const {
> + Expr::EvalStatus Status;
> + EvalInfo Info(Ctx, Status, false, true);
> +
> + ArgVector ArgValues(Args.size());
> + for (ArrayRef<const Expr*>::iterator I = Args.begin(), E = Args.end();
> + I != E; ++I)
> + if (!Evaluate(ArgValues[I - Args.begin()], Info, *I))
> + // If evaluation fails, throw away the argument entirely.
> + ArgValues[I - Args.begin()] = APValue();
> +
> + // Build fake call to Callee.
> + CallStackFrame Frame(Info, Callee->getLocation(), Callee, /*This*/0,
> + ArgValues.data());
> + return Evaluate(Value, Info, this);
> +}
> +
> bool Expr::isPotentialConstantExpr(const FunctionDecl *FD,
> SmallVectorImpl<
> PartialDiagnosticAt> &Diags) {
> Index: lib/Parse/ParseDecl.cpp
> ===================================================================
> --- lib/Parse/ParseDecl.cpp (revision 191171)
> +++ lib/Parse/ParseDecl.cpp (working copy)
> @@ -117,7 +117,8 @@
> /// We follow the C++ model, but don't allow junk after the identifier.
> void Parser::ParseGNUAttributes(ParsedAttributes &attrs,
> SourceLocation *endLoc,
> - LateParsedAttrList *LateAttrs) {
> + LateParsedAttrList *LateAttrs,
> + Declarator *D) {
> assert(Tok.is(tok::kw___attribute) && "Not a GNU attribute list!");
>
> while (Tok.is(tok::kw___attribute)) {
> @@ -164,7 +165,7 @@
> LA->Toks.push_back(Eof);
> } else {
> ParseGNUAttributeArgs(AttrName, AttrNameLoc, attrs, endLoc,
> - 0, SourceLocation(), AttributeList::AS_GNU);
> + 0, SourceLocation(), AttributeList::AS_GNU, D);
> }
> } else {
> attrs.addNew(AttrName, AttrNameLoc, 0, AttrNameLoc, 0, 0,
> @@ -206,7 +207,8 @@
> SourceLocation *EndLoc,
> IdentifierInfo *ScopeName,
> SourceLocation ScopeLoc,
> - AttributeList::Syntax Syntax) {
> + AttributeList::Syntax Syntax,
> + Declarator *D) {
>
> assert(Tok.is(tok::l_paren) && "Attribute arg list not starting with '('");
>
> @@ -226,6 +228,21 @@
> ParseTypeTagForDatatypeAttribute(*AttrName, AttrNameLoc, Attrs, EndLoc);
> return;
> }
> + // These may refer to the function arguments, but need to be parsed early to
> + // participate in determining whether it's a redeclaration.
> + llvm::OwningPtr<ParseScope> PrototypeScope;
> + if (AttrName->isStr("enable_if") && D && D->isFunctionDeclarator()) {
> + DeclaratorChunk::FunctionTypeInfo FTI = D->getFunctionTypeInfo();
> + PrototypeScope.reset(new ParseScope(this, Scope::FunctionPrototypeScope |
> + Scope::FunctionDeclarationScope |
> + Scope::DeclScope));
> + for (unsigned i = 0; i != FTI.NumArgs; ++i) {
> + ParmVarDecl *Param = cast<ParmVarDecl>(FTI.ArgInfo[i].Param);
> + getCurScope()->AddDecl(Param);
> + if (Param->getDeclName())
> + Actions.IdResolver.AddDecl(Param);
> + }
> + }
>
> ConsumeParen(); // ignore the left paren loc for now
>
> @@ -1085,7 +1102,7 @@
> Actions.ActOnReenterFunctionContext(Actions.CurScope, D);
>
> ParseGNUAttributeArgs(&LA.AttrName, LA.AttrNameLoc, Attrs, &endLoc,
> - 0, SourceLocation(), AttributeList::AS_GNU);
> + 0, SourceLocation(), AttributeList::AS_GNU, 0);
>
> if (HasFunScope) {
> Actions.ActOnExitFunctionContext();
> @@ -1098,7 +1115,7 @@
> // If there are multiple decls, then the decl cannot be within the
> // function scope.
> ParseGNUAttributeArgs(&LA.AttrName, LA.AttrNameLoc, Attrs, &endLoc,
> - 0, SourceLocation(), AttributeList::AS_GNU);
> + 0, SourceLocation(), AttributeList::AS_GNU, 0);
> }
> } else {
> Diag(Tok, diag::warn_attribute_no_decl) << LA.AttrName.getName();
> Index: lib/Parse/ParseDeclCXX.cpp
> ===================================================================
> --- lib/Parse/ParseDeclCXX.cpp (revision 191171)
> +++ lib/Parse/ParseDeclCXX.cpp (working copy)
> @@ -3228,7 +3228,7 @@
> if (Tok.is(tok::l_paren)) {
> if (ScopeName && ScopeName->getName() == "gnu") {
> ParseGNUAttributeArgs(AttrName, AttrLoc, attrs, endLoc,
> - ScopeName, ScopeLoc, AttributeList::AS_CXX11);
> + ScopeName, ScopeLoc, AttributeList::AS_CXX11, 0);
> AttrParsed = true;
> } else {
> if (StandardAttr)
> Index: lib/Parse/ParseExpr.cpp
> ===================================================================
> --- lib/Parse/ParseExpr.cpp (revision 191171)
> +++ lib/Parse/ParseExpr.cpp (working copy)
> @@ -816,7 +816,7 @@
> if (getLangOpts().ObjC1 &&
> ((Tok.is(tok::identifier) && !InMessageExpression) ||
> Tok.is(tok::code_completion))) {
> - const Token& Next = NextToken();
> + const Token &Next = NextToken();
> if (Tok.is(tok::code_completion) ||
> Next.is(tok::colon) || Next.is(tok::r_square))
> if (ParsedType Typ = Actions.getTypeName(II, ILoc, getCurScope()))
> Index: lib/Sema/SemaChecking.cpp
> ===================================================================
> --- lib/Sema/SemaChecking.cpp (revision 191171)
> +++ lib/Sema/SemaChecking.cpp (working copy)
> @@ -754,6 +754,22 @@
> CheckArgumentWithTypeTag(*i, Args.data());
> }
> }
> +
> + if (FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(FDecl)) {
> + if (FD->hasAttr<EnableIfAttr>()) {
> + ArrayRef<Expr *> NonConstArgs =
> + llvm::makeArrayRef<Expr*>(const_cast<Expr**>(Args.data()), Args.size());
This const_cast makes me a bit nervous; is there a way to avoid it?
> + if (EnableIfAttr *Attr = CheckEnableIf(FD, NonConstArgs, true)) {
> + Diag(Loc, IsMemberFunction ?
> + diag::err_ovl_no_viable_member_function_in_call :
> + diag::err_ovl_no_viable_function_in_call)
> + << FD->getDeclName() << Range;
> + Diag(FD->getLocation(),
> + diag::note_ovl_candidate_disabled_by_enable_if_attr)
> + << Attr->getCond()->getSourceRange() << Attr->getMessage();
> + }
> + }
> + }
> }
>
> /// CheckConstructorCall - Check a constructor call for correctness and safety
> Index: lib/Sema/SemaDeclAttr.cpp
> ===================================================================
> --- lib/Sema/SemaDeclAttr.cpp (revision 191171)
> +++ lib/Sema/SemaDeclAttr.cpp (working copy)
> @@ -982,6 +982,33 @@
> Attr.getAttributeSpellingListIndex()));
> }
>
> +static void handleEnableIfAttr(Sema &S, Decl *D, const AttributeList &Attr) {
> + if (!isa<FunctionDecl>(D) && !isa<FunctionTemplateDecl>(D)) {
> + S.Diag(Attr.getLoc(), diag::err_attribute_enable_if_not_function);
> + return;
> + }
Does this apply to function-like things as well (such as function
pointers, etc)?
> +
> + if (!checkAttributeNumArgs(S, Attr, 2))
> + return;
> +
> + Expr *Cond = Attr.getArgAsExpr(0);
> + Expr *Message = Attr.getArgAsExpr(1);
Attributes can take unresolved identifiers as well as expressions (for
the first argument position), so you should be prepared to handle a
case like that.
> +
> + StringLiteral *MsgStr = dyn_cast<StringLiteral>(Message);
> + if (!MsgStr || !MsgStr->isAscii()) {
> + S.Diag(Message->getExprLoc(), diag::err_attribute_enable_if_not_string);
> + return;
> + }
Please use checkStringLiteralArgument.
> +
> + ExprResult Converted = S.PerformContextuallyConvertToBool(Cond);
> + if (Converted.isInvalid())
> + return;
> +
> + D->addAttr(::new (S.Context) EnableIfAttr(Attr.getRange(), S.Context,
> + Converted.take(),
> + MsgStr->getString()));
> +}
> +
> static void handleConsumableAttr(Sema &S, Decl *D, const AttributeList &Attr) {
> ConsumableAttr::ConsumedState DefaultState;
>
> @@ -4518,6 +4545,7 @@
> handleAttrWithMessage<DeprecatedAttr>(S, D, Attr);
> break;
> case AttributeList::AT_Destructor: handleDestructorAttr (S, D, Attr); break;
> + case AttributeList::AT_EnableIf: handleEnableIfAttr (S, D, Attr); break;
> case AttributeList::AT_ExtVectorType:
> handleExtVectorTypeAttr(S, scope, D, Attr);
> break;
> Index: lib/Sema/SemaOverload.cpp
> ===================================================================
> --- lib/Sema/SemaOverload.cpp (revision 191171)
> +++ lib/Sema/SemaOverload.cpp (working copy)
> @@ -1007,8 +1007,8 @@
> isa<FunctionNoProtoType>(NewQType.getTypePtr()))
> return false;
>
> - const FunctionProtoType* OldType = cast<FunctionProtoType>(OldQType);
> - const FunctionProtoType* NewType = cast<FunctionProtoType>(NewQType);
> + const FunctionProtoType *OldType = cast<FunctionProtoType>(OldQType);
> + const FunctionProtoType *NewType = cast<FunctionProtoType>(NewQType);
>
> // The signature of a function includes the types of its
> // parameters (C++ 1.3.10), which includes the presence or absence
> @@ -1079,6 +1079,21 @@
> return true;
> }
>
> + // enable_if attributes are an order-sensitive part of the signature.
> + for (specific_attr_iterator<EnableIfAttr>
> + NewI = New->specific_attr_begin<EnableIfAttr>(),
> + NewE = New->specific_attr_end<EnableIfAttr>(),
> + OldI = Old->specific_attr_begin<EnableIfAttr>(),
> + OldE = Old->specific_attr_end<EnableIfAttr>();
> + NewI != NewE && OldI != OldE; ++NewI, ++OldI) {
> + if (NewI == NewE || OldI == OldE)
> + return true;
> + llvm::FoldingSetNodeID NewID, OldID;
> + NewI->getCond()->Profile(NewID, Context, true);
> + OldI->getCond()->Profile(OldID, Context, true);
> + return !(NewID == OldID);
> + }
> +
> // The signatures match; this is not an overload.
> return false;
> }
> @@ -5412,11 +5427,11 @@
> Sema::AddOverloadCandidate(FunctionDecl *Function,
> DeclAccessPair FoundDecl,
> ArrayRef<Expr *> Args,
> - OverloadCandidateSet& CandidateSet,
> + OverloadCandidateSet &CandidateSet,
> bool SuppressUserConversions,
> bool PartialOverloading,
> bool AllowExplicit) {
> - const FunctionProtoType* Proto
> + const FunctionProtoType *Proto
> = dyn_cast<FunctionProtoType>(Function->getType()->getAs<FunctionType>());
> assert(Proto && "Functions without a prototype cannot be overloaded");
> assert(!Function->getDescribedFunctionTemplate() &&
> @@ -5520,7 +5535,7 @@
> if (Candidate.Conversions[ArgIdx].isBad()) {
> Candidate.Viable = false;
> Candidate.FailureKind = ovl_fail_bad_conversion;
> - break;
> + return;
> }
> } else {
> // (C++ 13.3.2p2): For the purposes of overload resolution, any
> @@ -5529,8 +5544,66 @@
> Candidate.Conversions[ArgIdx].setEllipsis();
> }
> }
> +
> + if (EnableIfAttr *FailedAttr = CheckEnableIf(Function, Args)) {
> + Candidate.Viable = false;
> + Candidate.FailureKind = ovl_fail_enable_if;
> + Candidate.DeductionFailure.EnableIfAttribute = FailedAttr;
> + }
> }
>
> +EnableIfAttr *Sema::CheckEnableIf(FunctionDecl *Function, ArrayRef<Expr *> Args,
> + bool MissingImplicitThis) {
> +
> + for (specific_attr_iterator<EnableIfAttr>
> + I = Function->specific_attr_begin<EnableIfAttr>(),
> + E = Function->specific_attr_end<EnableIfAttr>(); I != E; ++I) {
> + APValue Result;
> +
> + SFINAETrap Trap(*this);
> +
> + // Convert the arguments.
> + SmallVector<Expr *, 16> Params;
> + bool InitializationFailed = false;
> + for (unsigned i = 0, e = Args.size(); i != e; ++i) {
> + if (i == 0 && !MissingImplicitThis && isa<CXXMethodDecl>(Function) &&
> + !cast<CXXMethodDecl>(Function)->isStatic()) {
> + CXXMethodDecl *Method = cast<CXXMethodDecl>(Function);
> + ExprResult R =
> + PerformObjectArgumentInitialization(Args[0], /*Qualifier=*/0,
> + Method, Method);
> + if (R.isInvalid()) {
> + InitializationFailed = true;
> + break;
> + }
> + Params.push_back(R.take());
> + } else {
> + ExprResult R =
> + PerformCopyInitialization(InitializedEntity::InitializeParameter(
> + Context,
> + Function->getParamDecl(i)),
> + SourceLocation(),
> + Args[i]);
> + if (R.isInvalid()) {
> + InitializationFailed = true;
> + break;
> + }
> + Params.push_back(R.take());
> + }
> + }
> +
> + if (InitializationFailed || Trap.hasErrorOccurred() ||
> + !(*I)->getCond()->EvaluateWithSubstitution(
> + Result, Context, Function,
> + llvm::ArrayRef<const Expr*>(Params.data(), Params.size())) ||
> + !Result.isInt() || !Result.getInt().getBoolValue()) {
> + // FIXME: Produce a different error if EvaluateWithSubstitution failed.
> + return *I;
> + }
> + }
> + return 0;
> +}
> +
> /// \brief Add all of the function declarations in the given function set to
> /// the overload candidate set.
> void Sema::AddFunctionCandidates(const UnresolvedSetImpl &Fns,
> @@ -5610,9 +5683,9 @@
> CXXRecordDecl *ActingContext, QualType ObjectType,
> Expr::Classification ObjectClassification,
> ArrayRef<Expr *> Args,
> - OverloadCandidateSet& CandidateSet,
> + OverloadCandidateSet &CandidateSet,
> bool SuppressUserConversions) {
> - const FunctionProtoType* Proto
> + const FunctionProtoType *Proto
> = dyn_cast<FunctionProtoType>(Method->getType()->getAs<FunctionType>());
> assert(Proto && "Methods without a prototype cannot be overloaded");
> assert(!isa<CXXConstructorDecl>(Method) &&
> @@ -5656,6 +5729,13 @@
> return;
> }
>
> + if (EnableIfAttr *FailedAttr = CheckEnableIf(Method, Args, true)) {
> + Candidate.Viable = false;
> + Candidate.FailureKind = ovl_fail_enable_if;
> + Candidate.DeductionFailure.EnableIfAttribute = FailedAttr;
> + return;
> + }
> +
> Candidate.Viable = true;
>
> if (Method->isStatic() || ObjectType.isNull())
> @@ -8722,6 +8802,15 @@
> << (unsigned) FnKind << CalleeTarget << CallerTarget;
> }
>
> +void DiagnoseFailedEnableIfAttr(Sema &S, OverloadCandidate *Cand) {
> + FunctionDecl *Callee = Cand->Function;
> + EnableIfAttr *Attr = Cand->DeductionFailure.EnableIfAttribute;
> +
> + S.Diag(Callee->getLocation(),
> + diag::note_ovl_candidate_disabled_by_enable_if_attr)
> + << Attr->getCond()->getSourceRange() << Attr->getMessage();
> +}
> +
> /// Generates a 'note' diagnostic for an overload candidate. We've
> /// already generated a primary error at the call site.
> ///
> @@ -8785,6 +8874,9 @@
>
> case ovl_fail_bad_target:
> return DiagnoseBadTarget(S, Cand);
> +
> + case ovl_fail_enable_if:
> + return DiagnoseFailedEnableIfAttr(S, Cand);
> }
> }
>
> @@ -11007,7 +11099,7 @@
> << qualsString
> << (qualsString.find(' ') == std::string::npos ? 1 : 2);
> }
> -
> +
> CXXMemberCallExpr *call
> = new (Context) CXXMemberCallExpr(Context, MemExprE, Args,
> resultType, valueKind, RParenLoc);
> Index: test/Sema/enable_if.c
> ===================================================================
> --- test/Sema/enable_if.c (revision 0)
> +++ test/Sema/enable_if.c (working copy)
> @@ -0,0 +1,70 @@
> +// RUN: %clang_cc1 %s -verify -Wno-gcc-compat
> +// RUN: %clang_cc1 %s -DCODEGEN -emit-llvm -o - -Wno-gcc-compat | FileCheck %s
> +
> +#define O_CREAT 0x100
> +typedef int mode_t;
> +typedef unsigned long size_t;
> +
> +int open(const char *pathname, int flags) __attribute__((enable_if(!__builtin_constant_p(flags) || !(flags & O_CREAT), "must specify mode when using O_CREAT"))) __attribute__((overloadable)); // expected-note{{candidate ignored: must specify mode when using O_CREAT}}
> +int open(const char *pathname, int flags, mode_t mode) __attribute__((overloadable)); // expected-note{{candidate function not viable: requires 3 arguments, but 2 were provided}}
> +
> +void test1() {
> +#ifndef CODEGEN
> + open("path", O_CREAT); // expected-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) // expected-note{{candidate ignored: chosen when target buffer size is unknown}}
> + __attribute__((overloadable))
> + __attribute__((enable_if(__builtin_object_size(s, 0) == -1,
> + "chosen when target buffer size is unknown")))
> + __asm__("__strnlen_real1");
> +size_t strnlen(const char *s, size_t maxlen) // expected-note{{candidate ignored: chosen when maxlen is known to be less than or equal to the buffer size}}
> + __attribute__((overloadable))
> + __attribute__((enable_if(__builtin_object_size(s, 0) != -1 && __builtin_constant_p(maxlen) && 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) // expected-note{{candidate function has been explicitly made unavailable}}
> + __attribute__((overloadable))
> + __attribute__((enable_if(__builtin_object_size(s, 0) != -1 && __builtin_constant_p(maxlen) && maxlen > __builtin_object_size(s, 0),
> + "chosen when maxlen is larger than the buffer size")))
> + __attribute__((unavailable("maxlen is larger than the buffer size")));
> +
> +static inline size_t strnlen(const char *s, size_t maxlen) // expected-note{{candidate ignored: chosen when the buffer size is known but maxlen is not}}
> + __attribute__((always_inline))
> + __attribute__((overloadable))
> + __attribute__((enable_if(__builtin_object_size(s, 0) != -1 && !__builtin_constant_p(maxlen),
> + "chosen when the buffer size is known but maxlen is not")))
> +{
> + return __strnlen_chk(s, maxlen, __builtin_object_size(s, 0));
> +}
> +
> +void test2(const char *s, int i) {
> +// CHECK: define void @test2
> + const char c[123];
> + 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); // expected-error{{call to unavailable function 'strnlen': maxlen is larger than the buffer size}}
> +#endif
> +}
> +
> +int isdigit(int c) __attribute__((enable_if( !__builtin_constant_p(c) || (c >= -1 && c <= 255) , "'c' must have the value of an unsigned char or EOF"))); // expected-note{{candidate ignored: 'c' must have the value of an unsigned char}}
> +
> +void test3() {
> + isdigit(10);
> +#ifndef CODEGEN
> + isdigit(-10); // expected-error{{no matching function for call to isdigit}}
> +#endif
> +}
> Index: test/SemaCXX/enable_if.cpp
> ===================================================================
> --- test/SemaCXX/enable_if.cpp (revision 0)
> +++ test/SemaCXX/enable_if.cpp (working copy)
> @@ -0,0 +1,35 @@
> +// RUN: %clang_cc1 -Wno-gcc-compat -verify %s
> +
> +struct X {
> + void f(int n) __attribute__((enable_if(n == 0, "chosen when n is zero")));
> + void f(int n) __attribute__((enable_if(n == 1, "chosen when n is one"))); // expected-note{{member declaration nearly matches}} expected-note{{candidate ignored: chosen when n is one}}
> +
> + static void s(int n) __attribute__((enable_if(n == 0, "chosen when n is zero"))); // expected-note2{{candidate ignored: chosen when n is zero}}
> +
> + void conflict(int n) __attribute__((enable_if(n+n == 10, "chosen when n is five"))); // expected-note{{candidate function}}
> + void conflict(int n) __attribute__((enable_if(n*2 == 10, "chosen when n is five"))); // expected-note{{candidate function}}
> +};
> +
> +void X::f(int n) __attribute__((enable_if(n == 0, "chosen when n is zero"))) // expected-note{{member declaration nearly matches}} expected-note{{candidate ignored: chosen when n is zero}}
> +{
> +}
> +
> +void X::f(int n) __attribute__((enable_if(n == 2, "chosen when n is two"))) // expected-error{{out-of-line definition of 'f' does not match any declaration in 'X'}} expected-note{{candidate ignored: chosen when n is two}}
> +{
> +}
> +
> +void test() {
> + X x;
> + x.f(0);
> + x.f(1);
> + x.f(2); // no error, suppressed by erroneous out-of-line definition
> + x.f(3); // expected-error{{no matching member function for call to 'f'}}
> +
> + x.s(0);
> + x.s(1); // expected-error{{no matching function for call to 's'}}
> +
> + X::s(0);
> + X::s(1); // expected-error{{no matching function for call to 's'}}
> +
> + x.conflict(5); // expected-error{{call to member function 'conflict' is ambiguous}}
> +}
>
Missing test cases for everything in SemaDeclAttr.cpp. Be sure to hit
edge cases as well (such as unresolved identifier as the expression).
Thanks!
~Aaron
On Sun, Sep 22, 2013 at 6:44 AM, Nick Lewycky <nicholas at mxc.ca> wrote:
> Nick Lewycky wrote:
>>
>> The attached patch adds a new attribute named "enable_if" which takes an
>> expression and a string. At the call site, the arguments are substituted
>> into the enable_if expression (which is written in terms of the
>> parameters) to determine whether this function is a viable candidate.
>>
>> Please review!
>
>
> I forgot to mention. This patch implements the parse+sema, but there will
> need to be a follow-up patch to make this useful in C++ mode (without using
> __asm__). We'll need to mangle the enable_if expression into the name, but I
> don't have a good mangling for that. The ABI does provide ways to encode
> vendor extensions, but there's no way to indicate that the vendor-extension
> should be demangled as an expression. It's a problem for a future patch.
>
> Nick
> _______________________________________________
> cfe-commits mailing list
> cfe-commits at cs.uiuc.edu
> http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits
More information about the cfe-commits
mailing list