patch: new attribute enable_if
Aaron Ballman
aaron at aaronballman.com
Fri Jan 10 13:42:46 PST 2014
On Fri, Jan 10, 2014 at 3:43 PM, Nick Lewycky <nicholas at mxc.ca> 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
More information about the cfe-commits
mailing list