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