From: Nick Lewycky Date: Sat, 11 Jan 2014 02:50:57 +0000 (+0000) Subject: Add a new attribute 'enable_if' which can be used to control overload resolution... X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=c8cd634957fb93a2e316058b6f1f8f2aa254ed98;p=clang Add a new attribute 'enable_if' which can be used to control overload resolution based on the values of the function arguments at the call site. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@198996 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/docs/LanguageExtensions.rst b/docs/LanguageExtensions.rst index 3ad078a5fe..110b016888 100644 --- a/docs/LanguageExtensions.rst +++ b/docs/LanguageExtensions.rst @@ -1316,6 +1316,8 @@ parameters of protocol-qualified type. Query the presence of this new mangling with ``__has_feature(objc_protocol_qualifier_mangling)``. +.. _langext-overloading: + Function Overloading in C ========================= @@ -1398,6 +1400,84 @@ caveats to this use of name mangling: 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`` 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. For example, suppose we want calls to +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 +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 ========================================== diff --git a/include/clang/AST/Expr.h b/include/clang/AST/Expr.h index b56e494dfc..3453cb6bdd 100644 --- a/include/clang/AST/Expr.h +++ b/include/clang/AST/Expr.h @@ -508,6 +508,16 @@ public: 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 @@ public: const VarDecl *VD, SmallVectorImpl &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, + const FunctionDecl *Callee, + llvm::ArrayRef Args) const; + /// \brief Enumeration used to describe the kind of Null pointer constant /// returned from \c isNullPointerConstant(). enum NullPointerConstantKind { diff --git a/include/clang/Basic/Attr.td b/include/clang/Basic/Attr.td index 33e58f46bb..37244dcba7 100644 --- a/include/clang/Basic/Attr.td +++ b/include/clang/Basic/Attr.td @@ -470,6 +470,13 @@ def Destructor : InheritableAttr { let Subjects = SubjectList<[Function]>; } +def EnableIf : InheritableAttr { + let Spellings = [GNU<"enable_if">]; + let Subjects = SubjectList<[Function]>; + let Args = [ExprArgument<"Cond">, StringArgument<"Message">]; + let TemplateDependent = 1; +} + def ExtVectorType : Attr { let Spellings = [GNU<"ext_vector_type">]; let Subjects = SubjectList<[TypedefName], ErrorDiag>; diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index 837862e575..18b6fa2e8b 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -1737,6 +1737,8 @@ def err_constexpr_local_var_no_init : Error< def ext_constexpr_function_never_constant_expr : ExtWarn< "constexpr %select{function|constructor}0 never produces a " "constant expression">, InGroup>, DefaultError; +def err_enable_if_never_constant_expr : Error< + "'enable_if' attribute expression never produces a constant expression">; def err_constexpr_body_no_return : Error< "no return statement in constexpr function">; def warn_cxx11_compat_constexpr_body_no_return : Warning< @@ -2586,6 +2588,8 @@ def note_ovl_candidate_substitution_failure : Note< "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">; diff --git a/include/clang/Parse/Parser.h b/include/clang/Parse/Parser.h index b0dda6385d..ccb4259cd1 100644 --- a/include/clang/Parse/Parser.h +++ b/include/clang/Parse/Parser.h @@ -1968,7 +1968,7 @@ private: if (Tok.is(tok::kw___attribute)) { ParsedAttributes attrs(AttrFactory); SourceLocation endLoc; - ParseGNUAttributes(attrs, &endLoc, LateAttrs); + ParseGNUAttributes(attrs, &endLoc, LateAttrs, &D); D.takeAttributes(attrs, endLoc); } } @@ -1980,14 +1980,16 @@ private: } 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) { diff --git a/include/clang/Sema/Overload.h b/include/clang/Sema/Overload.h index b8bd14aa1c..7086b723d4 100644 --- a/include/clang/Sema/Overload.h +++ b/include/clang/Sema/Overload.h @@ -579,7 +579,11 @@ namespace clang { /// (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). diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h index d5ba71a3d1..40f86e9895 100644 --- a/include/clang/Sema/Sema.h +++ b/include/clang/Sema/Sema.h @@ -101,6 +101,7 @@ namespace clang { class DependentDiagnostic; class DesignatedInitExpr; class Designation; + class EnableIfAttr; class EnumConstantDecl; class Expr; class ExtVectorType; @@ -2200,6 +2201,11 @@ public: // 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 Args, + bool MissingImplicitThis = false); + // [PossiblyAFunctionType] --> [Return] // NonFunctionType --> NonFunctionType // R (A) --> R(A) @@ -4770,6 +4776,7 @@ public: 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); diff --git a/lib/AST/ExprConstant.cpp b/lib/AST/ExprConstant.cpp index bef7768164..e836e18238 100644 --- a/lib/AST/ExprConstant.cpp +++ b/lib/AST/ExprConstant.cpp @@ -474,13 +474,30 @@ namespace { /// 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 @@ namespace { // some later problem. case EM_ConstantExpression: case EM_PotentialConstantExpression: + case EM_ConstantExpressionUnevaluated: + case EM_PotentialConstantExpressionUnevaluated: HasActiveDiagnostic = false; return OptionalDiagnostic(); } @@ -644,11 +663,13 @@ namespace { 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 @@ namespace { 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 @@ namespace { 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 @@ bool IntExprEvaluator::VisitCallExpr(const CallExpr *E) { // 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); + } } case Builtin::BI__builtin_bswap16: @@ -8656,6 +8691,28 @@ bool Expr::isCXX11ConstantExpr(const ASTContext &Ctx, APValue *Result, return IsConstExpr; } +bool Expr::EvaluateWithSubstitution(APValue &Value, ASTContext &Ctx, + const FunctionDecl *Callee, + llvm::ArrayRef Args) const { + Expr::EvalStatus Status; + EvalInfo Info(Ctx, Status, EvalInfo::EM_ConstantExpressionUnevaluated); + + ArgVector ArgValues(Args.size()); + for (ArrayRef::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,27 @@ bool Expr::isPotentialConstantExpr(const FunctionDecl *FD, 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 Args; + ArgVector ArgValues(0); + bool Success = EvaluateArgs(Args, ArgValues, Info); + (void)Success; + 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(); +} diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index d3e76c5e92..9835e249b1 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -117,7 +117,8 @@ static bool isAttributeLateParsed(const IdentifierInfo &II) { /// 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 @@ void Parser::ParseGNUAttributes(ParsedAttributes &attrs, // 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 @@ void Parser::ParseGNUAttributeArgs(IdentifierInfo *AttrName, 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 '('"); @@ -272,23 +274,38 @@ void Parser::ParseGNUAttributeArgs(IdentifierInfo *AttrName, ParseAvailabilityAttribute(*AttrName, AttrNameLoc, Attrs, EndLoc); return; } - + if (AttrKind == AttributeList::AT_ObjCBridgeRelated) { ParseObjCBridgeRelatedAttribute(*AttrName, AttrNameLoc, Attrs, EndLoc); return; } - + // 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 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(FTI.ArgInfo[i].Param); + Actions.ActOnReenterCXXMethodParameter(getCurScope(), Param); + } + } + // Ignore the left paren location for now. ConsumeParen(); @@ -1156,7 +1173,7 @@ void Parser::ParseLexedAttribute(LateParsedAttribute &LA, 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(); @@ -1169,7 +1186,7 @@ void Parser::ParseLexedAttribute(LateParsedAttribute &LA, // 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(); diff --git a/lib/Parse/ParseDeclCXX.cpp b/lib/Parse/ParseDeclCXX.cpp index 5135530cd9..4968631480 100644 --- a/lib/Parse/ParseDeclCXX.cpp +++ b/lib/Parse/ParseDeclCXX.cpp @@ -3243,7 +3243,7 @@ void Parser::ParseCXX11AttributeSpecifier(ParsedAttributes &attrs, 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) diff --git a/lib/Sema/SemaDeclAttr.cpp b/lib/Sema/SemaDeclAttr.cpp index 945525bdd5..6555a3d27f 100644 --- a/lib/Sema/SemaDeclAttr.cpp +++ b/lib/Sema/SemaDeclAttr.cpp @@ -827,6 +827,34 @@ static void handleLocksExcludedAttr(Sema &S, Decl *D, Attr.getAttributeSpellingListIndex())); } +static void handleEnableIfAttr(Sema &S, Decl *D, const AttributeList &Attr) { + Expr *Cond = Attr.getArgAsExpr(0); + if (!Cond->isTypeDependent()) { + ExprResult Converted = S.PerformContextuallyConvertToBool(Cond); + if (Converted.isInvalid()) + return; + Cond = Converted.take(); + } + + StringRef Msg; + if (!S.checkStringLiteralArgumentAttr(Attr, 1, Msg)) + return; + + SmallVector Diags; + if (!Cond->isValueDependent() && + !Expr::isPotentialConstantExprUnevaluated(Cond, cast(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, Cond, Msg, + Attr.getAttributeSpellingListIndex())); +} + static void handleConsumableAttr(Sema &S, Decl *D, const AttributeList &Attr) { ConsumableAttr::ConsumedState DefaultState; @@ -3990,6 +4018,7 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, handleAttrWithMessage(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; diff --git a/lib/Sema/SemaDeclCXX.cpp b/lib/Sema/SemaDeclCXX.cpp index 28f038a69e..820f57f5f0 100644 --- a/lib/Sema/SemaDeclCXX.cpp +++ b/lib/Sema/SemaDeclCXX.cpp @@ -6079,6 +6079,18 @@ void Sema::ActOnFinishDelayedMemberDeclarations(Scope *S, Decl *RecordD) { 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 diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp index ca261cd784..f479dc8ae5 100644 --- a/lib/Sema/SemaExpr.cpp +++ b/lib/Sema/SemaExpr.cpp @@ -4474,6 +4474,21 @@ Sema::ActOnCallExpr(Scope *S, Expr *Fn, SourceLocation LParenLoc, else if (isa(NakedFn)) NDecl = cast(NakedFn)->getMemberDecl(); + if (FunctionDecl *FD = dyn_cast_or_null(NDecl)) { + if (FD->hasAttr()) { + if (const EnableIfAttr *Attr = CheckEnableIf(FD, ArgExprs, true)) { + Diag(Fn->getLocStart(), + isa(FD) ? + diag::err_ovl_no_viable_member_function_in_call : + diag::err_ovl_no_viable_function_in_call) + << FD << FD->getSourceRange(); + 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); } diff --git a/lib/Sema/SemaOverload.cpp b/lib/Sema/SemaOverload.cpp index 13337480b0..6032ed3980 100644 --- a/lib/Sema/SemaOverload.cpp +++ b/lib/Sema/SemaOverload.cpp @@ -1008,8 +1008,8 @@ bool Sema::IsOverload(FunctionDecl *New, FunctionDecl *Old, isa(NewQType.getTypePtr())) return false; - const FunctionProtoType* OldType = cast(OldQType); - const FunctionProtoType* NewType = cast(NewQType); + const FunctionProtoType *OldType = cast(OldQType); + const FunctionProtoType *NewType = cast(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 @@ bool Sema::IsOverload(FunctionDecl *New, FunctionDecl *Old, return true; } + // enable_if attributes are an order-sensitive part of the signature. + for (specific_attr_iterator + NewI = New->specific_attr_begin(), + NewE = New->specific_attr_end(), + OldI = Old->specific_attr_begin(), + OldE = Old->specific_attr_end(); + 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)) + return true; + } + // The signatures match; this is not an overload. return false; } @@ -5452,11 +5468,11 @@ void Sema::AddOverloadCandidate(FunctionDecl *Function, DeclAccessPair FoundDecl, ArrayRef Args, - OverloadCandidateSet& CandidateSet, + OverloadCandidateSet &CandidateSet, bool SuppressUserConversions, bool PartialOverloading, bool AllowExplicit) { - const FunctionProtoType* Proto + const FunctionProtoType *Proto = dyn_cast(Function->getType()->getAs()); assert(Proto && "Functions without a prototype cannot be overloaded"); assert(!Function->getDescribedFunctionTemplate() && @@ -5568,7 +5584,7 @@ Sema::AddOverloadCandidate(FunctionDecl *Function, 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,6 +5593,77 @@ Sema::AddOverloadCandidate(FunctionDecl *Function, 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(A); } + +EnableIfAttr *Sema::CheckEnableIf(FunctionDecl *Function, ArrayRef Args, + bool MissingImplicitThis) { + // FIXME: specific_attr_iterator iterates in reverse order, but + // we need to find the first failing one. + if (!Function->hasAttrs()) + return 0; + AttrVec Attrs = Function->getAttrs(); + AttrVec::iterator E = std::remove_if(Attrs.begin(), Attrs.end(), + IsNotEnableIfAttr); + if (Attrs.begin() == E) + return 0; + std::reverse(Attrs.begin(), E); + + SFINAETrap Trap(*this); + + // Convert the arguments. + SmallVector ConvertedArgs; + bool InitializationFailed = false; + for (unsigned i = 0, e = Args.size(); i != e; ++i) { + if (i == 0 && !MissingImplicitThis && isa(Function) && + !cast(Function)->isStatic()) { + CXXMethodDecl *Method = cast(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(Attrs[0]); + + for (AttrVec::iterator I = Attrs.begin(); I != E; ++I) { + APValue Result; + EnableIfAttr *EIA = cast(*I); + if (!EIA->getCond()->EvaluateWithSubstitution( + Result, Context, Function, + llvm::ArrayRef(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 @@ -5658,9 +5745,9 @@ Sema::AddMethodCandidate(CXXMethodDecl *Method, DeclAccessPair FoundDecl, CXXRecordDecl *ActingContext, QualType ObjectType, Expr::Classification ObjectClassification, ArrayRef Args, - OverloadCandidateSet& CandidateSet, + OverloadCandidateSet &CandidateSet, bool SuppressUserConversions) { - const FunctionProtoType* Proto + const FunctionProtoType *Proto = dyn_cast(Method->getType()->getAs()); assert(Proto && "Methods without a prototype cannot be overloaded"); assert(!isa(Method) && @@ -5747,15 +5834,22 @@ Sema::AddMethodCandidate(CXXMethodDecl *Method, DeclAccessPair FoundDecl, 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 +6065,7 @@ Sema::AddConversionCandidate(CXXConversionDecl *Conversion, 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 +6125,7 @@ Sema::AddConversionCandidate(CXXConversionDecl *Conversion, 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 +6137,26 @@ Sema::AddConversionCandidate(CXXConversionDecl *Conversion, 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())) { + Candidate.Viable = false; + Candidate.FailureKind = ovl_fail_enable_if; + Candidate.DeductionFailure.Data = FailedAttr; + return; + } } /// \brief Adds a conversion function template specialization @@ -6191,7 +6294,7 @@ void Sema::AddSurrogateCandidate(CXXConversionDecl *Conversion, 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 +6303,13 @@ void Sema::AddSurrogateCandidate(CXXConversionDecl *Conversion, Candidate.Conversions[ArgIdx + 1].setEllipsis(); } } + + if (EnableIfAttr *FailedAttr = CheckEnableIf(Conversion, ArrayRef())) { + 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 +8221,47 @@ isBetterOverloadCandidate(Sema &S, } } + // Check for enable_if value-based overload resolution. + if (Cand1.Function && Cand2.Function && + (Cand1.Function->hasAttr() || + Cand2.Function->hasAttr())) { + // FIXME: The next several lines are just + // specific_attr_iterator but going in declaration order, + // instead of reverse order which is how they're stored in the AST. + AttrVec Cand1Attrs; + AttrVec::iterator Cand1E = Cand1Attrs.end(); + if (Cand1.Function->hasAttrs()) { + Cand1Attrs = Cand1.Function->getAttrs(); + Cand1E = std::remove_if(Cand1Attrs.begin(), Cand1Attrs.end(), + IsNotEnableIfAttr); + std::reverse(Cand1Attrs.begin(), Cand1E); + } + + AttrVec Cand2Attrs; + AttrVec::iterator Cand2E = Cand2Attrs.end(); + if (Cand2.Function->hasAttrs()) { + Cand2Attrs = Cand2.Function->getAttrs(); + 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(*Cand1I)->getCond()->Profile(Cand1ID, + S.getASTContext(), true); + cast(*Cand2I)->getCond()->Profile(Cand2ID, + S.getASTContext(), true); + if (!(Cand1ID == Cand2ID)) + return false; + } + } + return false; } @@ -8819,6 +8970,15 @@ void DiagnoseBadTarget(Sema &S, OverloadCandidate *Cand) { << (unsigned) FnKind << CalleeTarget << CallerTarget; } +void DiagnoseFailedEnableIfAttr(Sema &S, OverloadCandidate *Cand) { + FunctionDecl *Callee = Cand->Function; + EnableIfAttr *Attr = static_cast(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 +9042,9 @@ void NoteFunctionCandidate(Sema &S, OverloadCandidate *Cand, case ovl_fail_bad_target: return DiagnoseBadTarget(S, Cand); + + case ovl_fail_enable_if: + return DiagnoseFailedEnableIfAttr(S, Cand); } } @@ -11107,7 +11270,7 @@ Sema::BuildCallToMemberFunction(Scope *S, Expr *MemExprE, << qualsString << (qualsString.find(' ') == std::string::npos ? 1 : 2); } - + CXXMemberCallExpr *call = new (Context) CXXMemberCallExpr(Context, MemExprE, Args, resultType, valueKind, RParenLoc); diff --git a/lib/Sema/SemaTemplateInstantiateDecl.cpp b/lib/Sema/SemaTemplateInstantiateDecl.cpp index 22f13d7d2f..6995ae712b 100644 --- a/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -129,6 +129,39 @@ static void instantiateDependentAlignedAttr( } } +static void instantiateDependentEnableIfAttr( + Sema &S, const MultiLevelTemplateArgumentList &TemplateArgs, + const EnableIfAttr *A, const Decl *Tmpl, Decl *New) { + Expr *Cond = 0; + { + EnterExpressionEvaluationContext Unevaluated(S, Sema::Unevaluated); + ExprResult Result = S.SubstExpr(A->getCond(), TemplateArgs); + if (Result.isInvalid()) + return; + Cond = Result.takeAs(); + } + if (A->getCond()->isTypeDependent() && !Cond->isTypeDependent()) { + ExprResult Converted = S.PerformContextuallyConvertToBool(Cond); + if (Converted.isInvalid()) + return; + Cond = Converted.take(); + } + + SmallVector Diags; + if (A->getCond()->isValueDependent() && !Cond->isValueDependent() && + !Expr::isPotentialConstantExprUnevaluated(Cond, cast(Tmpl), + Diags)) { + S.Diag(A->getLocation(), 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; + } + + EnableIfAttr *EIA = new (S.getASTContext()) EnableIfAttr( + A->getLocation(), S.getASTContext(), Cond, A->getMessage()); + New->addAttr(EIA); +} + void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs, const Decl *Tmpl, Decl *New, LateInstantiatedAttrVec *LateAttrs, @@ -144,6 +177,13 @@ void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs, continue; } + const EnableIfAttr *EnableIf = dyn_cast(TmplAttr); + if (EnableIf && EnableIf->getCond()->isValueDependent()) { + instantiateDependentEnableIfAttr(*this, TemplateArgs, EnableIf, Tmpl, + New); + continue; + } + assert(!TmplAttr->isPackExpansion()); if (TmplAttr->isLateParsed() && LateAttrs) { // Late parsed attributes must be instantiated and attached after the diff --git a/test/Sema/enable_if.c b/test/Sema/enable_if.c new file mode 100644 index 0000000000..48ff250931 --- /dev/null +++ b/test/Sema/enable_if.c @@ -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 diff --git a/test/SemaCXX/enable_if.cpp b/test/SemaCXX/enable_if.cpp new file mode 100644 index 0000000000..b52f86ea3c --- /dev/null +++ b/test/SemaCXX/enable_if.cpp @@ -0,0 +1,72 @@ +// 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); + +struct Nothing { }; +template void typedep(T t) __attribute__((enable_if(t, ""))); // expected-note{{candidate disabled:}} expected-error{{value of type 'Nothing' is not contextually convertible to 'bool'}} +template void valuedep() __attribute__((enable_if(N == 1, ""))); + +// FIXME: we skip potential constant expression evaluation on value dependent +// enable-if expressions +int not_constexpr(); +template void valuedep() __attribute__((enable_if(N == not_constexpr(), ""))); + +template void instantiationdep() __attribute__((enable_if(sizeof(sizeof(T)) != 0, ""))); + +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'}} + + Nothing n; + typedep(0); // expected-error{{no matching function for call to 'typedep'}} + typedep(1); + typedep(n); // expected-note{{in instantiation of function template specialization 'typedep' requested here}} +}