On Fri, Jan 10, 2014 at 3:43 PM, Nick Lewycky <[email protected]> wrote: > Aaron Ballman wrote: >> >> This is fantastic, thank you for taking this on! Some minor comments >> and observations below. > > > Thank you for the thorough review! I haven't been able to fix everything you > pointed out, but I'm attaching an updated patch anyways, with FIXME's in it. > > The two issues unfixed are: > - Why does C++11 syntax think there's only one argument? > - Why is potential-constexpr evaluation failing on VarTemplateDecls? > >>> Index: docs/LanguageExtensions.rst >>> =================================================================== >>> --- docs/LanguageExtensions.rst (revision 198923) >>> +++ docs/LanguageExtensions.rst (working copy) >>> @@ -1316,6 +1316,8 @@ >>> Query the presence of this new mangling with >>> ``__has_feature(objc_protocol_qualifier_mangling)``. >>> >>> +.. _langext-overloading: >>> + >>> Function Overloading in C >>> ========================= >>> >>> @@ -1398,6 +1400,84 @@ >>> >>> Query for this feature with >>> ``__has_extension(attribute_overloadable)``. >>> >>> +Controlling Overload Resolution >>> +=============================== >>> + >>> +Clang introduces the ``enable_if`` attribute, which can be placed on >>> function >>> +declarations to control which overload is selected based on the values >>> of the >>> +function's arguments. When combined with the >>> +:ref:``overloadable<langext-overloading>`` attribute, this feature is >>> also >>> +available in C. >>> + >>> +.. code-block:: c++ >>> + >>> + int isdigit(int c); >>> + int isdigit(int c) __attribute__((enable_if(c>= -1&& c<= 255, "'c' >>> must have the value of an unsigned char or EOF"))); >>> >>> + >>> + void foo(char c) { >>> + isdigit(c); >>> + isdigit(10); >>> + isdigit(-10); // results in a compile-time error. >>> + } >>> + >>> +The enable_if attribute takes two arguments, the first is an expression >>> written >>> +in terms of the function parameters, the second is a string explaining >>> why this >>> +overload candidate could not be selected to be displayed in diagnostics. >>> The >>> +expression is part of the function signature for the purposes of >>> determining >>> +whether it is a redeclaration (following the rules used when determining >>> +whether a C++ template specialization is ODR-equivalent), but is not >>> part of >>> +the type. >>> + >>> +An enable_if expression will be evaluated by substituting the values of >>> the >>> +parameters from the call site into the arguments in the expression and >>> +determining whether the result is true. If the result is false or could >>> not be >>> +determined through constant expression evaluation, then this overload >>> will not >>> +be chosen and the reason supplied in the string will be given to the >>> user if >>> +their code does not compile as a result. >>> + >>> +Because the enable_if expression is an unevaluated context, there are no >>> global >>> +state changes, nor the ability to pass information from the enable_if >>> +expression to the function body. As a worked example, suppose we want >>> calls to >> >> >> "As a worked example" reads a bit funny to me, is it a typo? > > > Hm, it's a phrase I use to mean "an example that's been worked out to show > the reader how to do it" but that doesn't appear to be common usage. Fixed > to simply use "For example". > > >>> +strnlen(strbuf, maxlen) to resolve to strnlen_chk(strbuf, maxlen, size >>> of >>> +strbuf) only if the size of strbuf can be determined: >>> + >>> +.. code-block:: c++ >>> + >>> + __attribute__((always_inline)) >>> + static inline size_t strnlen(const char *s, size_t maxlen) >>> + __attribute__((overloadable)) >>> + __attribute__((enable_if(__builtin_object_size(s, 0) != -1))), >>> + "chosen when the buffer size is known but >>> 'maxlen' is not"))) >>> + { >>> + return strnlen_chk(s, maxlen, __builtin_object_size(s, 0)); >>> + } >>> + >>> +Multiple enable_if attributes may be applied to a single declaration. In >>> this >>> +case, the enable_if expressions are evaluated from left to right in the >>> +following manner. First the candidates whose enable_if expressions >>> evaluate to >> >> >> "First" should be followed by a comma. Wow, that's nitpicky. ;-) > > > Done. :) > >>> +false or cannot be evaluated are discarded. If the remaining candidates >>> do not >>> +share ODR-equivalent enable_if expressions, the overload resolution is >>> +ambiguous. Otherwise, enable_if overload resolution continues with the >>> next >>> +enable_if attribute on the candidates that have not been discarded and >>> have >>> +remaining enable_if attributes. In this way, we pick the most specific >>> +overload out of a number of viable overloads using enable_if. >>> + >>> +.. code-block:: c++ >>> + void f() __attribute__((enable_if(true, ""))); // #1 >>> + void f() __attribute__((enable_if(true, ""))) >>> __attribute__((enable_if(true, ""))); // #2 >>> + >>> + void g(int i, int j) __attribute__((enable_if(i, ""))); // #1 >>> + void g(int i, int j) __attribute__((enable_if(j, ""))) >>> __attribute__((enable_if(true))); // #2 >>> + >>> +In this example, a call to f() is always resolved to #2, as the first >>> enable_if >>> +expression is ODR-equivalent for both declarations, but #1 does not have >>> another >>> +enable_if expression to continue evaluating, so the next round of >>> evaluation has >>> +only a single candidate. In a call to g(1, 1), the call is ambiguous >>> even though >>> +#2 has more enable_if attributes, because the first enable_if >>> expressions are >>> +not ODR-equivalent. >>> + >>> +Query for this feature with ``__has_attribute(enable_if)``. >>> + >>> Initializer lists for complex numbers in C >>> ========================================== >>> >>> Index: include/clang/AST/Expr.h >>> =================================================================== >>> --- include/clang/AST/Expr.h (revision 198923) >>> +++ include/clang/AST/Expr.h (working copy) >>> @@ -508,6 +508,16 @@ >>> SmallVectorImpl< >>> PartialDiagnosticAt> &Diags); >>> >>> + /// isPotentialConstantExprUnevaluted - Return true if this expression >>> might >>> + /// be usable in a constant expression in C++11 in an unevaluated >>> context, if >>> + /// it were in function FD marked constexpr. Return false if the >>> function can >>> + /// never produce a constant expression, along with diagnostics >>> describing >>> + /// why not. >>> + static bool isPotentialConstantExprUnevaluated(Expr *E, >>> + const FunctionDecl *FD, >>> + SmallVectorImpl< >>> + PartialDiagnosticAt> >>> &Diags); >>> + >>> /// isConstantInitializer - Returns true if this expression can be >>> emitted to >>> /// IR as a constant, and thus can be used as a constant initializer >>> in C. >>> bool isConstantInitializer(ASTContext&Ctx, bool ForRef) const; >>> >>> @@ -600,6 +610,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, inside >>> an >>> + /// unevaluated context. Returns true if the expression could be >>> folded to a >>> + /// constant. >>> + 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 198923) >>> +++ include/clang/Basic/Attr.td (working copy) >>> @@ -470,6 +470,13 @@ >>> let Subjects = SubjectList<[Function]>; >>> } >>> >>> +def EnableIf : InheritableAttr { >>> + let Spellings = [GNU<"enable_if">]; >> >> >> Since this is also supported in C++, could we have a CXX11<"clang", >> "enable_if"> spelling as well? > > > I tried adding that, but it doesn't seem work out of the box: > > void f(int n) [[clang::enable_if((n > 5), "chosen when 'n' is greater than > five")]]; > > produces "error: expected expression" while: > > [[clang::enable_if((n > 5), "chosen when 'n' is greater than five")]] void > f(int n); > > produces "error: 'enable_if' attribute requires exactly 2 arguments"! > > That's going to take some debugging. I've added a testcase for this > commented out with a FIXME on it.
I can save you the debugging -- I totally forgot that C++11 attribute parsing doesn't support arguments. I'll put that on my list of things to look into sometime in the near-ish future though. Thanks for the reminder! :-) > > >>> + let Subjects = SubjectList<[Function]>; >> >> >> Do you want this to apply to FunctionTemplate and perhaps ObjCMethod as >> well? > > > To quote Richard Smith, "this [attribute] should apply to the FunctionDecl > within a FunctionTemplateDecl, not to the template itself, right?" - > http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20130923/089332.html Hmmm, I may have to educate myself on this point a bit more then. > > I'm not sure about ObjCMethod. I'm leaning towards not supporting it simply > because I don't know enough objc to write any testcases. I don't have any > ideological opposition here. > > >>> + let Args = [ExprArgument<"Cond">, StringArgument<"Message">]; >>> + let TemplateDependent = 1; >> >> >> You *may* have to set LateParsed = 1 here as well, as the expressions >> are template dependant. I'm not 100% certain though. > > > Uh oh. I musn't set LateParsed = 1 here, because we need to parse it before > we can decide whether it's a redeclaration. Late parsed is too late. > > Unfortunately, enable_if on a templated function looks pretty broken, see my > comments at the end. At the moment, I don't think that LateParsed = 0 is the > problem, but until I finish debugging it I can't be sure. > > >>> +} >>> + >>> def ExtVectorType : Attr { >>> let Spellings = [GNU<"ext_vector_type">]; >>> let Subjects = SubjectList<[TypedefName], ErrorDiag>; >>> Index: include/clang/Basic/DiagnosticSemaKinds.td >>> =================================================================== >>> --- include/clang/Basic/DiagnosticSemaKinds.td (revision 198923) >>> +++ include/clang/Basic/DiagnosticSemaKinds.td (working copy) >>> @@ -1739,6 +1739,8 @@ >>> def ext_constexpr_function_never_constant_expr : ExtWarn< >>> "constexpr %select{function|constructor}0 never produces a" >>> "constant expression">, InGroup<DiagGroup<"invalid-constexpr">>, >>> DefaultError; >>> +def err_enable_if_never_constant_expr : Error< >>> + "enable_if attribute expression never produces a constant >>> expression">; >> >> >> I'd prefer the diagnostic to have enable_if in single quotes (it's >> more consistent with other attribute diagnostics then). > > > Done. > > >>> def err_constexpr_body_no_return : Error< >>> "no return statement in constexpr function">; >>> def warn_cxx11_compat_constexpr_body_no_return : Warning< >>> @@ -2588,6 +2590,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 disabled: %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 198923) >>> +++ include/clang/Parse/Parser.h (working copy) >>> @@ -1972,7 +1972,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); >>> } >>> } >>> @@ -1984,14 +1984,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 198923) >>> +++ 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 198923) >>> +++ 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; >>> @@ -2200,6 +2201,11 @@ >>> // identified by the expression Expr >>> void NoteAllOverloadCandidates(Expr* E, QualType DestType = >>> QualType()); >>> >>> + /// Check the enable_if expressions on the given function. Returns the >>> first >>> + /// failing attribute, or NULL if they were all successful. >>> + EnableIfAttr *CheckEnableIf(FunctionDecl *Function, ArrayRef<Expr *> >>> Args, >>> + bool MissingImplicitThis = false); >>> + >> >> >> A bit of const love here would not make me sad, but isn't strictly >> required. > > > I tried making using ArrayRef<const Expr *> here but ended up pulling a very > long string (or else using bad const_casts). It's not worth it. I was thinking more for the returned Attrm the given FunctionDecl and the CheckEnableIf function itself (if possible). > >>> // [PossiblyAFunctionType] --> [Return] >>> // NonFunctionType --> NonFunctionType >>> // R (A) --> R(A) >>> @@ -4776,6 +4782,7 @@ >>> AttributeList *AttrList); >>> void ActOnFinishCXXMemberDecls(); >>> >>> + void ActOnReenterCXXMethodParameter(Scope *S, ParmVarDecl *Param); >>> void ActOnReenterTemplateScope(Scope *S, Decl *Template); >>> void ActOnReenterDeclaratorTemplateScope(Scope *S, DeclaratorDecl >>> *D); >>> void ActOnStartDelayedMemberDeclarations(Scope *S, Decl *Record); >>> Index: lib/AST/ExprConstant.cpp >>> =================================================================== >>> --- lib/AST/ExprConstant.cpp (revision 198923) >>> +++ lib/AST/ExprConstant.cpp (working copy) >>> @@ -474,13 +474,30 @@ >>> >>> /// Evaluate in any way we know how. Don't worry about >>> side-effects that >>> /// can't be modeled. >>> - EM_IgnoreSideEffects >>> + EM_IgnoreSideEffects, >>> + >>> + /// Evaluate as a constant expression. Stop if we find that the >>> expression >>> + /// is not a constant expression. Some expressions can be retried >>> in the >>> + /// optimizer if we don't constant fold them here, but in an >>> unevaluated >>> + /// context we try to fold them immediately since the optimizer >>> never >>> + /// gets a chance to look at it. >>> + EM_ConstantExpressionUnevaluated, >>> + >>> + /// Evaluate as a potential constant expression. Keep going if we >>> hit a >>> + /// construct that we can't evaluate yet (because we don't yet >>> know the >>> + /// value of something) but stop if we hit something that could >>> never be >>> + /// a constant expression. Some expressions can be retried in the >>> + /// optimizer if we don't constant fold them here, but in an >>> unevaluated >>> + /// context we try to fold them immediately since the optimizer >>> never >>> + /// gets a chance to look at it. >>> + EM_PotentialConstantExpressionUnevaluated >>> } EvalMode; >>> >>> /// Are we checking whether the expression is a potential constant >>> /// expression? >>> bool checkingPotentialConstantExpression() const { >>> - return EvalMode == EM_PotentialConstantExpression; >>> + return EvalMode == EM_PotentialConstantExpression || >>> + EvalMode == EM_PotentialConstantExpressionUnevaluated; >>> } >>> >>> /// Are we checking an expression for overflow? >>> @@ -573,6 +590,8 @@ >>> // some later problem. >>> case EM_ConstantExpression: >>> case EM_PotentialConstantExpression: >>> + case EM_ConstantExpressionUnevaluated: >>> + case EM_PotentialConstantExpressionUnevaluated: >>> HasActiveDiagnostic = false; >>> return OptionalDiagnostic(); >>> } >>> @@ -644,11 +663,13 @@ >>> bool keepEvaluatingAfterSideEffect() { >>> switch (EvalMode) { >>> case EM_PotentialConstantExpression: >>> + case EM_PotentialConstantExpressionUnevaluated: >>> case EM_EvaluateForOverflow: >>> case EM_IgnoreSideEffects: >>> return true; >>> >>> case EM_ConstantExpression: >>> + case EM_ConstantExpressionUnevaluated: >>> case EM_ConstantFold: >>> return false; >>> } >>> @@ -670,10 +691,12 @@ >>> >>> switch (EvalMode) { >>> case EM_PotentialConstantExpression: >>> + case EM_PotentialConstantExpressionUnevaluated: >>> case EM_EvaluateForOverflow: >>> return true; >>> >>> case EM_ConstantExpression: >>> + case EM_ConstantExpressionUnevaluated: >>> case EM_ConstantFold: >>> case EM_IgnoreSideEffects: >>> return false; >>> @@ -696,7 +719,9 @@ >>> Info.EvalStatus.Diag->empty()&& >>> !Info.EvalStatus.HasSideEffects), >>> OldMode(Info.EvalMode) { >>> - if (Enabled&& Info.EvalMode == EvalInfo::EM_ConstantExpression) >>> >>> + if (Enabled&& >>> + (Info.EvalMode == EvalInfo::EM_ConstantExpression || >>> + Info.EvalMode == EvalInfo::EM_ConstantExpressionUnevaluated)) >>> Info.EvalMode = EvalInfo::EM_ConstantFold; >>> } >>> void keepDiagnostics() { Enabled = false; } >>> @@ -5985,7 +6010,17 @@ >>> >>> // Expression had no side effects, but we couldn't statically >>> determine the >>> // size of the referenced object. >>> - return Error(E); >>> + switch (Info.EvalMode) { >>> + case EvalInfo::EM_ConstantExpression: >>> + case EvalInfo::EM_PotentialConstantExpression: >>> + case EvalInfo::EM_ConstantFold: >>> + case EvalInfo::EM_EvaluateForOverflow: >>> + case EvalInfo::EM_IgnoreSideEffects: >>> + return Error(E); >>> + case EvalInfo::EM_ConstantExpressionUnevaluated: >>> + case EvalInfo::EM_PotentialConstantExpressionUnevaluated: >>> + return Success(-1ULL, E); >> >> >> std::numeric_limits<unsigned long long>::max()? Not heavily tied to it. > > > The other two callers in the file also use "Success(-1ULL,". Let me know if > you want it anyways and I'll update them too. I don't have a strong opinion one way or the other, but I do find our usages of -1ULL a bit contradictory when I come across them. It might be done better as a style discussion of its own, instead of part of this patch, so I guess I'd say leave it as-is for now. > >>> + } >>> } >>> >>> case Builtin::BI__builtin_bswap16: >>> @@ -8656,6 +8691,28 @@ >>> return IsConstExpr; >>> } >>> >>> +bool Expr::EvaluateWithSubstitution(APValue&Value, ASTContext&Ctx, >>> + FunctionDecl *Callee, >>> + llvm::ArrayRef<const Expr*> Args) >>> const { >>> + Expr::EvalStatus Status; >>> + EvalInfo Info(Ctx, Status, >>> EvalInfo::EM_ConstantExpressionUnevaluated); >>> + >>> + 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(); >>> + if (Info.EvalStatus.HasSideEffects) >>> + return false; >>> + } >>> + >>> + // Build fake call to Callee. >>> + CallStackFrame Frame(Info, Callee->getLocation(), Callee, /*This*/0, >>> + ArgValues.data()); >>> + return Evaluate(Value, Info, this)&& !Info.EvalStatus.HasSideEffects; >>> >>> +} >>> + >>> bool Expr::isPotentialConstantExpr(const FunctionDecl *FD, >>> SmallVectorImpl< >>> PartialDiagnosticAt> &Diags) { >>> @@ -8696,3 +8753,26 @@ >>> >>> return Diags.empty(); >>> } >>> + >>> +bool Expr::isPotentialConstantExprUnevaluated(Expr *E, >>> + const FunctionDecl *FD, >>> + SmallVectorImpl< >>> + PartialDiagnosticAt> >>> &Diags) { >>> + Expr::EvalStatus Status; >>> + Status.Diag =&Diags; >>> + >>> + EvalInfo Info(FD->getASTContext(), Status, >>> + EvalInfo::EM_PotentialConstantExpressionUnevaluated); >>> + >>> + // Fabricate a call stack frame to give the arguments a plausible >>> cover story. >>> + ArrayRef<const Expr*> Args; >>> + ArgVector ArgValues(0); >>> + bool Success = EvaluateArgs(Args, ArgValues, Info); >>> + assert(Success&& >>> + "Failed to set up arguments for potential constant >>> evaluation"); >>> + CallStackFrame Frame(Info, SourceLocation(), FD, 0, ArgValues.data()); >>> + >>> + APValue ResultScratch; >>> + Evaluate(ResultScratch, Info, E); >>> + return Diags.empty(); >>> +} >>> Index: lib/Parse/ParseDecl.cpp >>> =================================================================== >>> --- lib/Parse/ParseDecl.cpp (revision 198923) >>> +++ 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)) { >>> @@ -153,7 +154,7 @@ >>> // Handle "parameterized" attributes >>> if (!LateAttrs || !isAttributeLateParsed(*AttrName)) { >>> ParseGNUAttributeArgs(AttrName, AttrNameLoc, attrs, endLoc, 0, >>> - SourceLocation(), AttributeList::AS_GNU); >>> + SourceLocation(), AttributeList::AS_GNU, >>> D); >>> continue; >>> } >>> >>> @@ -258,7 +259,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 >>> '('"); >>> >>> >>> @@ -278,17 +280,33 @@ >>> return; >>> } >>> >>> + >> >> >> Extra newline snuck in here. > > > Fixed. > >>> // Type safety attributes have their own grammar. >>> if (AttrKind == AttributeList::AT_TypeTagForDatatype) { >>> ParseTypeTagForDatatypeAttribute(*AttrName, AttrNameLoc, Attrs, >>> EndLoc); >>> return; >>> } >>> + >>> // Some attributes expect solely a type parameter. >>> if (attributeIsTypeArgAttr(*AttrName)) { >>> ParseAttributeWithTypeArg(*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); >>> + Actions.ActOnReenterCXXMethodParameter(getCurScope(), Param); >>> + } >>> + } >>> + >>> // Ignore the left paren location for now. >>> ConsumeParen(); >>> >>> @@ -1164,7 +1182,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(); >>> @@ -1177,7 +1195,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 198923) >>> +++ lib/Parse/ParseDeclCXX.cpp (working copy) >>> @@ -3259,7 +3259,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/Sema/SemaDeclAttr.cpp >>> =================================================================== >>> --- lib/Sema/SemaDeclAttr.cpp (revision 198923) >>> +++ lib/Sema/SemaDeclAttr.cpp (working copy) >>> @@ -827,6 +827,30 @@ >>> Attr.getAttributeSpellingListIndex())); >>> } >>> >>> +static void handleEnableIfAttr(Sema&S, Decl *D, const >>> AttributeList&Attr) { >>> + Expr *Cond = Attr.getArgAsExpr(0); >>> + ExprResult Converted = S.PerformContextuallyConvertToBool(Cond); >>> + if (Converted.isInvalid()) >>> + return; >>> + >>> + StringRef Msg; >>> + if (!S.checkStringLiteralArgumentAttr(Attr, 1, Msg)) >>> + return; >>> + >>> + SmallVector<PartialDiagnosticAt, 8> Diags; >>> + if (!Expr::isPotentialConstantExprUnevaluated(Cond, >>> cast<FunctionDecl>(D), >>> + Diags)) { >>> + S.Diag(Attr.getLoc(), diag::err_enable_if_never_constant_expr); >>> + for (int I = 0, N = Diags.size(); I != N; ++I) >>> + S.Diag(Diags[I].first, Diags[I].second); >>> + return; >>> + } >>> + >>> + D->addAttr(::new (S.Context) >>> + EnableIfAttr(Attr.getRange(), S.Context, Converted.take(), >>> Msg, >>> + Attr.getAttributeSpellingListIndex())); >>> +} >>> + >>> static void handleConsumableAttr(Sema&S, Decl *D, const >>> AttributeList&Attr) { >>> ConsumableAttr::ConsumedState DefaultState; >>> >>> @@ -3990,6 +4014,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/SemaDeclCXX.cpp >>> =================================================================== >>> --- lib/Sema/SemaDeclCXX.cpp (revision 198923) >>> +++ lib/Sema/SemaDeclCXX.cpp (working copy) >>> @@ -6079,6 +6079,18 @@ >>> PopDeclContext(); >>> } >>> >>> +/// This is used to implement the constant expression evaluation part of >>> the >>> +/// attribute enable_if extension. There is nothing in standard C++ >>> which would >>> +/// require reentering parameters. >>> +void Sema::ActOnReenterCXXMethodParameter(Scope *S, ParmVarDecl *Param) >>> { >>> + if (!Param) >>> + return; >>> + >>> + S->AddDecl(Param); >>> + if (Param->getDeclName()) >>> + IdResolver.AddDecl(Param); >>> +} >>> + >>> /// ActOnStartDelayedCXXMethodDeclaration - We have completed >>> /// parsing a top-level (non-nested) C++ class, and we are now >>> /// parsing those parts of the given Method declaration that could >>> Index: lib/Sema/SemaExpr.cpp >>> =================================================================== >>> --- lib/Sema/SemaExpr.cpp (revision 198923) >>> +++ lib/Sema/SemaExpr.cpp (working copy) >>> @@ -4474,6 +4474,21 @@ >>> else if (isa<MemberExpr>(NakedFn)) >>> NDecl = cast<MemberExpr>(NakedFn)->getMemberDecl(); >>> >>> + if (FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(NDecl)) { >>> + if (FD->hasAttr<EnableIfAttr>()) { >>> + if (EnableIfAttr *Attr = CheckEnableIf(FD, ArgExprs, true)) { >>> + Diag(Fn->getLocStart(), >>> + isa<CXXMethodDecl>(FD) ? >>> + diag::err_ovl_no_viable_member_function_in_call : >>> + diag::err_ovl_no_viable_function_in_call) >>> +<< FD->getDeclName()<< FD->getSourceRange(); >> >> >> No need to pass getDeclName -- the diagnostics engine already >> understands NamedDecls. > > > Cool. Fixed! > > >>> + Diag(FD->getLocation(), >>> + diag::note_ovl_candidate_disabled_by_enable_if_attr) >>> +<< Attr->getCond()->getSourceRange()<< Attr->getMessage(); >>> + } >>> + } >>> + } >>> + >>> return BuildResolvedCallExpr(Fn, NDecl, LParenLoc, ArgExprs, >>> RParenLoc, >>> ExecConfig, IsExecConfig); >>> } >>> Index: lib/Sema/SemaOverload.cpp >>> =================================================================== >>> --- lib/Sema/SemaOverload.cpp (revision 198923) >>> +++ lib/Sema/SemaOverload.cpp (working copy) >>> @@ -1008,8 +1008,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 >>> @@ -1085,6 +1085,22 @@ >>> return true; >>> } >>> >>> + // enable_if attributes are an order-sensitive part of the signature. >> >> >> So you are okay with them going in reverse order here? > > > Yes. We care that the two of them have the same enable_if attrs in the same > order, but we can check that in any order. > > >>> + 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); >>> + if (!(NewID == OldID)) >> >> >> Is there an != operator for FoldingSetNodeID? > > > There isn't! It's on my obvious cleanups list to add one and use it here. Thanks! > >>> + return true; >>> + } >>> + >>> // The signatures match; this is not an overload. >>> return false; >>> } >>> @@ -5452,11 +5468,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()&& >>> @@ -5568,7 +5584,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 >>> @@ -5577,8 +5593,78 @@ >>> Candidate.Conversions[ArgIdx].setEllipsis(); >>> } >>> } >>> + >>> + if (EnableIfAttr *FailedAttr = CheckEnableIf(Function, Args)) { >>> + Candidate.Viable = false; >>> + Candidate.FailureKind = ovl_fail_enable_if; >>> + Candidate.DeductionFailure.Data = FailedAttr; >>> + return; >>> + } >>> } >>> >>> +static bool IsNotEnableIfAttr(Attr *A) { return !isa<EnableIfAttr>(A); } >>> + >>> +EnableIfAttr *Sema::CheckEnableIf(FunctionDecl *Function, ArrayRef<Expr >>> *> Args, >>> + bool MissingImplicitThis) { >>> + // FIXME: specific_attr_iterator<EnableIfAttr> iterates in reverse >>> order, but >>> + // we need to find the first failing one. >>> + if (!Function->hasAttrs()) return 0; >> >> >> Return should be on a new line. > > > Done. > > >>> + AttrVec Attrs = Function->getAttrs(); >>> + AttrVec::iterator E = std::remove_if(Attrs.begin(), Attrs.end(), >>> + IsNotEnableIfAttr); >>> + if (Attrs.begin() == E) return 0; >> >> >> I think it would be more clear if it were Attrs.empty(). > > > Nope! remove_if doesn't remove elements from the container, it shuffles them > to the end and returns a new 'end' iterator marking the point at which > elements fail the predicate. > > > Also a new line here. > > Done. > > >> >>> + std::reverse(Attrs.begin(), E); >>> + >>> + SFINAETrap Trap(*this); >>> + >>> + // Convert the arguments. >>> + SmallVector<Expr *, 16> ConvertedArgs; >>> + 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; >>> + } >>> + ConvertedArgs.push_back(R.take()); >>> + } else { >>> + ExprResult R = >>> + >>> PerformCopyInitialization(InitializedEntity::InitializeParameter( >>> + Context, >>> + >>> Function->getParamDecl(i)), >>> + SourceLocation(), >>> + Args[i]); >>> + if (R.isInvalid()) { >>> + InitializationFailed = true; >>> + break; >>> + } >>> + ConvertedArgs.push_back(R.take()); >>> + } >>> + } >>> + >>> + if (InitializationFailed || Trap.hasErrorOccurred()) { >>> + return cast<EnableIfAttr>(Attrs[0]); >>> + } >> >> >> Braces are not needed here. > > > Done. > >>> + >>> + for (AttrVec::iterator I = Attrs.begin(); I != E; ++I) { >>> + APValue Result; >>> + EnableIfAttr *EIA = cast<EnableIfAttr>(*I); >>> + if (!EIA->getCond()->EvaluateWithSubstitution( >>> + Result, Context, Function, >>> + llvm::ArrayRef<const Expr*>(ConvertedArgs.data(), >>> + ConvertedArgs.size())) || >>> + !Result.isInt() || !Result.getInt().getBoolValue()) { >>> + return EIA; >>> + } >>> + } >>> + 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, >>> @@ -5658,9 +5744,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)&& >>> @@ -5747,15 +5833,22 @@ >>> if (Candidate.Conversions[ArgIdx + 1].isBad()) { >>> Candidate.Viable = false; >>> Candidate.FailureKind = ovl_fail_bad_conversion; >>> - break; >>> + return; >>> } >>> } else { >>> // (C++ 13.3.2p2): For the purposes of overload resolution, any >>> // argument for which there is no corresponding parameter is >>> - // considered to ""match the ellipsis" (C+ 13.3.3.1.3). >>> + // considered to "match the ellipsis" (C+ 13.3.3.1.3). >>> Candidate.Conversions[ArgIdx + 1].setEllipsis(); >>> } >>> } >>> + >>> + if (EnableIfAttr *FailedAttr = CheckEnableIf(Method, Args, true)) { >>> + Candidate.Viable = false; >>> + Candidate.FailureKind = ovl_fail_enable_if; >>> + Candidate.DeductionFailure.Data = FailedAttr; >>> + return; >>> + } >>> } >>> >>> /// \brief Add a C++ member function template as a candidate to the >>> candidate >>> @@ -5971,7 +6064,7 @@ >>> return; >>> } >>> >>> - // We won't go through a user-define type conversion function to >>> convert a >>> + // We won't go through a user-defined type conversion function to >>> convert a >>> // derived to base as such conversions are given Conversion Rank. >>> They only >>> // go through a copy constructor. 13.3.3.1.2-p4 [over.ics.user] >>> QualType FromCanon >>> @@ -6031,6 +6124,7 @@ >>> GetConversionRank(ICS.Standard.Second) != ICR_Exact_Match) { >>> Candidate.Viable = false; >>> Candidate.FailureKind = ovl_fail_final_conversion_not_exact; >>> + return; >>> } >>> >>> // C++0x [dcl.init.ref]p5: >>> @@ -6042,18 +6136,26 @@ >>> ICS.Standard.First == ICK_Lvalue_To_Rvalue) { >>> Candidate.Viable = false; >>> Candidate.FailureKind = ovl_fail_bad_final_conversion; >>> + return; >>> } >>> break; >>> >>> case ImplicitConversionSequence::BadConversion: >>> Candidate.Viable = false; >>> Candidate.FailureKind = ovl_fail_bad_final_conversion; >>> - break; >>> + return; >>> >>> default: >>> llvm_unreachable( >>> "Can only end up with a standard conversion sequence or >>> failure"); >>> } >>> + >>> + if (EnableIfAttr *FailedAttr = CheckEnableIf(Conversion, >>> ArrayRef<Expr*>())) { >>> + Candidate.Viable = false; >>> + Candidate.FailureKind = ovl_fail_enable_if; >>> + Candidate.DeductionFailure.Data = FailedAttr; >>> + return; >>> + } >>> } >>> >>> /// \brief Adds a conversion function template specialization >>> @@ -6191,7 +6293,7 @@ >>> if (Candidate.Conversions[ArgIdx + 1].isBad()) { >>> Candidate.Viable = false; >>> Candidate.FailureKind = ovl_fail_bad_conversion; >>> - break; >>> + return; >>> } >>> } else { >>> // (C++ 13.3.2p2): For the purposes of overload resolution, any >>> @@ -6200,6 +6302,13 @@ >>> Candidate.Conversions[ArgIdx + 1].setEllipsis(); >>> } >>> } >>> + >>> + if (EnableIfAttr *FailedAttr = CheckEnableIf(Conversion, >>> ArrayRef<Expr*>())) { >>> + Candidate.Viable = false; >>> + Candidate.FailureKind = ovl_fail_enable_if; >>> + Candidate.DeductionFailure.Data = FailedAttr; >>> + return; >>> + } >>> } >>> >>> /// \brief Add overload candidates for overloaded operators that are >>> @@ -8111,6 +8220,41 @@ >>> } >>> } >>> >>> + // Check for enable_if value-based overload resolution. >>> + if (Cand1.Function&& Cand2.Function&& >>> + (Cand1.Function->hasAttr<EnableIfAttr>() || >>> + Cand2.Function->hasAttr<EnableIfAttr>())) { >>> + // FIXME: The next several lines are just >>> + // specific_attr_iterator<EnableIfAttr> but going in declaration >>> order, >>> + // instead of reverse order which is how they're stored in the AST. >>> + AttrVec Cand1Attrs = Cand1.Function->getAttrs(); >>> + AttrVec::iterator Cand1E = std::remove_if(Cand1Attrs.begin(), >>> + Cand1Attrs.end(), >>> + IsNotEnableIfAttr); >>> + std::reverse(Cand1Attrs.begin(), Cand1E); >>> + >>> + AttrVec Cand2Attrs = Cand2.Function->getAttrs(); >>> + AttrVec::iterator Cand2E = std::remove_if(Cand2Attrs.begin(), >>> + Cand2Attrs.end(), >>> + IsNotEnableIfAttr); >>> + std::reverse(Cand2Attrs.begin(), Cand2E); >>> + for (AttrVec::iterator >>> + Cand1I = Cand1Attrs.begin(), Cand2I = Cand2Attrs.begin(); >>> + Cand1I != Cand1E || Cand2I != Cand2E; ++Cand1I, ++Cand2I) { >>> + if (Cand1I == Cand1E) >>> + return false; >>> + if (Cand2I == Cand2E) >>> + return true; >>> + llvm::FoldingSetNodeID Cand1ID, Cand2ID; >>> + cast<EnableIfAttr>(*Cand1I)->getCond()->Profile(Cand1ID, >>> + S.getASTContext(), >>> true); >>> + cast<EnableIfAttr>(*Cand2I)->getCond()->Profile(Cand2ID, >>> + S.getASTContext(), >>> true); >>> + if (!(Cand1ID == Cand2ID)) >> >> >> operator != ? > > > Nope. > >>> + return false; >>> + } >>> + } >>> + >>> return false; >>> } >>> >>> @@ -8819,6 +8963,15 @@ >>> << (unsigned) FnKind<< CalleeTarget<< CallerTarget; >>> } >>> >>> +void DiagnoseFailedEnableIfAttr(Sema&S, OverloadCandidate *Cand) { >>> >>> + FunctionDecl *Callee = Cand->Function; >>> + EnableIfAttr *Attr = >>> static_cast<EnableIfAttr*>(Cand->DeductionFailure.Data); >>> + >>> + 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. >>> /// >>> @@ -8882,6 +9035,9 @@ >>> >>> case ovl_fail_bad_target: >>> return DiagnoseBadTarget(S, Cand); >>> + >>> + case ovl_fail_enable_if: >>> + return DiagnoseFailedEnableIfAttr(S, Cand); >>> } >>> } >>> >>> @@ -11107,7 +11263,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,97 @@ >>> +// 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(!(flags& O_CREAT), "must specify mode when using >>> O_CREAT"))) __attribute__((overloadable)); // expected-note{{candidate >>> disabled: 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 function}} >>> + __attribute__((overloadable)) >>> + __asm__("strnlen_real1"); >>> + >>> +__attribute__((always_inline)) >>> +inline size_t strnlen(const char *s, size_t maxlen) // >>> expected-note{{candidate function}} >>> + __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) // >>> expected-note{{candidate disabled: 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, >>> + "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) // >>> expected-note{{candidate function has been explicitly made unavailable}} >>> + __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]; >>> + 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__((overloadable)); // >>> expected-note{{candidate function}} >>> +int isdigit(int c) __attribute__((overloadable)) // >>> expected-note{{candidate function has been explicitly made unavailable}} >>> + __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); >>> + isdigit(10); >>> +#ifndef CODEGEN >>> + isdigit(-10); // expected-error{{call to unavailable function >>> 'isdigit': 'c' must have the value of an unsigned char or EOF}} >>> +#endif >>> +} >>> + >>> +#ifndef CODEGEN >>> +__attribute__((enable_if(n == 0, "chosen when 'n' is zero"))) void >>> f1(int n); // expected-error{{use of undeclared identifier 'n'}} >>> + >>> +int n __attribute__((enable_if(1, "always chosen"))); // >>> expected-warning{{'enable_if' attribute only applies to functions}} >>> + >>> +void f(int n) __attribute__((enable_if("chosen when 'n' is zero", n == >>> 0))); // expected-error{{'enable_if' attribute requires a string}} >>> + >>> +void f(int n) __attribute__((enable_if())); // >>> expected-error{{'enable_if' attribute requires exactly 2 arguments}} >>> + >>> +void f(int n) __attribute__((enable_if(unresolvedid, "chosen when >>> 'unresolvedid' is non-zero"))); // expected-error{{use of undeclared >>> identifier 'unresolvedid'}} >>> + >>> +int global; >>> +void f(int n) __attribute__((enable_if(global == 0, "chosen when >>> 'global' is zero"))); // expected-error{{enable_if attribute expression >>> never produces a constant expression}} // expected-note{{subexpression not >>> valid in a constant expression}} >>> + >>> +const int cst = 7; >>> +void return_cst(void) __attribute__((overloadable)) >>> __attribute__((enable_if(cst == 7, "chosen when 'cst' is 7"))); >>> +void test_return_cst() { return_cst(); } >>> +#endif >>> Index: test/SemaCXX/enable_if.cpp >>> =================================================================== >>> --- test/SemaCXX/enable_if.cpp (revision 0) >>> +++ test/SemaCXX/enable_if.cpp (working copy) >>> @@ -0,0 +1,56 @@ >>> +// RUN: %clang_cc1 -std=c++11 -verify -Wno-gcc-compat %s >>> + >>> +typedef int (*fp)(int); >>> +int surrogate(int); >>> + >>> +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 disabled: chosen when 'n' is one}} >>> + >>> + static void s(int n) __attribute__((enable_if(n == 0, "chosen when 'n' >>> is zero"))); // expected-note2{{candidate disabled: 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}} >>> + >>> + operator long() __attribute__((enable_if(true, "chosen on your >>> platform"))); >>> + operator int() __attribute__((enable_if(false, "chosen on other >>> platform"))); >>> + >>> + operator fp() __attribute__((enable_if(false, "never enabled"))) { >>> return surrogate; } // expected-note{{conversion candidate of type 'int >>> (*)(int)'}} // FIXME: the message is not displayed >>> +}; >>> + >>> +void X::f(int n) __attribute__((enable_if(n == 0, "chosen when 'n' is >>> zero"))) // expected-note{{member declaration nearly matches}} >>> expected-note{{candidate disabled: 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 disabled: chosen when 'n' is >>> two}} >>> +{ >>> +} >>> + >>> +__attribute__((deprecated)) constexpr int old() { return 0; } // >>> expected-note2{{'old' has been explicitly marked deprecated here}} >>> +void deprec1(int i) __attribute__((enable_if(old() == 0, "chosen when >>> old() is zero"))); // expected-warning{{'old' is deprecated}} >>> +void deprec2(int i) __attribute__((enable_if(old() == 0, "chosen when >>> old() is zero"))); // expected-warning{{'old' is deprecated}} >>> + >>> +void overloaded(int); >>> +void overloaded(long); >>> + >>> +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 member function for call to >>> 's'}} >>> + >>> + X::s(0); >>> + X::s(1); // expected-error{{no matching member function for call to >>> 's'}} >>> + >>> + x.conflict(5); // expected-error{{call to member function 'conflict' >>> is ambiguous}} >>> + >>> + deprec2(0); >>> + >>> + overloaded(x); >>> + >>> + int i = x(1); // expected-error{{no matching function for call to >>> object of type 'X'}} >>> +} >> >> >> Some tests involving templates might be kind of interesting. > > > Agreed! I was just wondering whether: > > template<int N> void foo() __attribute__((enable_if(N == 1))); > template<int N> void foo() __attribute__((enable_if(N == 1))); > > is considered one declaration + redeclaration, or two declarations. The > verdict? "'enable_if' attribute expression never produces a constant". Sigh. I'll let Richard tackle this one. :-) ~Aaron _______________________________________________ cfe-commits mailing list [email protected] http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits
