Index: include/clang/AST/Expr.h =================================================================== --- include/clang/AST/Expr.h +++ include/clang/AST/Expr.h @@ -651,7 +651,8 @@ /// constant. bool EvaluateWithSubstitution(APValue &Value, ASTContext &Ctx, const FunctionDecl *Callee, - ArrayRef Args) const; + ArrayRef Args, + const Expr *This = nullptr) const; /// \brief If the current Expr is a pointer, this will try to statically /// determine the number of bytes available where the pointer is pointing. Index: include/clang/Basic/Attr.td =================================================================== --- include/clang/Basic/Attr.td +++ include/clang/Basic/Attr.td @@ -140,12 +140,15 @@ bit Fake = fake; } -class BoolArgument : Argument; +class BoolArgument : Argument; class IdentifierArgument : Argument; class IntArgument : Argument; class StringArgument : Argument; class ExprArgument : Argument; -class FunctionArgument : Argument; +class FunctionArgument : Argument; class TypeArgument : Argument; class UnsignedArgument : Argument; class VariadicUnsignedArgument : Argument; @@ -1585,6 +1588,26 @@ let Documentation = [Undocumented]; } +def DiagnoseIf : InheritableAttr { + let Spellings = [GNU<"diagnose_if">]; + let Subjects = SubjectList<[Function]>; + let Args = [ExprArgument<"Cond">, StringArgument<"Message">, + EnumArgument<"DiagnosticType", + "DiagnosticType", + ["error", "warning"], + ["DT_Error", "DT_Warning"]>, + BoolArgument<"ArgDependent", 0, /*fake*/ 1>, + FunctionArgument<"Parent", 0, /*fake*/ 1>]; + let DuplicatesAllowedWhileMerging = 1; + let LateParsed = 1; + let AdditionalMembers = [{ + bool isError() const { return diagnosticType == DT_Error; } + bool isWarning() const { return diagnosticType == DT_Warning; } + }]; + let TemplateDependent = 1; + let Documentation = [DiagnoseIfDocs]; +} + def ArcWeakrefUnavailable : InheritableAttr { let Spellings = [GNU<"objc_arc_weak_reference_unavailable">]; let Subjects = SubjectList<[ObjCInterface], ErrorDiag>; Index: include/clang/Basic/AttrDocs.td =================================================================== --- include/clang/Basic/AttrDocs.td +++ include/clang/Basic/AttrDocs.td @@ -378,6 +378,65 @@ }]; } +def DiagnoseIfDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +The ``diagnose_if`` attribute can be placed on function declarations to emit +warnings or errors at compile-time if calls to the attributed function meet +certain user-defined criteria. For example: + +.. code-block:: c + void abs(int a) + __attribute__((diagnose_if(a >= 0, "Redundant abs call", "warning"))); + void must_abs(int a) + __attribute__((diagnose_if(a >= 0, "Redundant abs call", "error"))); + + int val = abs(1); // warning: Redundant abs call + int val2 = must_abs(1); // error: Redundant abs call + int val3 = abs(val); + int val4 = must_abs(val); // Because run-time checks are not emitted for + // diagnose_if attributes, this executes without + // issue. + + +``diagnose_if`` is closely related to ``enable_if``, with a few key differences: + +* Overload resolution is not aware of ``diagnose_if`` attributes: they're + considered only after we select the best candidate from a given candidate set. +* Function declarations that differ only in their ``diagnose_if`` attributes are + considered to be redeclarations of the same function (not overloads). +* If the condition provided to ``diagnose_if`` cannot be evaluated, no + diagnostic will be emitted. + +Otherwise, ``diagnose_if`` is essentially the logical negation of ``enable_if``. + +As a result of bullet number two, ``diagnose_if`` attributes will stack on the +same function. For example: + +.. code-block:: c + + int foo() __attribute__((diagnose_if(1, "diag1", "warning"))); + int foo() __attribute__((diagnose_if(1, "diag2", "warning"))); + + int bar = foo(); // warning: diag1 + // warning: diag2 + int (*fooptr)(void) = foo; // warning: diag1 + // warning: diag2 + + constexpr int supportsAPILevel(int N) { return N < 5; } + int baz(int a) + __attribute__((diagnose_if(!supportsAPILevel(10), + "Upgrade to API level 10 to use baz", "error"))); + int baz(int a) + __attribute__((diagnose_if(!a, "0 is not recommended.", "warning"))); + + int (*bazptr)(int) = baz; // error: Upgrade to API level 10 to use baz + int v = baz(0); // error: Upgrade to API level 10 to use baz + +Query for this feature with ``__has_attribute(diagnose_if)``. + }]; +} + def PassObjectSizeDocs : Documentation { let Category = DocCatVariable; // Technically it's a parameter doc, but eh. let Content = [{ Index: include/clang/Basic/DiagnosticCommonKinds.td =================================================================== --- include/clang/Basic/DiagnosticCommonKinds.td +++ include/clang/Basic/DiagnosticCommonKinds.td @@ -161,6 +161,8 @@ InGroup; def ext_clang_enable_if : Extension<"'enable_if' is a clang extension">, InGroup; +def ext_clang_diagnose_if : Extension<"'diagnose_if' is a clang extension">, + InGroup; // SEH def err_seh_expected_handler : Error< Index: include/clang/Basic/DiagnosticGroups.td =================================================================== --- include/clang/Basic/DiagnosticGroups.td +++ include/clang/Basic/DiagnosticGroups.td @@ -495,6 +495,7 @@ def UnusedGetterReturnValue : DiagGroup<"unused-getter-return-value">; def UsedButMarkedUnused : DiagGroup<"used-but-marked-unused">; def UserDefinedLiterals : DiagGroup<"user-defined-literals">; +def UserDefinedWarnings : DiagGroup<"user-defined-warnings">; def Reorder : DiagGroup<"reorder">; def UndeclaredSelector : DiagGroup<"undeclared-selector">; def ImplicitAtomic : DiagGroup<"implicit-atomic-properties">; @@ -683,7 +684,8 @@ OverloadedVirtual, PrivateExtern, SelTypeCast, - ExternCCompat + ExternCCompat, + UserDefinedWarnings ]>; // Thread Safety warnings Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -2141,8 +2141,11 @@ 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_attr_cond_never_constant_expr : Error< + "%0 attribute expression never produces a constant expression">; +def err_diagnose_if_invalid_diagnostic_type : Error< + "invalid diagnostic type for 'diagnose_if'; use \"error\" or \"warning\" " + "instead">; def err_constexpr_body_no_return : Error< "no return statement in constexpr function">; def err_constexpr_return_missing_expr : Error< @@ -3366,7 +3369,9 @@ def note_ovl_candidate_has_pass_object_size_params: Note< "candidate address cannot be taken because parameter %0 has " "pass_object_size attribute">; -def note_ovl_candidate_disabled_by_enable_if_attr : Note< +def err_diagnose_if_succeeded : Error<"%0">; +def warn_diagnose_if_succeeded : Warning<"%0">, InGroup; +def note_ovl_candidate_disabled_by_function_cond_attr : Note< "candidate disabled: %0">; def note_ovl_candidate_disabled_by_extension : Note< "candidate disabled due to OpenCL extension">; @@ -4393,6 +4398,7 @@ def err_undeclared_use : Error<"use of undeclared %0">; def warn_deprecated : Warning<"%0 is deprecated">, InGroup; +def note_from_diagnose_if : Note<"from 'diagnose_if' attribute on %0:">; def warn_property_method_deprecated : Warning<"property access is using %0 method which is deprecated">, InGroup; Index: include/clang/Sema/Initialization.h =================================================================== --- include/clang/Sema/Initialization.h +++ include/clang/Sema/Initialization.h @@ -215,14 +215,14 @@ /// \brief Create the initialization entity for a parameter. static InitializedEntity InitializeParameter(ASTContext &Context, - ParmVarDecl *Parm) { + const ParmVarDecl *Parm) { return InitializeParameter(Context, Parm, Parm->getType()); } /// \brief Create the initialization entity for a parameter, but use /// another type. static InitializedEntity InitializeParameter(ASTContext &Context, - ParmVarDecl *Parm, + const ParmVarDecl *Parm, QualType Type) { bool Consumed = (Context.getLangOpts().ObjCAutoRefCount && Parm->hasAttr()); Index: include/clang/Sema/Overload.h =================================================================== --- include/clang/Sema/Overload.h +++ include/clang/Sema/Overload.h @@ -664,6 +664,26 @@ /// to be used while performing partial ordering of function templates. unsigned ExplicitCallArguments; + /// The number of diagnose_if attributes that this overload triggered. + /// If any of the triggered attributes are errors, this won't count + /// diagnose_if warnings. + unsigned NumTriggeredDiagnoseIfs = 0; + + /// Basically a TinyPtrVector that doesn't own the vector: + /// If NumTriggeredDiagnoseIfs is 0 or 1, this is a DiagnoseIfAttr *, + /// otherwise it's a pointer to an array of `NumTriggeredDiagnoseIfs` + /// DiagnoseIfAttr *s. + llvm::PointerUnion DiagnoseIfInfo; + + /// Gets an ArrayRef for the data at DiagnoseIfInfo. Note that this may give + /// you a pointer into DiagnoseIfInfo. + ArrayRef getDiagnoseIfInfo() const { + auto *Ptr = NumTriggeredDiagnoseIfs <= 1 + ? DiagnoseIfInfo.getAddrOfPtr1() + : DiagnoseIfInfo.get(); + return {Ptr, NumTriggeredDiagnoseIfs}; + } + union { DeductionFailureInfo DeductionFailure; @@ -728,17 +748,42 @@ SmallVector Candidates; llvm::SmallPtrSet Functions; - // Allocator for OverloadCandidate::Conversions. We store the first few - // elements inline to avoid allocation for small sets. - llvm::BumpPtrAllocator ConversionSequenceAllocator; + // Allocator for OverloadCandidate::Conversions and DiagnoseIfAttr* arrays. + // We store the first few of each of these inline to avoid allocation for + // small sets. + llvm::BumpPtrAllocator SlabAllocator; SourceLocation Loc; CandidateSetKind Kind; - unsigned NumInlineSequences; - llvm::AlignedCharArray - InlineSpace; + constexpr static unsigned NumInlineBytes = + 24 * sizeof(ImplicitConversionSequence); + unsigned NumInlineBytesUsed; + llvm::AlignedCharArray InlineSpace; + + /// If we have space, allocates from inline storage. Otherwise, allocates + /// from the slab allocator. + /// FIXME: It would probably be nice to have a SmallBumpPtrAllocator + /// instead. + template + T *slabAllocate(unsigned N) { + // It's simpler if this doesn't need to consider alignment. + static_assert(alignof(T) == alignof(void *), + "Only works for pointer-aligned types."); + static_assert(std::is_trivial::value || + std::is_same::value, + "Add destruction logic to OverloadCandidateSet::clear()."); + + unsigned NBytes = sizeof(T) * N; + if (NBytes > NumInlineBytes - NumInlineBytesUsed) + return SlabAllocator.Allocate(N); + char *FreeSpaceStart = InlineSpace.buffer + NumInlineBytesUsed; + assert(uintptr_t(FreeSpaceStart) % alignof(void *) == 0 && + "Misaligned storage!"); + + NumInlineBytesUsed += NBytes; + return reinterpret_cast(FreeSpaceStart); + } OverloadCandidateSet(const OverloadCandidateSet &) = delete; void operator=(const OverloadCandidateSet &) = delete; @@ -747,12 +792,17 @@ public: OverloadCandidateSet(SourceLocation Loc, CandidateSetKind CSK) - : Loc(Loc), Kind(CSK), NumInlineSequences(0) {} + : Loc(Loc), Kind(CSK), NumInlineBytesUsed(0) {} ~OverloadCandidateSet() { destroyCandidates(); } SourceLocation getLocation() const { return Loc; } CandidateSetKind getKind() const { return Kind; } + /// Make a DiagnoseIfAttr* array in a block of memory that will live for + /// as long as this OverloadCandidateSet. Returns a pointer to the start + /// of that array. + DiagnoseIfAttr **addDiagnoseIfComplaints(ArrayRef CA); + /// \brief Determine when this overload candidate will be new to the /// overload set. bool isNewCandidate(Decl *F) { @@ -775,19 +825,7 @@ Candidates.push_back(OverloadCandidate()); OverloadCandidate &C = Candidates.back(); - // Assign space from the inline array if there are enough free slots - // available. - if (NumConversions + NumInlineSequences <= 16) { - ImplicitConversionSequence *I = - (ImplicitConversionSequence *)InlineSpace.buffer; - C.Conversions = &I[NumInlineSequences]; - NumInlineSequences += NumConversions; - } else { - // Otherwise get memory from the allocator. - C.Conversions = ConversionSequenceAllocator - .Allocate(NumConversions); - } - + C.Conversions = slabAllocate(NumConversions); // Construct the new objects. for (unsigned i = 0; i != NumConversions; ++i) new (&C.Conversions[i]) ImplicitConversionSequence(); Index: include/clang/Sema/Sema.h =================================================================== --- include/clang/Sema/Sema.h +++ include/clang/Sema/Sema.h @@ -2523,14 +2523,14 @@ void AddMethodCandidate(DeclAccessPair FoundDecl, QualType ObjectType, Expr::Classification ObjectClassification, - ArrayRef Args, + Expr *ThisArg, ArrayRef Args, OverloadCandidateSet& CandidateSet, bool SuppressUserConversion = false); void AddMethodCandidate(CXXMethodDecl *Method, DeclAccessPair FoundDecl, CXXRecordDecl *ActingContext, QualType ObjectType, Expr::Classification ObjectClassification, - ArrayRef Args, + Expr *ThisArg, ArrayRef Args, OverloadCandidateSet& CandidateSet, bool SuppressUserConversions = false, bool PartialOverloading = false); @@ -2540,6 +2540,7 @@ TemplateArgumentListInfo *ExplicitTemplateArgs, QualType ObjectType, Expr::Classification ObjectClassification, + Expr *ThisArg, ArrayRef Args, OverloadCandidateSet& CandidateSet, bool SuppressUserConversions = false, @@ -2603,6 +2604,38 @@ EnableIfAttr *CheckEnableIf(FunctionDecl *Function, ArrayRef Args, bool MissingImplicitThis = false); + /// Check the diagnose_if attributes on the given function. Returns the + /// first succesful fatal attribute, or null if calling Function(Args) isn't + /// an error. + /// + /// This only considers ArgDependent DiagnoseIfAttrs. + /// + /// This will populate Nonfatal with all non-error DiagnoseIfAttrs that + /// succeed. If this function returns non-null, the contents of Nonfatal are + /// unspecified. + DiagnoseIfAttr * + checkArgDependentDiagnoseIf(FunctionDecl *Function, ArrayRef Args, + SmallVectorImpl &Nonfatal, + bool MissingImplicitThis = false, + Expr *ThisArg = nullptr); + + /// Check the diagnose_if expressions on the given function. Returns the + /// first succesful fatal attribute, or null if using Function isn't + /// an error. + /// + /// This ignores all ArgDependent DiagnoseIfAttrs. + /// + /// This will populate Nonfatal with all non-error DiagnoseIfAttrs that + /// succeed. If this function returns non-null, the contents of Nonfatal are + /// unspecified. + DiagnoseIfAttr * + checkArgIndependentDiagnoseIf(FunctionDecl *Function, + SmallVectorImpl &Nonfatal); + + /// Emits the diagnostic contained in the given DiagnoseIfAttr at Loc. Also + /// emits a note about the location of said attribute. + void emitDiagnoseIfDiagnostic(SourceLocation Loc, const DiagnoseIfAttr *DIA); + /// Returns whether the given function's address can be taken or not, /// optionally emitting a diagnostic if the address can't be taken. /// Index: lib/AST/ExprConstant.cpp =================================================================== --- lib/AST/ExprConstant.cpp +++ lib/AST/ExprConstant.cpp @@ -10357,10 +10357,25 @@ bool Expr::EvaluateWithSubstitution(APValue &Value, ASTContext &Ctx, const FunctionDecl *Callee, - ArrayRef Args) const { + ArrayRef Args, + const Expr *This) const { Expr::EvalStatus Status; EvalInfo Info(Ctx, Status, EvalInfo::EM_ConstantExpressionUnevaluated); + LValue ThisVal; + const LValue *ThisPtr = nullptr; + if (This) { +#ifndef NDEBUG + auto *MD = dyn_cast(Callee); + assert(MD && "Don't provide `this` for non-methods."); + assert(!MD->isStatic() && "Don't provide `this` for static methods."); +#endif + if (EvaluateObjectArgument(Info, This, ThisVal)) + ThisPtr = &ThisVal; + if (Info.EvalStatus.HasSideEffects) + return false; + } + ArgVector ArgValues(Args.size()); for (ArrayRef::iterator I = Args.begin(), E = Args.end(); I != E; ++I) { @@ -10373,7 +10388,7 @@ } // Build fake call to Callee. - CallStackFrame Frame(Info, Callee->getLocation(), Callee, /*This*/nullptr, + CallStackFrame Frame(Info, Callee->getLocation(), Callee, ThisPtr, ArgValues.data()); return Evaluate(Value, Info, this) && !Info.EvalStatus.HasSideEffects; } Index: lib/Sema/SemaDeclAttr.cpp =================================================================== --- lib/Sema/SemaDeclAttr.cpp +++ lib/Sema/SemaDeclAttr.cpp @@ -32,6 +32,7 @@ #include "clang/Sema/Lookup.h" #include "clang/Sema/Scope.h" #include "clang/Sema/SemaInternal.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/MathExtras.h" @@ -890,34 +891,117 @@ Attr.getAttributeSpellingListIndex())); } -static void handleEnableIfAttr(Sema &S, Decl *D, const AttributeList &Attr) { - S.Diag(Attr.getLoc(), diag::ext_clang_enable_if); - - Expr *Cond = Attr.getArgAsExpr(0); +static bool checkFunctionConditionAttr(Sema &S, Decl *D, + const AttributeList &Attr, + Expr *&Cond, StringRef &Msg) { + Cond = Attr.getArgAsExpr(0); if (!Cond->isTypeDependent()) { ExprResult Converted = S.PerformContextuallyConvertToBool(Cond); if (Converted.isInvalid()) - return; + return false; Cond = Converted.get(); } - StringRef Msg; if (!S.checkStringLiteralArgumentAttr(Attr, 1, Msg)) - return; + return false; + + if (Msg.empty()) + Msg = ""; SmallVector Diags; if (!Cond->isValueDependent() && !Expr::isPotentialConstantExprUnevaluated(Cond, cast(D), Diags)) { - S.Diag(Attr.getLoc(), diag::err_enable_if_never_constant_expr); + S.Diag(Attr.getLoc(), diag::err_attr_cond_never_constant_expr) + << Attr.getName(); for (const PartialDiagnosticAt &PDiag : Diags) S.Diag(PDiag.first, PDiag.second); + return false; + } + return true; +} + +static void handleEnableIfAttr(Sema &S, Decl *D, const AttributeList &Attr) { + S.Diag(Attr.getLoc(), diag::ext_clang_enable_if); + + Expr *Cond; + StringRef Msg; + if (checkFunctionConditionAttr(S, D, Attr, Cond, Msg)) + D->addAttr(::new (S.Context) + EnableIfAttr(Attr.getRange(), S.Context, Cond, Msg, + Attr.getAttributeSpellingListIndex())); +} + +namespace { +/// Determines if a given Expr references any of the given function's +/// ParmVarDecls, or the function's implicit `this` parameter (if applicable). +class ArgumentDependenceChecker + : public RecursiveASTVisitor { +#ifndef NDEBUG + const CXXRecordDecl *ClassType; +#endif + llvm::SmallPtrSet Parms; + bool Result; + +public: + ArgumentDependenceChecker(const FunctionDecl *FD) { +#ifndef NDEBUG + if (const auto *MD = dyn_cast(FD)) + ClassType = MD->getParent(); + else + ClassType = nullptr; +#endif + Parms.insert(FD->param_begin(), FD->param_end()); + } + + bool referencesArgs(Expr *E) { + Result = false; + TraverseStmt(E); + return Result; + } + + bool VisitCXXThisExpr(CXXThisExpr *E) { + assert(E->getType()->getPointeeCXXRecordDecl() == ClassType && + "`this` doesn't refer to the enclosing class?"); + Result = true; + return false; + } + + bool VisitDeclRefExpr(DeclRefExpr *DRE) { + if (const auto *PVD = dyn_cast(DRE->getDecl())) + if (Parms.count(PVD)) { + Result = true; + return false; + } + return true; + } +}; +} + +static void handleDiagnoseIfAttr(Sema &S, Decl *D, const AttributeList &Attr) { + S.Diag(Attr.getLoc(), diag::ext_clang_diagnose_if); + + Expr *Cond; + StringRef Msg; + if (!checkFunctionConditionAttr(S, D, Attr, Cond, Msg)) + return; + + StringRef DiagTypeStr; + if (!S.checkStringLiteralArgumentAttr(Attr, 2, DiagTypeStr)) + return; + + DiagnoseIfAttr::DiagnosticType DiagType; + if (!DiagnoseIfAttr::ConvertStrToDiagnosticType(DiagTypeStr, DiagType)) { + S.Diag(Attr.getArgAsExpr(2)->getLocStart(), + diag::err_diagnose_if_invalid_diagnostic_type); return; } - D->addAttr(::new (S.Context) - EnableIfAttr(Attr.getRange(), S.Context, Cond, Msg, - Attr.getAttributeSpellingListIndex())); + auto *FD = cast(D); + bool ArgDependent = ArgumentDependenceChecker(FD).referencesArgs(Cond); + D->addAttr(::new (S.Context) DiagnoseIfAttr( + Attr.getRange(), S.Context, Cond, Msg, DiagType, ArgDependent, FD, + Attr.getAttributeSpellingListIndex())); } static void handlePassObjectSizeAttr(Sema &S, Decl *D, @@ -5682,6 +5766,9 @@ case AttributeList::AT_EnableIf: handleEnableIfAttr(S, D, Attr); break; + case AttributeList::AT_DiagnoseIf: + handleDiagnoseIfAttr(S, D, Attr); + break; case AttributeList::AT_ExtVectorType: handleExtVectorTypeAttr(S, scope, D, Attr); break; Index: lib/Sema/SemaExpr.cpp =================================================================== --- lib/Sema/SemaExpr.cpp +++ lib/Sema/SemaExpr.cpp @@ -342,6 +342,7 @@ } // See if this is a deleted function. + SmallVector DiagnoseIfWarnings; if (FunctionDecl *FD = dyn_cast(D)) { if (FD->isDeleted()) { auto *Ctor = dyn_cast(FD); @@ -363,6 +364,12 @@ if (getLangOpts().CUDA && !CheckCUDACall(Loc, FD)) return true; + + if (const DiagnoseIfAttr *A = + checkArgIndependentDiagnoseIf(FD, DiagnoseIfWarnings)) { + emitDiagnoseIfDiagnostic(Loc, A); + return true; + } } // [OpenMP 4.0], 2.15 declare reduction Directive, Restrictions @@ -377,6 +384,10 @@ Diag(D->getLocation(), diag::note_entity_declared_at) << D; return true; } + + for (const auto *W : DiagnoseIfWarnings) + emitDiagnoseIfDiagnostic(Loc, W); + DiagnoseAvailabilityOfDecl(*this, D, Loc, UnknownObjCClass, ObjCPropertyAccess); @@ -5154,12 +5165,40 @@ return OverloadDecl; } -static bool isNumberOfArgsValidForCall(Sema &S, const FunctionDecl *Callee, - std::size_t NumArgs) { - if (S.TooManyArguments(Callee->getNumParams(), NumArgs, - /*PartialOverloading=*/false)) - return Callee->isVariadic(); - return Callee->getMinRequiredArguments() <= NumArgs; +static void checkDirectCallValidity(Sema &S, const Expr *Fn, + FunctionDecl *Callee, + MultiExprArg ArgExprs) { + // `Callee` (when called with ArgExprs) may be ill-formed. enable_if (and + // similar attributes) really don't like it when functions are called with an + // invalid number of args. + if (S.TooManyArguments(Callee->getNumParams(), ArgExprs.size(), + /*PartialOverloading=*/false) && + !Callee->isVariadic()) + return; + if (Callee->getMinRequiredArguments() > ArgExprs.size()) + return; + + if (const EnableIfAttr *Attr = S.CheckEnableIf(Callee, ArgExprs, true)) { + S.Diag(Fn->getLocStart(), + isa(Callee) + ? diag::err_ovl_no_viable_member_function_in_call + : diag::err_ovl_no_viable_function_in_call) + << Callee << Callee->getSourceRange(); + S.Diag(Callee->getLocation(), + diag::note_ovl_candidate_disabled_by_function_cond_attr) + << Attr->getCond()->getSourceRange() << Attr->getMessage(); + return; + } + + SmallVector Nonfatal; + if (const DiagnoseIfAttr *Attr = S.checkArgDependentDiagnoseIf( + Callee, ArgExprs, Nonfatal, /*MissingImplicitThis=*/true)) { + S.emitDiagnoseIfDiagnostic(Fn->getLocStart(), Attr); + return; + } + + for (const auto *W : Nonfatal) + S.emitDiagnoseIfDiagnostic(Fn->getLocStart(), W); } /// ActOnCallExpr - Handle a call to Fn with the specified array of arguments. @@ -5294,26 +5333,8 @@ if (getLangOpts().OpenCL && checkOpenCLDisabledDecl(*FD, *Fn)) return ExprError(); - - // CheckEnableIf assumes that the we're passing in a sane number of args for - // FD, but that doesn't always hold true here. This is because, in some - // cases, we'll emit a diag about an ill-formed function call, but then - // we'll continue on as if the function call wasn't ill-formed. So, if the - // number of args looks incorrect, don't do enable_if checks; we should've - // already emitted an error about the bad call. - if (FD->hasAttr() && - isNumberOfArgsValidForCall(*this, FD, ArgExprs.size())) { - 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(); - } - } + + checkDirectCallValidity(*this, Fn, FD, ArgExprs); } return BuildResolvedCallExpr(Fn, NDecl, LParenLoc, ArgExprs, RParenLoc, Index: lib/Sema/SemaLookup.cpp =================================================================== --- lib/Sema/SemaLookup.cpp +++ lib/Sema/SemaLookup.cpp @@ -2960,6 +2960,7 @@ if (CXXMethodDecl *M = dyn_cast(Cand->getUnderlyingDecl())) { if (SM == CXXCopyAssignment || SM == CXXMoveAssignment) AddMethodCandidate(M, Cand, RD, ThisTy, Classification, + /*ThisArg=*/nullptr, llvm::makeArrayRef(&Arg, NumArgs), OCS, true); else if (CtorInfo) AddOverloadCandidate(CtorInfo.Constructor, CtorInfo.FoundDecl, @@ -2972,7 +2973,7 @@ if (SM == CXXCopyAssignment || SM == CXXMoveAssignment) AddMethodTemplateCandidate( Tmpl, Cand, RD, nullptr, ThisTy, Classification, - llvm::makeArrayRef(&Arg, NumArgs), OCS, true); + /*ThisArg=*/nullptr, llvm::makeArrayRef(&Arg, NumArgs), OCS, true); else if (CtorInfo) AddTemplateOverloadCandidate( CtorInfo.ConstructorTmpl, CtorInfo.FoundDecl, nullptr, Index: lib/Sema/SemaOverload.cpp =================================================================== --- lib/Sema/SemaOverload.cpp +++ lib/Sema/SemaOverload.cpp @@ -29,6 +29,7 @@ #include "clang/Sema/Template.h" #include "clang/Sema/TemplateDeduction.h" #include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallString.h" @@ -821,12 +822,20 @@ void OverloadCandidateSet::clear() { destroyCandidates(); - ConversionSequenceAllocator.Reset(); - NumInlineSequences = 0; + // DiagnoseIfAttrs are just pointers, so we don't need to destroy them. + SlabAllocator.Reset(); + NumInlineBytesUsed = 0; Candidates.clear(); Functions.clear(); } +DiagnoseIfAttr ** +OverloadCandidateSet::addDiagnoseIfComplaints(ArrayRef CA) { + auto *DIA = slabAllocate(CA.size()); + std::uninitialized_copy(CA.begin(), CA.end(), DIA); + return DIA; +} + namespace { class UnbridgedCastsSet { struct Entry { @@ -5805,6 +5814,28 @@ return false; } +static void initDiagnoseIfComplaint(Sema &S, OverloadCandidateSet &CandidateSet, + OverloadCandidate &Candidate, + FunctionDecl *Function, + ArrayRef Args, + bool MissingImplicitThis = false, + Expr *ExplicitThis = nullptr) { + SmallVector Results; + if (DiagnoseIfAttr *DIA = S.checkArgDependentDiagnoseIf( + Function, Args, Results, MissingImplicitThis, ExplicitThis)) { + Results.clear(); + Results.push_back(DIA); + } + + Candidate.NumTriggeredDiagnoseIfs = Results.size(); + if (Results.empty()) + Candidate.DiagnoseIfInfo = nullptr; + else if (Results.size() == 1) + Candidate.DiagnoseIfInfo = Results[0]; + else + Candidate.DiagnoseIfInfo = CandidateSet.addDiagnoseIfComplaints(Results); +} + /// AddOverloadCandidate - Adds the given function to the set of /// candidate functions, using the given function call arguments. If /// @p SuppressUserConversions, then don't allow user-defined @@ -5838,8 +5869,8 @@ // is irrelevant. AddMethodCandidate(Method, FoundDecl, Method->getParent(), QualType(), Expr::Classification::makeSimpleLValue(), - Args, CandidateSet, SuppressUserConversions, - PartialOverloading); + /*ThisArg=*/nullptr, Args, CandidateSet, + SuppressUserConversions, PartialOverloading); return; } // We treat a constructor like a non-member function, since its object @@ -5974,6 +6005,8 @@ Candidate.FailureKind = ovl_fail_ext_disabled; return; } + + initDiagnoseIfComplaint(*this, CandidateSet, Candidate, Function, Args); } ObjCMethodDecl * @@ -6086,66 +6119,87 @@ return Result; } -EnableIfAttr *Sema::CheckEnableIf(FunctionDecl *Function, ArrayRef Args, - bool MissingImplicitThis) { - auto EnableIfAttrs = getOrderedEnableIfAttrs(Function); - if (EnableIfAttrs.empty()) - return nullptr; - - SFINAETrap Trap(*this); - SmallVector ConvertedArgs; - bool InitializationFailed = false; +static bool +convertArgsForAvailabilityChecks(Sema &S, FunctionDecl *Function, Expr *ThisArg, + ArrayRef Args, Sema::SFINAETrap &Trap, + bool MissingImplicitThis, Expr *&ConvertedThis, + SmallVectorImpl &ConvertedArgs) { + if (ThisArg) { + CXXMethodDecl *Method = cast(Function); + assert(!isa(Method) && + "Shouldn't have `this` for ctors!"); + assert(!Method->isStatic() && "Shouldn't have `this` for static methods!"); + ExprResult R = S.PerformObjectArgumentInitialization( + ThisArg, /*Qualifier=*/nullptr, Method, Method); + if (R.isInvalid()) + return false; + ConvertedThis = R.get(); + } else { + if (auto *MD = dyn_cast(Function)) { + (void)MD; + assert((MissingImplicitThis || MD->isStatic() || + isa(MD)) && + "Expected `this` for non-ctor instance methods"); + } + ConvertedThis = nullptr; + } // Ignore any variadic arguments. Converting them is pointless, since the - // user can't refer to them in the enable_if condition. + // user can't refer to them in the function condition. unsigned ArgSizeNoVarargs = std::min(Function->param_size(), Args.size()); // Convert the arguments. for (unsigned I = 0; I != ArgSizeNoVarargs; ++I) { ExprResult R; - if (I == 0 && !MissingImplicitThis && isa(Function) && - !cast(Function)->isStatic() && - !isa(Function)) { - CXXMethodDecl *Method = cast(Function); - R = PerformObjectArgumentInitialization(Args[0], /*Qualifier=*/nullptr, - Method, Method); - } else { - R = PerformCopyInitialization(InitializedEntity::InitializeParameter( - Context, Function->getParamDecl(I)), + R = S.PerformCopyInitialization(InitializedEntity::InitializeParameter( + S.Context, Function->getParamDecl(I)), SourceLocation(), Args[I]); - } - if (R.isInvalid()) { - InitializationFailed = true; - break; - } + if (R.isInvalid()) + return false; ConvertedArgs.push_back(R.get()); } - if (InitializationFailed || Trap.hasErrorOccurred()) - return EnableIfAttrs[0]; + if (Trap.hasErrorOccurred()) + return false; // Push default arguments if needed. if (!Function->isVariadic() && Args.size() < Function->getNumParams()) { for (unsigned i = Args.size(), e = Function->getNumParams(); i != e; ++i) { ParmVarDecl *P = Function->getParamDecl(i); - ExprResult R = PerformCopyInitialization( - InitializedEntity::InitializeParameter(Context, + ExprResult R = S.PerformCopyInitialization( + InitializedEntity::InitializeParameter(S.Context, Function->getParamDecl(i)), SourceLocation(), P->hasUninstantiatedDefaultArg() ? P->getUninstantiatedDefaultArg() : P->getDefaultArg()); - if (R.isInvalid()) { - InitializationFailed = true; - break; - } + if (R.isInvalid()) + return false; ConvertedArgs.push_back(R.get()); } - if (InitializationFailed || Trap.hasErrorOccurred()) - return EnableIfAttrs[0]; + if (Trap.hasErrorOccurred()) + return false; } + return true; +} + +EnableIfAttr *Sema::CheckEnableIf(FunctionDecl *Function, ArrayRef Args, + bool MissingImplicitThis) { + SmallVector EnableIfAttrs = + getOrderedEnableIfAttrs(Function); + if (EnableIfAttrs.empty()) + return nullptr; + + SFINAETrap Trap(*this); + SmallVector ConvertedArgs; + // FIXME: We should look into making enable_if late-parsed. + Expr *DiscardedThis; + if (!convertArgsForAvailabilityChecks( + *this, Function, /*ThisArg=*/nullptr, Args, Trap, + /*MissingImplicitThis=*/true, DiscardedThis, ConvertedArgs)) + return EnableIfAttrs[0]; for (auto *EIA : EnableIfAttrs) { APValue Result; @@ -6161,6 +6215,87 @@ return nullptr; } +static bool gatherDiagnoseIfAttrs(FunctionDecl *Function, bool ArgDependent, + SmallVectorImpl &Errors, + SmallVectorImpl &Nonfatal) { + for (auto *DIA : Function->specific_attrs()) + if (ArgDependent == DIA->getArgDependent()) { + if (DIA->isError()) + Errors.push_back(DIA); + else + Nonfatal.push_back(DIA); + } + + return !Errors.empty() || !Nonfatal.empty(); +} + +template +static DiagnoseIfAttr * +checkDiagnoseIfAttrsWith(const SmallVectorImpl &Errors, + SmallVectorImpl &Nonfatal, + CheckFn &&IsSuccessful) { + // Note that diagnose_if attributes are late-parsed, so they appear in the + // correct order (unlike enable_if attributes). + auto ErrAttr = llvm::find_if(Errors, IsSuccessful); + if (ErrAttr != Errors.end()) + return *ErrAttr; + + llvm::erase_if(Nonfatal, [&](DiagnoseIfAttr *A) { return !IsSuccessful(A); }); + return nullptr; +} + +DiagnoseIfAttr * +Sema::checkArgDependentDiagnoseIf(FunctionDecl *Function, ArrayRef Args, + SmallVectorImpl &Nonfatal, + bool MissingImplicitThis, + Expr *ThisArg) { + SmallVector Errors; + if (!gatherDiagnoseIfAttrs(Function, /*ArgDependent=*/true, Errors, Nonfatal)) + return nullptr; + + SFINAETrap Trap(*this); + SmallVector ConvertedArgs; + Expr *ConvertedThis; + if (!convertArgsForAvailabilityChecks(*this, Function, ThisArg, Args, Trap, + MissingImplicitThis, ConvertedThis, + ConvertedArgs)) + return nullptr; + + return checkDiagnoseIfAttrsWith(Errors, Nonfatal, [&](DiagnoseIfAttr *DIA) { + APValue Result; + // It's sane to use the same ConvertedArgs for any redecl of this function, + // since EvaluateWithSubstitution only cares about the position of each + // argument in the arg list, not the ParmVarDecl* it maps to. + if (!DIA->getCond()->EvaluateWithSubstitution( + Result, Context, DIA->getParent(), ConvertedArgs, ConvertedThis)) + return false; + return Result.isInt() && Result.getInt().getBoolValue(); + }); +} + +DiagnoseIfAttr *Sema::checkArgIndependentDiagnoseIf( + FunctionDecl *Function, SmallVectorImpl &Nonfatal) { + SmallVector Errors; + if (!gatherDiagnoseIfAttrs(Function, /*ArgDependent=*/false, Errors, + Nonfatal)) + return nullptr; + + return checkDiagnoseIfAttrsWith(Errors, Nonfatal, [&](DiagnoseIfAttr *DIA) { + bool Result; + return DIA->getCond()->EvaluateAsBooleanCondition(Result, Context) && + Result; + }); +} + +void Sema::emitDiagnoseIfDiagnostic(SourceLocation Loc, + const DiagnoseIfAttr *DIA) { + auto Code = DIA->isError() ? diag::err_diagnose_if_succeeded + : diag::warn_diagnose_if_succeeded; + Diag(Loc, Code) << DIA->getMessage(); + Diag(DIA->getLocation(), diag::note_from_diagnose_if) + << DIA->getParent() << DIA->getCond()->getSourceRange(); +} + /// \brief Add all of the function declarations in the given function set to /// the overload candidate set. void Sema::AddFunctionCandidates(const UnresolvedSetImpl &Fns, @@ -6176,7 +6311,7 @@ AddMethodCandidate(cast(FD), F.getPair(), cast(FD)->getParent(), Args[0]->getType(), Args[0]->Classify(Context), - Args.slice(1), CandidateSet, + Args[0], Args.slice(1), CandidateSet, SuppressUserConversions, PartialOverloading); else AddOverloadCandidate(FD, F.getPair(), Args, CandidateSet, @@ -6185,13 +6320,12 @@ FunctionTemplateDecl *FunTmpl = cast(D); if (isa(FunTmpl->getTemplatedDecl()) && !cast(FunTmpl->getTemplatedDecl())->isStatic()) - AddMethodTemplateCandidate(FunTmpl, F.getPair(), - cast(FunTmpl->getDeclContext()), - ExplicitTemplateArgs, - Args[0]->getType(), - Args[0]->Classify(Context), Args.slice(1), - CandidateSet, SuppressUserConversions, - PartialOverloading); + AddMethodTemplateCandidate( + FunTmpl, F.getPair(), + cast(FunTmpl->getDeclContext()), + ExplicitTemplateArgs, Args[0]->getType(), + Args[0]->Classify(Context), Args[0], Args.slice(1), CandidateSet, + SuppressUserConversions, PartialOverloading); else AddTemplateOverloadCandidate(FunTmpl, F.getPair(), ExplicitTemplateArgs, Args, @@ -6206,6 +6340,7 @@ void Sema::AddMethodCandidate(DeclAccessPair FoundDecl, QualType ObjectType, Expr::Classification ObjectClassification, + Expr *ThisArg, ArrayRef Args, OverloadCandidateSet& CandidateSet, bool SuppressUserConversions) { @@ -6221,12 +6356,12 @@ AddMethodTemplateCandidate(TD, FoundDecl, ActingContext, /*ExplicitArgs*/ nullptr, ObjectType, ObjectClassification, - Args, CandidateSet, + ThisArg, Args, CandidateSet, SuppressUserConversions); } else { AddMethodCandidate(cast(Decl), FoundDecl, ActingContext, ObjectType, ObjectClassification, - Args, + ThisArg, Args, CandidateSet, SuppressUserConversions); } } @@ -6242,7 +6377,7 @@ Sema::AddMethodCandidate(CXXMethodDecl *Method, DeclAccessPair FoundDecl, CXXRecordDecl *ActingContext, QualType ObjectType, Expr::Classification ObjectClassification, - ArrayRef Args, + Expr *ThisArg, ArrayRef Args, OverloadCandidateSet &CandidateSet, bool SuppressUserConversions, bool PartialOverloading) { @@ -6359,6 +6494,9 @@ Candidate.DeductionFailure.Data = FailedAttr; return; } + + initDiagnoseIfComplaint(*this, CandidateSet, Candidate, Method, Args, + /*MissingImplicitThis=*/!ThisArg, ThisArg); } /// \brief Add a C++ member function template as a candidate to the candidate @@ -6371,6 +6509,7 @@ TemplateArgumentListInfo *ExplicitTemplateArgs, QualType ObjectType, Expr::Classification ObjectClassification, + Expr *ThisArg, ArrayRef Args, OverloadCandidateSet& CandidateSet, bool SuppressUserConversions, @@ -6411,8 +6550,9 @@ assert(isa(Specialization) && "Specialization is not a member function?"); AddMethodCandidate(cast(Specialization), FoundDecl, - ActingContext, ObjectType, ObjectClassification, Args, - CandidateSet, SuppressUserConversions, PartialOverloading); + ActingContext, ObjectType, ObjectClassification, + /*ThisArg=*/ThisArg, Args, CandidateSet, + SuppressUserConversions, PartialOverloading); } /// \brief Add a C++ function template specialization as a candidate @@ -6668,6 +6808,8 @@ Candidate.DeductionFailure.Data = FailedAttr; return; } + + initDiagnoseIfComplaint(*this, CandidateSet, Candidate, Conversion, None, false, From); } /// \brief Adds a conversion function template specialization @@ -6820,6 +6962,8 @@ Candidate.DeductionFailure.Data = FailedAttr; return; } + + initDiagnoseIfComplaint(*this, CandidateSet, Candidate, Conversion, None); } /// \brief Add overload candidates for overloaded operators that are @@ -6868,10 +7012,8 @@ Oper != OperEnd; ++Oper) AddMethodCandidate(Oper.getPair(), Args[0]->getType(), - Args[0]->Classify(Context), - Args.slice(1), - CandidateSet, - /* SuppressUserConversions = */ false); + Args[0]->Classify(Context), Args[0], Args.slice(1), + CandidateSet, /*SuppressUserConversions=*/false); } } @@ -8902,6 +9044,17 @@ } } +static bool isCandidateUnavailableDueToDiagnoseIf(const OverloadCandidate &OC) { + ArrayRef Info = OC.getDiagnoseIfInfo(); + if (!Info.empty() && Info[0]->isError()) + return true; + + assert(llvm::all_of(Info, + [](const DiagnoseIfAttr *A) { return !A->isError(); }) && + "DiagnoseIf info shouldn't have mixed warnings and errors."); + return false; +} + /// \brief Computes the best viable function (C++ 13.3.3) /// within an overload candidate set. /// @@ -8980,13 +9133,19 @@ // Best is the best viable function. if (Best->Function && (Best->Function->isDeleted() || - S.isFunctionConsideredUnavailable(Best->Function))) + S.isFunctionConsideredUnavailable(Best->Function) || + isCandidateUnavailableDueToDiagnoseIf(*Best))) return OR_Deleted; if (!EquivalentCands.empty()) S.diagnoseEquivalentInternalLinkageDeclarations(Loc, Best->Function, EquivalentCands); + for (const auto *W : Best->getDiagnoseIfInfo()) { + assert(W->isWarning() && "Errors should've been caught earlier!"); + S.emitDiagnoseIfDiagnostic(Loc, W); + } + return OR_Success; } @@ -9825,7 +9984,7 @@ EnableIfAttr *Attr = static_cast(Cand->DeductionFailure.Data); S.Diag(Callee->getLocation(), - diag::note_ovl_candidate_disabled_by_enable_if_attr) + diag::note_ovl_candidate_disabled_by_function_cond_attr) << Attr->getCond()->getSourceRange() << Attr->getMessage(); } @@ -9855,21 +10014,28 @@ FunctionDecl *Fn = Cand->Function; // Note deleted candidates, but only if they're viable. - if (Cand->Viable && (Fn->isDeleted() || - S.isFunctionConsideredUnavailable(Fn))) { - std::string FnDesc; - OverloadCandidateKind FnKind = + if (Cand->Viable) { + if (Fn->isDeleted() || S.isFunctionConsideredUnavailable(Fn)) { + std::string FnDesc; + OverloadCandidateKind FnKind = ClassifyOverloadCandidate(S, Cand->FoundDecl, Fn, FnDesc); - S.Diag(Fn->getLocation(), diag::note_ovl_candidate_deleted) - << FnKind << FnDesc - << (Fn->isDeleted() ? (Fn->isDeletedAsWritten() ? 1 : 2) : 0); - MaybeEmitInheritedConstructorNote(S, Cand->FoundDecl); - return; - } + S.Diag(Fn->getLocation(), diag::note_ovl_candidate_deleted) + << FnKind << FnDesc + << (Fn->isDeleted() ? (Fn->isDeletedAsWritten() ? 1 : 2) : 0); + MaybeEmitInheritedConstructorNote(S, Cand->FoundDecl); + return; + } + if (isCandidateUnavailableDueToDiagnoseIf(*Cand)) { + auto *A = Cand->DiagnoseIfInfo.get(); + assert(A->isError() && "Non-error diagnose_if disables a candidate?"); + S.Diag(Cand->Function->getLocation(), + diag::note_ovl_candidate_disabled_by_function_cond_attr) + << A->getCond()->getSourceRange() << A->getMessage(); + return; + } - // We don't really have anything else to say about viable candidates. - if (Cand->Viable) { + // We don't really have anything else to say about viable candidates. S.NoteOverloadCandidate(Cand->FoundDecl, Fn); return; } @@ -12417,6 +12583,16 @@ TemplateArgs = &TemplateArgsBuffer; } + // Poor-programmer's Lazy; isImplicitAccess requires stripping + // parens/casts, which would be nice to avoid potentially doing multiple + // times. + llvm::Optional UnresolvedBase; + auto GetUnresolvedBase = [&] { + if (!UnresolvedBase.hasValue()) + UnresolvedBase = + UnresExpr->isImplicitAccess() ? nullptr : UnresExpr->getBase(); + return *UnresolvedBase; + }; for (UnresolvedMemberExpr::decls_iterator I = UnresExpr->decls_begin(), E = UnresExpr->decls_end(); I != E; ++I) { @@ -12437,14 +12613,15 @@ continue; AddMethodCandidate(Method, I.getPair(), ActingDC, ObjectType, - ObjectClassification, Args, CandidateSet, + ObjectClassification, + /*ThisArg=*/GetUnresolvedBase(), Args, CandidateSet, /*SuppressUserConversions=*/false); } else { - AddMethodTemplateCandidate(cast(Func), - I.getPair(), ActingDC, TemplateArgs, - ObjectType, ObjectClassification, - Args, CandidateSet, - /*SuppressUsedConversions=*/false); + AddMethodTemplateCandidate( + cast(Func), I.getPair(), ActingDC, + TemplateArgs, ObjectType, ObjectClassification, + /*ThisArg=*/GetUnresolvedBase(), Args, CandidateSet, + /*SuppressUsedConversions=*/false); } } @@ -12557,10 +12734,20 @@ diag::err_ovl_no_viable_member_function_in_call) << Method << Method->getSourceRange(); Diag(Method->getLocation(), - diag::note_ovl_candidate_disabled_by_enable_if_attr) + diag::note_ovl_candidate_disabled_by_function_cond_attr) << Attr->getCond()->getSourceRange() << Attr->getMessage(); return ExprError(); } + + SmallVector Nonfatal; + if (const DiagnoseIfAttr *Attr = checkArgDependentDiagnoseIf( + Method, Args, Nonfatal, false, MemE->getBase())) { + emitDiagnoseIfDiagnostic(MemE->getMemberLoc(), Attr); + return ExprError(); + } + + for (const auto *Attr : Nonfatal) + emitDiagnoseIfDiagnostic(MemE->getMemberLoc(), Attr); } if ((isa(CurContext) || @@ -12640,7 +12827,7 @@ Oper != OperEnd; ++Oper) { AddMethodCandidate(Oper.getPair(), Object.get()->getType(), Object.get()->Classify(Context), - Args, CandidateSet, + Object.get(), Args, CandidateSet, /*SuppressUserConversions=*/ false); } @@ -12916,7 +13103,8 @@ for (LookupResult::iterator Oper = R.begin(), OperEnd = R.end(); Oper != OperEnd; ++Oper) { AddMethodCandidate(Oper.getPair(), Base->getType(), Base->Classify(Context), - None, CandidateSet, /*SuppressUserConversions=*/false); + Base, None, CandidateSet, + /*SuppressUserConversions=*/false); } bool HadMultipleCandidates = (CandidateSet.size() > 1); Index: lib/Sema/SemaTemplateInstantiateDecl.cpp =================================================================== --- lib/Sema/SemaTemplateInstantiateDecl.cpp +++ lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -168,39 +168,58 @@ Aligned->getSpellingListIndex()); } -static void instantiateDependentEnableIfAttr( +static Expr *instantiateDependentFunctionAttrCondition( Sema &S, const MultiLevelTemplateArgumentList &TemplateArgs, - const EnableIfAttr *A, const Decl *Tmpl, Decl *New) { + const Attr *A, Expr *OldCond, const Decl *Tmpl, FunctionDecl *New) { Expr *Cond = nullptr; { EnterExpressionEvaluationContext Unevaluated(S, Sema::Unevaluated); - ExprResult Result = S.SubstExpr(A->getCond(), TemplateArgs); + ExprResult Result = S.SubstExpr(OldCond, TemplateArgs); if (Result.isInvalid()) - return; + return nullptr; Cond = Result.getAs(); } if (!Cond->isTypeDependent()) { ExprResult Converted = S.PerformContextuallyConvertToBool(Cond); if (Converted.isInvalid()) - return; + return nullptr; Cond = Converted.get(); } SmallVector Diags; - if (A->getCond()->isValueDependent() && !Cond->isValueDependent() && - !Expr::isPotentialConstantExprUnevaluated(Cond, cast(New), - 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; + if (OldCond->isValueDependent() && !Cond->isValueDependent() && + !Expr::isPotentialConstantExprUnevaluated(Cond, New, Diags)) { + S.Diag(A->getLocation(), diag::err_attr_cond_never_constant_expr) << A; + for (const auto &P : Diags) + S.Diag(P.first, P.second); + return nullptr; } + return Cond; +} - EnableIfAttr *EIA = new (S.getASTContext()) - EnableIfAttr(A->getLocation(), S.getASTContext(), Cond, - A->getMessage(), - A->getSpellingListIndex()); - New->addAttr(EIA); +static void instantiateDependentEnableIfAttr( + Sema &S, const MultiLevelTemplateArgumentList &TemplateArgs, + const EnableIfAttr *EIA, const Decl *Tmpl, FunctionDecl *New) { + Expr *Cond = instantiateDependentFunctionAttrCondition( + S, TemplateArgs, EIA, EIA->getCond(), Tmpl, New); + + if (Cond) + New->addAttr(new (S.getASTContext()) EnableIfAttr( + EIA->getLocation(), S.getASTContext(), Cond, EIA->getMessage(), + EIA->getSpellingListIndex())); +} + +static void instantiateDependentDiagnoseIfAttr( + Sema &S, const MultiLevelTemplateArgumentList &TemplateArgs, + const DiagnoseIfAttr *DIA, const Decl *Tmpl, FunctionDecl *New) { + Expr *Cond = instantiateDependentFunctionAttrCondition( + S, TemplateArgs, DIA, DIA->getCond(), Tmpl, New); + + if (Cond) + New->addAttr(new (S.getASTContext()) DiagnoseIfAttr( + DIA->getLocation(), S.getASTContext(), Cond, DIA->getMessage(), + DIA->getDiagnosticType(), DIA->getArgDependent(), New, + DIA->getSpellingListIndex())); } // Constructs and adds to New a new instance of CUDALaunchBoundsAttr using @@ -334,7 +353,13 @@ if (const auto *EnableIf = dyn_cast(TmplAttr)) { instantiateDependentEnableIfAttr(*this, TemplateArgs, EnableIf, Tmpl, - New); + cast(New)); + continue; + } + + if (const auto *DiagnoseIf = dyn_cast(TmplAttr)) { + instantiateDependentDiagnoseIfAttr(*this, TemplateArgs, DiagnoseIf, Tmpl, + cast(New)); continue; } Index: test/Sema/diagnose_if.c =================================================================== --- /dev/null +++ test/Sema/diagnose_if.c @@ -0,0 +1,152 @@ +// RUN: %clang_cc1 %s -verify -fno-builtin + +#define _diagnose_if(...) __attribute__((diagnose_if(__VA_ARGS__))) + +void failure() _diagnose_if(); // expected-error{{exactly 3 arguments}} +void failure() _diagnose_if(0); // expected-error{{exactly 3 arguments}} +void failure() _diagnose_if(0, ""); // expected-error{{exactly 3 arguments}} +void failure() _diagnose_if(0, "", "error", 1); // expected-error{{exactly 3 arguments}} +void failure() _diagnose_if(0, 0, "error"); // expected-error{{requires a string}} +void failure() _diagnose_if(0, "", "invalid"); // expected-error{{invalid diagnostic type for 'diagnose_if'; use "error" or "warning" instead}} +void failure() _diagnose_if(0, "", "ERROR"); // expected-error{{invalid diagnostic type}} +void failure(int a) _diagnose_if(a, "", ""); // expected-error{{invalid diagnostic type}} +void failure() _diagnose_if(a, "", ""); // expected-error{{undeclared identifier 'a'}} + +int globalVar; +void never_constant() _diagnose_if(globalVar, "", "error"); // expected-error{{'diagnose_if' attribute expression never produces a constant expression}} expected-note{{subexpression not valid}} +void never_constant() _diagnose_if(globalVar, "", "warning"); // expected-error{{'diagnose_if' attribute expression never produces a constant expression}} expected-note{{subexpression not valid}} + +int alwaysok(int q) _diagnose_if(0, "", "error"); +int neverok(int q) _diagnose_if(1, "oh no", "error"); // expected-note 5{{from 'diagnose_if' attribute on 'neverok'}} +int alwayswarn(int q) _diagnose_if(1, "oh no", "warning"); // expected-note 5{{from 'diagnose_if' attribute}} +int neverwarn(int q) _diagnose_if(0, "", "warning"); + +void runConstant() { + int m; + alwaysok(0); + alwaysok(1); + alwaysok(m); + + { + int (*pok)(int) = alwaysok; + pok = &alwaysok; + } + + neverok(0); // expected-error{{oh no}} + neverok(1); // expected-error{{oh no}} + neverok(m); // expected-error{{oh no}} + { + int (*pok)(int) = neverok; // expected-error{{oh no}} + pok = &neverok; // expected-error{{oh no}} + } + + alwayswarn(0); // expected-warning{{oh no}} + alwayswarn(1); // expected-warning{{oh no}} + alwayswarn(m); // expected-warning{{oh no}} + { + int (*pok)(int) = alwayswarn; // expected-warning{{oh no}} + pok = &alwayswarn; // expected-warning{{oh no}} + } + + neverwarn(0); + neverwarn(1); + neverwarn(m); + { + int (*pok)(int) = neverwarn; + pok = &neverwarn; + } +} + +int abs(int q) _diagnose_if(q >= 0, "redundant abs call", "error"); //expected-note{{from 'diagnose_if'}} +void runVariable() { + int m; + abs(-1); + abs(1); // expected-error{{redundant abs call}} + abs(m); + + int (*pabs)(int) = abs; + pabs = &abs; +} + +#define _overloadable __attribute__((overloadable)) + +int ovl1(const char *n) _overloadable _diagnose_if(n, "oh no", "error"); // expected-note{{oh no}} +int ovl1(void *m) _overloadable; // expected-note{{candidate function}} + +int ovl2(const char *n) _overloadable _diagnose_if(n, "oh no", "error"); // expected-note{{candidate function}} +int ovl2(char *m) _overloadable; // expected-note{{candidate function}} +void overloadsYay() { + ovl1((void *)0); + ovl1(""); // expected-error{{call to unavailable function}} + + ovl2((void *)0); // expected-error{{ambiguous}} +} + +void errorWarnDiagnose1() _diagnose_if(1, "oh no", "error") // expected-note{{from 'diagnose_if'}} + _diagnose_if(1, "nop", "warning"); +void errorWarnDiagnose2() _diagnose_if(1, "oh no", "error") // expected-note{{from 'diagnose_if'}} + _diagnose_if(1, "nop", "error"); +void errorWarnDiagnose3() _diagnose_if(1, "nop", "warning") + _diagnose_if(1, "oh no", "error"); // expected-note{{from 'diagnose_if'}} + +void errorWarnDiagnoseArg1(int a) _diagnose_if(a == 1, "oh no", "error") // expected-note{{from 'diagnose_if'}} + _diagnose_if(a == 1, "nop", "warning"); +void errorWarnDiagnoseArg2(int a) _diagnose_if(a == 1, "oh no", "error") // expected-note{{from 'diagnose_if'}} + _diagnose_if(a == 1, "nop", "error"); +void errorWarnDiagnoseArg3(int a) _diagnose_if(a == 1, "nop", "warning") + _diagnose_if(a == 1, "oh no", "error"); // expected-note{{from 'diagnose_if'}} + +void runErrorWarnDiagnose() { + errorWarnDiagnose1(); // expected-error{{oh no}} + errorWarnDiagnose2(); // expected-error{{oh no}} + errorWarnDiagnose3(); // expected-error{{oh no}} + + errorWarnDiagnoseArg1(1); // expected-error{{oh no}} + errorWarnDiagnoseArg2(1); // expected-error{{oh no}} + errorWarnDiagnoseArg3(1); // expected-error{{oh no}} +} + +void warnWarnDiagnose() _diagnose_if(1, "oh no!", "warning") _diagnose_if(1, "foo", "warning"); // expected-note 2{{from 'diagnose_if'}} +void runWarnWarnDiagnose() { + warnWarnDiagnose(); // expected-warning{{oh no!}} expected-warning{{foo}} +} + +void declsStackErr1(int a) _diagnose_if(a & 1, "decl1", "error"); // expected-note 2{{from 'diagnose_if'}} +void declsStackErr1(int a) _diagnose_if(a & 2, "decl2", "error"); // expected-note{{from 'diagnose_if'}} +void declsStackErr2(); +void declsStackErr2() _diagnose_if(1, "complaint", "error"); // expected-note{{from 'diagnose_if'}} +void declsStackErr3() _diagnose_if(1, "complaint", "error"); // expected-note{{from 'diagnose_if'}} +void declsStackErr3(); +void runDeclsStackErr() { + declsStackErr1(0); + declsStackErr1(1); // expected-error{{decl1}} + declsStackErr1(2); // expected-error{{decl2}} + declsStackErr1(3); // expected-error{{decl1}} + declsStackErr2(); // expected-error{{complaint}} + declsStackErr3(); // expected-error{{complaint}} +} + +void declsStackWarn1(int a) _diagnose_if(a & 1, "decl1", "warning"); // expected-note 2{{from 'diagnose_if'}} +void declsStackWarn1(int a) _diagnose_if(a & 2, "decl2", "warning"); // expected-note 2{{from 'diagnose_if'}} +void declsStackWarn2(); +void declsStackWarn2() _diagnose_if(1, "complaint", "warning"); // expected-note{{from 'diagnose_if'}} +void declsStackWarn3() _diagnose_if(1, "complaint", "warning"); // expected-note{{from 'diagnose_if'}} +void declsStackWarn3(); +void runDeclsStackWarn() { + declsStackWarn1(0); + declsStackWarn1(1); // expected-warning{{decl1}} + declsStackWarn1(2); // expected-warning{{decl2}} + declsStackWarn1(3); // expected-warning{{decl1}} expected-warning{{decl2}} + declsStackWarn2(); // expected-warning{{complaint}} + declsStackWarn3(); // expected-warning{{complaint}} +} + +void noMsg(int n) _diagnose_if(n, "", "warning"); // expected-note{{from 'diagnose_if'}} +void runNoMsg() { + noMsg(1); // expected-warning{{}} +} + +void alwaysWarnWithArg(int a) _diagnose_if(1 || a, "alwaysWarn", "warning"); // expected-note{{from 'diagnose_if'}} +void runAlwaysWarnWithArg(int a) { + alwaysWarnWithArg(a); // expected-warning{{alwaysWarn}} +} Index: test/SemaCXX/diagnose_if.cpp =================================================================== --- /dev/null +++ test/SemaCXX/diagnose_if.cpp @@ -0,0 +1,460 @@ +// RUN: %clang_cc1 %s -verify -fno-builtin -std=c++14 + +#define _diagnose_if(...) __attribute__((diagnose_if(__VA_ARGS__))) + +namespace type_dependent { +template +void neverok() _diagnose_if(!T(), "oh no", "error") {} // expected-note 4{{from 'diagnose_if'}} + +template +void alwaysok() _diagnose_if(T(), "oh no", "error") {} + +template +void alwayswarn() _diagnose_if(!T(), "oh no", "warning") {} // expected-note 4{{from 'diagnose_if'}} + +template +void neverwarn() _diagnose_if(T(), "oh no", "warning") {} + +void runAll() { + alwaysok(); + alwaysok(); + + { + void (*pok)() = alwaysok; + pok = &alwaysok; + } + + neverok(); // expected-error{{oh no}} + neverok(); // expected-error{{oh no}} + + { + void (*pok)() = neverok; // expected-error{{oh no}} + } + { + void (*pok)(); + pok = &neverok; // expected-error{{oh no}} + } + + alwayswarn(); // expected-warning{{oh no}} + alwayswarn(); // expected-warning{{oh no}} + { + void (*pok)() = alwayswarn; // expected-warning{{oh no}} + pok = &alwayswarn; // expected-warning{{oh no}} + } + + neverwarn(); + neverwarn(); + { + void (*pok)() = neverwarn; + pok = &neverwarn; + } +} + +template +void errorIf(T a) _diagnose_if(T() != a, "oh no", "error") {} // expected-note {{candidate disabled: oh no}} + +template +void warnIf(T a) _diagnose_if(T() != a, "oh no", "warning") {} // expected-note {{from 'diagnose_if'}} + +void runIf() { + errorIf(0); + errorIf(1); // expected-error{{call to unavailable function}} + + warnIf(0); + warnIf(1); // expected-warning{{oh no}} +} +} + +namespace value_dependent { +template +void neverok() _diagnose_if(N == 0 || N != 0, "oh no", "error") {} // expected-note 4{{from 'diagnose_if'}} + +template +void alwaysok() _diagnose_if(N == 0 && N != 0, "oh no", "error") {} + +template +void alwayswarn() _diagnose_if(N == 0 || N != 0, "oh no", "warning") {} // expected-note 4{{from 'diagnose_if'}} + +template +void neverwarn() _diagnose_if(N == 0 && N != 0, "oh no", "warning") {} + +void runAll() { + alwaysok<0>(); + alwaysok<1>(); + + { + void (*pok)() = alwaysok<0>; + pok = &alwaysok<0>; + } + + neverok<0>(); // expected-error{{oh no}} + neverok<1>(); // expected-error{{oh no}} + + { + void (*pok)() = neverok<0>; // expected-error{{oh no}} + } + { + void (*pok)(); + pok = &neverok<0>; // expected-error{{oh no}} + } + + alwayswarn<0>(); // expected-warning{{oh no}} + alwayswarn<1>(); // expected-warning{{oh no}} + { + void (*pok)() = alwayswarn<0>; // expected-warning{{oh no}} + pok = &alwayswarn<0>; // expected-warning{{oh no}} + } + + neverwarn<0>(); + neverwarn<1>(); + { + void (*pok)() = neverwarn<0>; + pok = &neverwarn<0>; + } +} + +template +void errorIf(int a) _diagnose_if(N != a, "oh no", "error") {} // expected-note {{candidate disabled: oh no}} + +template +void warnIf(int a) _diagnose_if(N != a, "oh no", "warning") {} // expected-note {{from 'diagnose_if'}} + +void runIf() { + errorIf<0>(0); + errorIf<0>(1); // expected-error{{call to unavailable function}} + + warnIf<0>(0); + warnIf<0>(1); // expected-warning{{oh no}} +} +} + +namespace no_overload_interaction { +void foo(int) _diagnose_if(1, "oh no", "error"); // expected-note{{from 'diagnose_if'}} +void foo(short); + +void bar(int); +void bar(short) _diagnose_if(1, "oh no", "error"); + +void fooArg(int a) _diagnose_if(a, "oh no", "error"); // expected-note{{candidate disabled: oh no}} +void fooArg(short); // expected-note{{candidate function}} + +void barArg(int); +void barArg(short a) _diagnose_if(a, "oh no", "error"); + +void runAll() { + foo(1); // expected-error{{oh no}} + bar(1); + + fooArg(1); // expected-error{{call to unavailable function}} + barArg(1); + + auto p = foo; // expected-error{{incompatible initializer of type ''}} +} +} + +namespace with_default_args { +void foo(int a = 0) _diagnose_if(a, "oh no", "warning"); // expected-note 1{{from 'diagnose_if'}} +void bar(int a = 1) _diagnose_if(a, "oh no", "warning"); // expected-note 2{{from 'diagnose_if'}} + +void runAll() { + foo(); + foo(0); + foo(1); // expected-warning{{oh no}} + + bar(); // expected-warning{{oh no}} + bar(0); + bar(1); // expected-warning{{oh no}} +} +} + +namespace naked_mem_expr { +struct Foo { + void foo(int a) _diagnose_if(a, "should warn", "warning"); // expected-note{{from 'diagnose_if'}} + void bar(int a) _diagnose_if(a, "oh no", "error"); // expected-note{{from 'diagnose_if'}} +}; + +void runFoo() { + Foo().foo(0); + Foo().foo(1); // expected-warning{{should warn}} + + Foo().bar(0); + Foo().bar(1); // expected-error{{oh no}} +} +} + +namespace class_template { +template +struct Errors { + void foo(int i) _diagnose_if(i, "bad i", "error"); // expected-note{{from 'diagnose_if'}} + void bar(int i) _diagnose_if(i != T(), "bad i", "error"); // expected-note{{from 'diagnose_if'}} + + void fooOvl(int i) _diagnose_if(i, "int bad i", "error"); // expected-note 2{{int bad i}} + void fooOvl(short i) _diagnose_if(i, "short bad i", "error"); // expected-note 2{{short bad i}} + + void barOvl(int i) _diagnose_if(i != T(), "int bad i", "error"); // expected-note 2{{int bad i}} + void barOvl(short i) _diagnose_if(i != T(), "short bad i", "error"); // expected-note 2{{short bad i}} +}; + +void runErrors() { + Errors().foo(0); + Errors().foo(1); // expected-error{{bad i}} + + Errors().bar(0); + Errors().bar(1); // expected-error{{bad i}} + + Errors().fooOvl(0); + Errors().fooOvl(1); // expected-error{{call to unavailable}} + Errors().fooOvl(short(0)); + Errors().fooOvl(short(1)); // expected-error{{call to unavailable}} + + Errors().barOvl(0); + Errors().barOvl(1); // expected-error{{call to unavailable}} + Errors().barOvl(short(0)); + Errors().barOvl(short(1)); // expected-error{{call to unavailable}} +} + +template +struct Warnings { + void foo(int i) _diagnose_if(i, "bad i", "warning"); // expected-note{{from 'diagnose_if'}} + void bar(int i) _diagnose_if(i != T(), "bad i", "warning"); // expected-note{{from 'diagnose_if'}} + + void fooOvl(int i) _diagnose_if(i, "int bad i", "warning"); // expected-note{{from 'diagnose_if'}} + void fooOvl(short i) _diagnose_if(i, "short bad i", "warning"); // expected-note{{from 'diagnose_if'}} + + void barOvl(int i) _diagnose_if(i != T(), "int bad i", "warning"); // expected-note{{from 'diagnose_if'}} + void barOvl(short i) _diagnose_if(i != T(), "short bad i", "warning"); // expected-note{{from 'diagnose_if'}} +}; + +void runWarnings() { + Warnings().foo(0); + Warnings().foo(1); // expected-warning{{bad i}} + + Warnings().bar(0); + Warnings().bar(1); // expected-warning{{bad i}} + + Warnings().fooOvl(0); + Warnings().fooOvl(1); // expected-warning{{int bad i}} + Warnings().fooOvl(short(0)); + Warnings().fooOvl(short(1)); // expected-warning{{short bad i}} + + Warnings().barOvl(0); + Warnings().barOvl(1); // expected-warning{{int bad i}} + Warnings().barOvl(short(0)); + Warnings().barOvl(short(1)); // expected-warning{{short bad i}} +} +} + +namespace template_specialization { +template +struct Foo { + void foo() _diagnose_if(1, "override me", "error"); // expected-note{{from 'diagnose_if'}} + void bar(int i) _diagnose_if(i, "bad i", "error"); // expected-note{{from 'diagnose_if'}} + void baz(int i); +}; + +template <> +struct Foo { + void foo(); + void bar(int i); + void baz(int i) _diagnose_if(i, "bad i", "error"); // expected-note{{from 'diagnose_if'}} +}; + +void runAll() { + Foo().foo(); // expected-error{{override me}} + Foo().foo(); + + Foo().bar(1); // expected-error{{bad i}} + Foo().bar(1); + + Foo().baz(1); + Foo().baz(1); // expected-error{{bad i}} +} +} + +namespace late_constexpr { +constexpr int foo(); +constexpr int foo(int a); + +void bar() _diagnose_if(foo(), "bad foo", "error"); // expected-note{{from 'diagnose_if'}} expected-note{{not viable: requires 0 arguments}} +void bar(int a) _diagnose_if(foo(a), "bad foo", "error"); // expected-note{{bad foo}} + +void early() { + bar(); + bar(0); + bar(1); +} + +constexpr int foo() { return 1; } +constexpr int foo(int a) { return a; } + +void late() { + bar(); // expected-error{{bad foo}} + bar(0); + bar(1); // expected-error{{call to unavailable function}} +} +} + +namespace late_parsed { +struct Foo { + int i; + constexpr Foo(int i): i(i) {} + constexpr bool isFooable() const { return i; } + + void go() const _diagnose_if(isFooable(), "oh no", "error") {} // expected-note{{from 'diagnose_if'}} + operator int() const _diagnose_if(isFooable(), "oh no", "error") { return 1; } // expected-note{{oh no}} + + void go2() const _diagnose_if(isFooable(), "oh no", "error") // expected-note{{oh no}} + __attribute__((enable_if(true, ""))) {} + void go2() const _diagnose_if(isFooable(), "oh no", "error") {} // expected-note{{oh no}} + + constexpr int go3() const _diagnose_if(isFooable(), "oh no", "error") + __attribute__((enable_if(true, ""))) { + return 1; + } + + constexpr int go4() const _diagnose_if(isFooable(), "oh no", "error") { + return 1; + } + constexpr int go4() const _diagnose_if(isFooable(), "oh no", "error") + __attribute__((enable_if(true, ""))) { + return 1; + } + + // We hope to support emitting these errors in the future. For now, though... + constexpr int runGo() const { + return go3() + go4(); + } +}; + +void go(const Foo &f) _diagnose_if(f.isFooable(), "oh no", "error") {} // expected-note{{oh no}} + +void run() { + Foo(0).go(); + Foo(1).go(); // expected-error{{oh no}} + + (void)int(Foo(0)); + (void)int(Foo(1)); // expected-error{{uses deleted function}} + + Foo(0).go2(); + Foo(1).go2(); // expected-error{{call to unavailable member function}} + + go(Foo(0)); + go(Foo(1)); // expected-error{{call to unavailable function}} +} +} + +namespace member_templates { +struct Foo { + int i; + constexpr Foo(int i): i(i) {} + constexpr bool bad() const { return i; } + + template T getVal() _diagnose_if(bad(), "oh no", "error") { // expected-note{{oh no}} + return T(); + } + + template + constexpr T getVal2() const _diagnose_if(bad(), "oh no", "error") { // expected-note{{oh no}} + return T(); + } + + template + constexpr operator T() const _diagnose_if(bad(), "oh no", "error") { // expected-note{{oh no}} + return T(); + } + + // We hope to support emitting these errors in the future. + int run() { return getVal() + getVal2() + int(*this); } +}; + +void run() { + Foo(0).getVal(); + Foo(1).getVal(); // expected-error{{call to unavailable member function}} + + Foo(0).getVal2(); + Foo(1).getVal2(); // expected-error{{call to unavailable member function}} + + (void)int(Foo(0)); + (void)int(Foo(1)); // expected-error{{uses deleted function}} +} +} + +namespace special_member_operators { +struct Bar { int j; }; +struct Foo { + int i; + constexpr Foo(int i): i(i) {} + constexpr bool bad() const { return i; } + const Bar *operator->() const _diagnose_if(bad(), "oh no", "error") { // expected-note{{oh no}} + return nullptr; + } + void operator()() const _diagnose_if(bad(), "oh no", "error") {} // expected-note{{oh no}} +}; + +struct ParenOverload { + int i; + constexpr ParenOverload(int i): i(i) {} + constexpr bool bad() const { return i; } + void operator()(double) const _diagnose_if(bad(), "oh no", "error") {} // expected-note 2{{oh no}} + void operator()(int) const _diagnose_if(bad(), "oh no", "error") {} // expected-note 2{{oh no}} +}; + +struct ParenTemplate { + int i; + constexpr ParenTemplate(int i): i(i) {} + constexpr bool bad() const { return i; } + template + void operator()(T) const _diagnose_if(bad(), "oh no", "error") {} // expected-note 2{{oh no}} +}; + +void run() { + (void)Foo(0)->j; + (void)Foo(1)->j; // expected-error{{selected unavailable operator '->'}} + + Foo(0)(); + Foo(1)(); // expected-error{{unavailable function call operator}} + + ParenOverload(0)(1); + ParenOverload(0)(1.); + + ParenOverload(1)(1); // expected-error{{unavailable function call operator}} + ParenOverload(1)(1.); // expected-error{{unavailable function call operator}} + + ParenTemplate(0)(1); + ParenTemplate(0)(1.); + + ParenTemplate(1)(1); // expected-error{{unavailable function call operator}} + ParenTemplate(1)(1.); // expected-error{{unavailable function call operator}} +} + +void runLambda() { + auto L1 = [](int i) _diagnose_if(i, "oh no", "error") {}; // expected-note{{oh no}} expected-note{{conversion candidate}} + L1(0); + L1(1); // expected-error{{call to unavailable function call}} +} +} + +namespace ctors { +struct Foo { + int I; + constexpr Foo(int I): I(I) {} + + constexpr const Foo &operator=(const Foo &) const // expected-note 2{{disabled: oh no}} + _diagnose_if(I, "oh no", "error") { + return *this; + } + + constexpr const Foo &operator=(const Foo &&) const // expected-note{{disabled: oh no}} expected-note{{no known conversion}} + _diagnose_if(I, "oh no", "error") { + return *this; + } +}; + +void run() { + constexpr Foo F{0}; + constexpr Foo F2{1}; + + F2 = F; // expected-error{{selected unavailable operator}} + F2 = Foo{2}; // expected-error{{selected unavailable operator}} +} +}