https://github.com/mizvekov created https://github.com/llvm/llvm-project/pull/134461
This fixes partial ordering of pack expansions of NTTPs, by procedding with the check using the pattern of the NTTP through the rules of the non-pack case. This also unifies almost all of the different versions of FinishTemplateArgumentDeduction (except the function template case). This makes sure they all follow the rules consistently, instantiating the parameters and comparing those with the argument. Fixes #132562 >From 765304cf05864c8f7fa9aceaab9e3de3bb8e6e24 Mon Sep 17 00:00:00 2001 From: Matheus Izvekov <mizve...@gmail.com> Date: Mon, 31 Mar 2025 17:19:18 -0300 Subject: [PATCH] [clang] fix partial ordering of NTTP packs This fixes partial ordering of pack expansions of NTTPs, by procedding with the check using the pattern of the NTTP through the rules of the non-pack case. This also unifies almost all of the different versions of FinishTemplateArgumentDeduction (except the function template case). This makes sure they all follow the rules consistently, instantiating the parameters and comparing those with the argument. Fixes #132562 --- clang/docs/ReleaseNotes.rst | 1 + clang/include/clang/AST/ExprCXX.h | 6 +- clang/include/clang/Sema/Sema.h | 17 +- clang/lib/AST/ASTContext.cpp | 3 +- clang/lib/AST/ASTImporter.cpp | 7 +- clang/lib/Sema/SemaOverload.cpp | 8 +- clang/lib/Sema/SemaTemplate.cpp | 272 ++++++++++------- clang/lib/Sema/SemaTemplateDeduction.cpp | 281 +++++++----------- clang/lib/Sema/SemaTemplateInstantiate.cpp | 8 +- clang/lib/Sema/SemaTemplateVariadic.cpp | 3 +- clang/lib/Sema/TreeTransform.h | 3 +- .../test/Import/pack-expansion-expr/test.cpp | 2 +- clang/test/SemaTemplate/attributes.cpp | 22 +- clang/test/SemaTemplate/partial-order.cpp | 41 ++- clang/test/SemaTemplate/temp_arg_nontype.cpp | 16 +- 15 files changed, 354 insertions(+), 336 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index c521b56a98606..6756b0d924be6 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -385,6 +385,7 @@ Bug Fixes to C++ Support - Improved fix for an issue with pack expansions of type constraints, where this now also works if the constraint has non-type or template template parameters. (#GH131798) +- Fixes to partial ordering of non-type template parameter packs. (#GH132562) - Fix crash when evaluating the trailing requires clause of generic lambdas which are part of a pack expansion. - Fixes matching of nested template template parameters. (#GH130362) diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h index ac78d2faefe42..c613ce162a6a4 100644 --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -4209,10 +4209,10 @@ class PackExpansionExpr : public Expr { Stmt *Pattern; public: - PackExpansionExpr(QualType T, Expr *Pattern, SourceLocation EllipsisLoc, + PackExpansionExpr(Expr *Pattern, SourceLocation EllipsisLoc, UnsignedOrNone NumExpansions) - : Expr(PackExpansionExprClass, T, Pattern->getValueKind(), - Pattern->getObjectKind()), + : Expr(PackExpansionExprClass, Pattern->getType(), + Pattern->getValueKind(), Pattern->getObjectKind()), EllipsisLoc(EllipsisLoc), NumExpansions(NumExpansions ? *NumExpansions + 1 : 0), Pattern(Pattern) { diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index b835697f99670..0f4377540bb51 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -10126,13 +10126,14 @@ class Sema final : public SemaBase { /// Contexts in which a converted constant expression is required. enum CCEKind { - CCEK_CaseValue, ///< Expression in a case label. - CCEK_Enumerator, ///< Enumerator value with fixed underlying type. - CCEK_TemplateArg, ///< Value of a non-type template parameter. - CCEK_InjectedTTP, ///< Injected parameter of a template template parameter. - CCEK_ArrayBound, ///< Array bound in array declarator or new-expression. - CCEK_ExplicitBool, ///< Condition in an explicit(bool) specifier. - CCEK_Noexcept, ///< Condition in a noexcept(bool) specifier. + CCEK_CaseValue, ///< Expression in a case label. + CCEK_Enumerator, ///< Enumerator value with fixed underlying type. + CCEK_TemplateArg, ///< Value of a non-type template parameter. + CCEK_TempArgStrict, ///< As above, but applies strict template checking + ///< rules. + CCEK_ArrayBound, ///< Array bound in array declarator or new-expression. + CCEK_ExplicitBool, ///< Condition in an explicit(bool) specifier. + CCEK_Noexcept, ///< Condition in a noexcept(bool) specifier. CCEK_StaticAssertMessageSize, ///< Call to size() in a static assert ///< message. CCEK_StaticAssertMessageData, ///< Call to data() in a static assert @@ -11894,7 +11895,7 @@ class Sema final : public SemaBase { QualType InstantiatedParamType, Expr *Arg, TemplateArgument &SugaredConverted, TemplateArgument &CanonicalConverted, - bool MatchingTTP, + bool StrictCheck, CheckTemplateArgumentKind CTAK); /// Check a template argument against its corresponding diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 1b6b3d06ddc1e..2123a3397f125 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -5852,8 +5852,7 @@ TemplateArgument ASTContext::getInjectedTemplateArg(NamedDecl *Param) const { T, VK, NTTP->getLocation()); if (NTTP->isParameterPack()) - E = new (*this) - PackExpansionExpr(DependentTy, E, NTTP->getLocation(), std::nullopt); + E = new (*this) PackExpansionExpr(E, NTTP->getLocation(), std::nullopt); Arg = TemplateArgument(E); } else { auto *TTP = cast<TemplateTemplateParmDecl>(Param); diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index 8c91cce22f78e..f4b977d1d14b3 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -8273,14 +8273,13 @@ ASTNodeImporter::VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *E) { ExpectedStmt ASTNodeImporter::VisitPackExpansionExpr(PackExpansionExpr *E) { Error Err = Error::success(); - auto ToType = importChecked(Err, E->getType()); - auto ToPattern = importChecked(Err, E->getPattern()); + auto *ToPattern = importChecked(Err, E->getPattern()); auto ToEllipsisLoc = importChecked(Err, E->getEllipsisLoc()); if (Err) return std::move(Err); - return new (Importer.getToContext()) PackExpansionExpr( - ToType, ToPattern, ToEllipsisLoc, E->getNumExpansions()); + return new (Importer.getToContext()) + PackExpansionExpr(ToPattern, ToEllipsisLoc, E->getNumExpansions()); } ExpectedStmt ASTNodeImporter::VisitSizeOfPackExpr(SizeOfPackExpr *E) { diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 0564557738170..f46ef2c7f5bd6 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6201,7 +6201,7 @@ static ExprResult BuildConvertedConstantExpression(Sema &S, Expr *From, Sema::CCEKind CCE, NamedDecl *Dest, APValue &PreNarrowingValue) { - assert((S.getLangOpts().CPlusPlus11 || CCE == Sema::CCEK_InjectedTTP) && + assert((S.getLangOpts().CPlusPlus11 || CCE == Sema::CCEK_TempArgStrict) && "converted constant expression outside C++11 or TTP matching"); if (checkPlaceholderForOverload(S, From)) @@ -6272,7 +6272,7 @@ static ExprResult BuildConvertedConstantExpression(Sema &S, Expr *From, // class type. ExprResult Result; bool IsTemplateArgument = - CCE == Sema::CCEK_TemplateArg || CCE == Sema::CCEK_InjectedTTP; + CCE == Sema::CCEK_TemplateArg || CCE == Sema::CCEK_TempArgStrict; if (T->isRecordType()) { assert(IsTemplateArgument && "unexpected class type converted constant expr"); @@ -6325,7 +6325,7 @@ static ExprResult BuildConvertedConstantExpression(Sema &S, Expr *From, // value-dependent so we can't tell whether it's actually narrowing. // For matching the parameters of a TTP, the conversion is ill-formed // if it may narrow. - if (CCE != Sema::CCEK_InjectedTTP) + if (CCE != Sema::CCEK_TempArgStrict) break; [[fallthrough]]; case NK_Type_Narrowing: @@ -6400,7 +6400,7 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value, Expr::EvalResult Eval; Eval.Diag = &Notes; - assert(CCE != Sema::CCEK_InjectedTTP && "unnexpected CCE Kind"); + assert(CCE != Sema::CCEK_TempArgStrict && "unnexpected CCE Kind"); ConstantExprKind Kind; if (CCE == Sema::CCEK_TemplateArg && T->isRecordType()) diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index 153f44f8ec67a..a7bcac50e561c 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -5263,9 +5263,9 @@ bool Sema::CheckTemplateArgument(NamedDecl *Param, TemplateArgumentLoc &ArgLoc, auto checkExpr = [&](Expr *E) -> Expr * { TemplateArgument SugaredResult, CanonicalResult; unsigned CurSFINAEErrors = NumSFINAEErrors; - ExprResult Res = - CheckTemplateArgument(NTTP, NTTPType, E, SugaredResult, - CanonicalResult, CTAI.MatchingTTP, CTAK); + ExprResult Res = CheckTemplateArgument( + NTTP, NTTPType, E, SugaredResult, CanonicalResult, + /*StrictCheck=*/CTAI.MatchingTTP || CTAI.PartialOrdering, CTAK); // If the current template argument causes an error, give up now. if (Res.isInvalid() || CurSFINAEErrors < NumSFINAEErrors) return nullptr; @@ -5344,9 +5344,9 @@ bool Sema::CheckTemplateArgument(NamedDecl *Param, TemplateArgumentLoc &ArgLoc, } TemplateArgument SugaredResult, CanonicalResult; - E = CheckTemplateArgument(NTTP, NTTPType, E.get(), SugaredResult, - CanonicalResult, /*PartialOrderingTTP=*/false, - CTAK_Specified); + E = CheckTemplateArgument( + NTTP, NTTPType, E.get(), SugaredResult, CanonicalResult, + /*StrictCheck=*/CTAI.PartialOrdering, CTAK_Specified); if (E.isInvalid()) return true; @@ -6757,9 +6757,21 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, QualType ParamType, Expr *Arg, TemplateArgument &SugaredConverted, TemplateArgument &CanonicalConverted, - bool PartialOrderingTTP, + bool StrictCheck, CheckTemplateArgumentKind CTAK) { SourceLocation StartLoc = Arg->getBeginLoc(); + auto *ArgPE = dyn_cast<PackExpansionExpr>(Arg); + Expr *DeductionArg = ArgPE ? ArgPE->getPattern() : Arg; + auto setDeductionArg = [&](Expr *NewDeductionArg) { + DeductionArg = NewDeductionArg; + if (ArgPE) { + // Recreate a pack expansion if we unwrapped one. + Arg = new (Context) PackExpansionExpr( + DeductionArg, ArgPE->getEllipsisLoc(), ArgPE->getNumExpansions()); + } else { + Arg = DeductionArg; + } + }; // If the parameter type somehow involves auto, deduce the type now. DeducedType *DeducedT = ParamType->getContainedDeducedType(); @@ -6769,7 +6781,7 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, // FIXME: The language rules don't say what happens in this case. // FIXME: We get an opaque dependent type out of decltype(auto) if the // expression is merely instantiation-dependent; is this enough? - if (Arg->isTypeDependent()) { + if (DeductionArg->isTypeDependent()) { auto *AT = dyn_cast<AutoType>(DeducedT); if (AT && AT->isDecltypeAuto()) { SugaredConverted = TemplateArgument(Arg); @@ -6782,9 +6794,6 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, // When checking a deduced template argument, deduce from its type even if // the type is dependent, in order to check the types of non-type template // arguments line up properly in partial ordering. - Expr *DeductionArg = Arg; - if (auto *PE = dyn_cast<PackExpansionExpr>(DeductionArg)) - DeductionArg = PE->getPattern(); TypeSourceInfo *TSI = Context.getTrivialTypeSourceInfo(ParamType, Param->getLocation()); if (isa<DeducedTemplateSpecializationType>(DeducedT)) { @@ -6837,64 +6846,55 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, assert(!ParamType.hasQualifiers() && "non-type template parameter type cannot be qualified"); + // If either the parameter has a dependent type or the argument is + // type-dependent, there's nothing we can check now. + if (ParamType->isDependentType() || DeductionArg->isTypeDependent()) { + // Force the argument to the type of the parameter to maintain invariants. + ExprResult E = ImpCastExprToType( + DeductionArg, ParamType.getNonLValueExprType(Context), CK_Dependent, + ParamType->isLValueReferenceType() ? VK_LValue + : ParamType->isRValueReferenceType() ? VK_XValue + : VK_PRValue); + if (E.isInvalid()) + return ExprError(); + setDeductionArg(E.get()); + SugaredConverted = TemplateArgument(Arg); + CanonicalConverted = TemplateArgument( + Context.getCanonicalTemplateArgument(SugaredConverted)); + return Arg; + } + // FIXME: When Param is a reference, should we check that Arg is an lvalue? - if (CTAK == CTAK_Deduced && + if (CTAK == CTAK_Deduced && !StrictCheck && (ParamType->isReferenceType() ? !Context.hasSameType(ParamType.getNonReferenceType(), - Arg->getType()) - : !Context.hasSameUnqualifiedType(ParamType, Arg->getType()))) { - // FIXME: If either type is dependent, we skip the check. This isn't - // correct, since during deduction we're supposed to have replaced each - // template parameter with some unique (non-dependent) placeholder. - // FIXME: If the argument type contains 'auto', we carry on and fail the - // type check in order to force specific types to be more specialized than - // 'auto'. It's not clear how partial ordering with 'auto' is supposed to - // work. Similarly for CTAD, when comparing 'A<x>' against 'A'. - if ((ParamType->isDependentType() || Arg->isTypeDependent()) && - !Arg->getType()->getContainedDeducedType()) { - SugaredConverted = TemplateArgument(Arg); - CanonicalConverted = TemplateArgument( - Context.getCanonicalTemplateArgument(SugaredConverted)); - return Arg; - } + DeductionArg->getType()) + : !Context.hasSameUnqualifiedType(ParamType, + DeductionArg->getType()))) { // FIXME: This attempts to implement C++ [temp.deduct.type]p17. Per DR1770, // we should actually be checking the type of the template argument in P, // not the type of the template argument deduced from A, against the // template parameter type. Diag(StartLoc, diag::err_deduced_non_type_template_arg_type_mismatch) - << Arg->getType() - << ParamType.getUnqualifiedType(); + << Arg->getType() << ParamType.getUnqualifiedType(); NoteTemplateParameterLocation(*Param); return ExprError(); } - // If either the parameter has a dependent type or the argument is - // type-dependent, there's nothing we can check now. - if (ParamType->isDependentType() || Arg->isTypeDependent()) { - // Force the argument to the type of the parameter to maintain invariants. - auto *PE = dyn_cast<PackExpansionExpr>(Arg); - if (PE) - Arg = PE->getPattern(); - ExprResult E = ImpCastExprToType( - Arg, ParamType.getNonLValueExprType(Context), CK_Dependent, - ParamType->isLValueReferenceType() ? VK_LValue - : ParamType->isRValueReferenceType() ? VK_XValue - : VK_PRValue); - if (E.isInvalid()) - return ExprError(); - if (PE) { - // Recreate a pack expansion if we unwrapped one. - E = new (Context) - PackExpansionExpr(E.get()->getType(), E.get(), PE->getEllipsisLoc(), - PE->getNumExpansions()); - } - SugaredConverted = TemplateArgument(E.get()); + // If the argument is a pack expansion, we don't know how many times it would + // expand. If we continue checking the argument, this will make the template + // definition ill-formed if it would be ill-formed for any number of + // expansions during insteantiation time. When partial ordering or matching + // template template parameters, this is exactly what we want. Otherwise, the + // normal template rules apply: we accept the template if it would be valid + // for any number of expansions (i.e. none). + if (ArgPE && !StrictCheck) { + SugaredConverted = TemplateArgument(Arg); CanonicalConverted = TemplateArgument( Context.getCanonicalTemplateArgument(SugaredConverted)); - return E; + return Arg; } - QualType CanonParamType = Context.getCanonicalType(ParamType); // Avoid making a copy when initializing a template parameter of class type // from a template parameter object of the same type. This is going beyond // the standard, but is required for soundness: in @@ -6903,15 +6903,15 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, // // Similarly, don't inject a call to a copy constructor when initializing // from a template parameter of the same type. - Expr *InnerArg = Arg->IgnoreParenImpCasts(); + Expr *InnerArg = DeductionArg->IgnoreParenImpCasts(); if (ParamType->isRecordType() && isa<DeclRefExpr>(InnerArg) && Context.hasSameUnqualifiedType(ParamType, InnerArg->getType())) { NamedDecl *ND = cast<DeclRefExpr>(InnerArg)->getDecl(); if (auto *TPO = dyn_cast<TemplateParamObjectDecl>(ND)) { SugaredConverted = TemplateArgument(TPO, ParamType); - CanonicalConverted = - TemplateArgument(TPO->getCanonicalDecl(), CanonParamType); + CanonicalConverted = TemplateArgument(TPO->getCanonicalDecl(), + ParamType.getCanonicalType()); return Arg; } if (isa<NonTypeTemplateParmDecl>(ND)) { @@ -6928,10 +6928,10 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, *this, Sema::ExpressionEvaluationContext::ConstantEvaluated); bool IsConvertedConstantExpression = true; - if (isa<InitListExpr>(Arg) || ParamType->isRecordType()) { + if (isa<InitListExpr>(DeductionArg) || ParamType->isRecordType()) { InitializationKind Kind = InitializationKind::CreateForInit( - Arg->getBeginLoc(), /*DirectInit=*/false, Arg); - Expr *Inits[1] = {Arg}; + StartLoc, /*DirectInit=*/false, DeductionArg); + Expr *Inits[1] = {DeductionArg}; InitializedEntity Entity = InitializedEntity::InitializeTemplateParameter(ParamType, Param); InitializationSequence InitSeq(*this, Entity, Kind, Inits); @@ -6941,14 +6941,15 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, Result = ActOnConstantExpression(Result.get()); if (Result.isInvalid() || !Result.get()) return ExprError(); - Arg = ActOnFinishFullExpr(Result.get(), Arg->getBeginLoc(), - /*DiscardedValue=*/false, - /*IsConstexpr=*/true, /*IsTemplateArgument=*/true) - .get(); + setDeductionArg(ActOnFinishFullExpr(Result.get(), Arg->getBeginLoc(), + /*DiscardedValue=*/false, + /*IsConstexpr=*/true, + /*IsTemplateArgument=*/true) + .get()); IsConvertedConstantExpression = false; } - if (getLangOpts().CPlusPlus17 || PartialOrderingTTP) { + if (getLangOpts().CPlusPlus17 || StrictCheck) { // C++17 [temp.arg.nontype]p1: // A template-argument for a non-type template parameter shall be // a converted constant expression of the type of the template-parameter. @@ -6956,24 +6957,25 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, ExprResult ArgResult; if (IsConvertedConstantExpression) { ArgResult = BuildConvertedConstantExpression( - Arg, ParamType, - PartialOrderingTTP ? CCEK_InjectedTTP : CCEK_TemplateArg, Param); + DeductionArg, ParamType, + StrictCheck ? CCEK_TempArgStrict : CCEK_TemplateArg, Param); assert(!ArgResult.isUnset()); if (ArgResult.isInvalid()) { NoteTemplateParameterLocation(*Param); return ExprError(); } } else { - ArgResult = Arg; + ArgResult = DeductionArg; } // For a value-dependent argument, CheckConvertedConstantExpression is // permitted (and expected) to be unable to determine a value. if (ArgResult.get()->isValueDependent()) { - SugaredConverted = TemplateArgument(ArgResult.get()); + setDeductionArg(ArgResult.get()); + SugaredConverted = TemplateArgument(Arg); CanonicalConverted = Context.getCanonicalTemplateArgument(SugaredConverted); - return ArgResult; + return Arg; } APValue PreNarrowingValue; @@ -6982,6 +6984,7 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, false, PreNarrowingValue); if (ArgResult.isInvalid()) return ExprError(); + setDeductionArg(ArgResult.get()); if (Value.isLValue()) { APValue::LValueBase Base = Value.getLValueBase(); @@ -7006,10 +7009,17 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, VD->getType()->isArrayType() && Value.getLValuePath()[0].getAsArrayIndex() == 0 && !Value.isLValueOnePastTheEnd() && ParamType->isPointerType()) { - SugaredConverted = TemplateArgument(VD, ParamType); - CanonicalConverted = TemplateArgument( - cast<ValueDecl>(VD->getCanonicalDecl()), CanonParamType); - return ArgResult.get(); + if (ArgPE) { + SugaredConverted = TemplateArgument(Arg); + CanonicalConverted = + Context.getCanonicalTemplateArgument(SugaredConverted); + } else { + SugaredConverted = TemplateArgument(VD, ParamType); + CanonicalConverted = + TemplateArgument(cast<ValueDecl>(VD->getCanonicalDecl()), + ParamType.getCanonicalType()); + } + return Arg; } // -- a subobject [until C++20] @@ -7030,9 +7040,16 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, if (Value.isAddrLabelDiff()) return Diag(StartLoc, diag::err_non_type_template_arg_addr_label_diff); - SugaredConverted = TemplateArgument(Context, ParamType, Value); - CanonicalConverted = TemplateArgument(Context, CanonParamType, Value); - return ArgResult.get(); + if (ArgPE) { + SugaredConverted = TemplateArgument(Arg); + CanonicalConverted = + Context.getCanonicalTemplateArgument(SugaredConverted); + } else { + SugaredConverted = TemplateArgument(Context, ParamType, Value); + CanonicalConverted = + TemplateArgument(Context, ParamType.getCanonicalType(), Value); + } + return Arg; } // C++ [temp.arg.nontype]p5: @@ -7061,18 +7078,18 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, // type, a converted constant expression of the type of the // template-parameter; or llvm::APSInt Value; - ExprResult ArgResult = - CheckConvertedConstantExpression(Arg, ParamType, Value, - CCEK_TemplateArg); + ExprResult ArgResult = CheckConvertedConstantExpression( + DeductionArg, ParamType, Value, CCEK_TemplateArg); if (ArgResult.isInvalid()) return ExprError(); + setDeductionArg(ArgResult.get()); // We can't check arbitrary value-dependent arguments. - if (ArgResult.get()->isValueDependent()) { - SugaredConverted = TemplateArgument(ArgResult.get()); + if (DeductionArg->isValueDependent()) { + SugaredConverted = TemplateArgument(Arg); CanonicalConverted = Context.getCanonicalTemplateArgument(SugaredConverted); - return ArgResult; + return Arg; } // Widen the argument value to sizeof(parameter type). This is almost @@ -7085,18 +7102,24 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, ? Context.getIntWidth(IntegerType) : Context.getTypeSize(IntegerType)); - SugaredConverted = TemplateArgument(Context, Value, ParamType); - CanonicalConverted = - TemplateArgument(Context, Value, Context.getCanonicalType(ParamType)); - return ArgResult; + if (ArgPE) { + SugaredConverted = TemplateArgument(Arg); + CanonicalConverted = + Context.getCanonicalTemplateArgument(SugaredConverted); + } else { + SugaredConverted = TemplateArgument(Context, Value, ParamType); + CanonicalConverted = TemplateArgument( + Context, Value, Context.getCanonicalType(ParamType)); + } + return Arg; } ExprResult ArgResult = DefaultLvalueConversion(Arg); if (ArgResult.isInvalid()) return ExprError(); - Arg = ArgResult.get(); + DeductionArg = ArgResult.get(); - QualType ArgType = Arg->getType(); + QualType ArgType = DeductionArg->getType(); // C++ [temp.arg.nontype]p1: // A template-argument for a non-type, non-template @@ -7107,11 +7130,11 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, // -- the name of a non-type template-parameter; or llvm::APSInt Value; if (!ArgType->isIntegralOrEnumerationType()) { - Diag(Arg->getBeginLoc(), diag::err_template_arg_not_integral_or_enumeral) - << ArgType << Arg->getSourceRange(); + Diag(StartLoc, diag::err_template_arg_not_integral_or_enumeral) + << ArgType << DeductionArg->getSourceRange(); NoteTemplateParameterLocation(*Param); return ExprError(); - } else if (!Arg->isValueDependent()) { + } else if (!DeductionArg->isValueDependent()) { class TmplArgICEDiagnoser : public VerifyICEDiagnoser { QualType T; @@ -7124,8 +7147,10 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, } } Diagnoser(ArgType); - Arg = VerifyIntegerConstantExpression(Arg, &Value, Diagnoser).get(); - if (!Arg) + DeductionArg = + VerifyIntegerConstantExpression(DeductionArg, &Value, Diagnoser) + .get(); + if (!DeductionArg) return ExprError(); } @@ -7138,23 +7163,28 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, // Okay: no conversion necessary } else if (ParamType->isBooleanType()) { // This is an integral-to-boolean conversion. - Arg = ImpCastExprToType(Arg, ParamType, CK_IntegralToBoolean).get(); + DeductionArg = + ImpCastExprToType(DeductionArg, ParamType, CK_IntegralToBoolean) + .get(); } else if (IsIntegralPromotion(Arg, ArgType, ParamType) || !ParamType->isEnumeralType()) { // This is an integral promotion or conversion. - Arg = ImpCastExprToType(Arg, ParamType, CK_IntegralCast).get(); + DeductionArg = + ImpCastExprToType(DeductionArg, ParamType, CK_IntegralCast).get(); } else { // We can't perform this conversion. - Diag(Arg->getBeginLoc(), diag::err_template_arg_not_convertible) - << Arg->getType() << ParamType << Arg->getSourceRange(); + Diag(StartLoc, diag::err_template_arg_not_convertible) + << DeductionArg->getType() << ParamType + << DeductionArg->getSourceRange(); NoteTemplateParameterLocation(*Param); return ExprError(); } + setDeductionArg(DeductionArg); // Add the value of this argument to the list of converted // arguments. We use the bitwidth and signedness of the template // parameter. - if (Arg->isValueDependent()) { + if (DeductionArg->isValueDependent()) { // The argument is value-dependent. Create a new // TemplateArgument with the converted expression. SugaredConverted = TemplateArgument(Arg); @@ -7212,14 +7242,20 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, } } - QualType T = ParamType->isEnumeralType() ? ParamType : IntegerType; - SugaredConverted = TemplateArgument(Context, Value, T); - CanonicalConverted = - TemplateArgument(Context, Value, Context.getCanonicalType(T)); + if (ArgPE) { + SugaredConverted = TemplateArgument(Arg); + CanonicalConverted = + Context.getCanonicalTemplateArgument(SugaredConverted); + } else { + QualType T = ParamType->isEnumeralType() ? ParamType : IntegerType; + SugaredConverted = TemplateArgument(Context, Value, T); + CanonicalConverted = + TemplateArgument(Context, Value, Context.getCanonicalType(T)); + } return Arg; } - QualType ArgType = Arg->getType(); + QualType ArgType = DeductionArg->getType(); DeclAccessPair FoundResult; // temporary for ResolveOverloadedFunction // Handle pointer-to-function, reference-to-function, and @@ -7246,7 +7282,7 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, ParamType->castAs<MemberPointerType>()->getPointeeType() ->isFunctionType())) { - if (Arg->getType() == Context.OverloadTy) { + if (DeductionArg->getType() == Context.OverloadTy) { if (FunctionDecl *Fn = ResolveAddressOfOverloadedFunction(Arg, ParamType, true, FoundResult)) { @@ -7256,11 +7292,12 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, ExprResult Res = FixOverloadedFunctionReference(Arg, FoundResult, Fn); if (Res.isInvalid()) return ExprError(); - Arg = Res.get(); + DeductionArg = Res.get(); ArgType = Arg->getType(); } else return ExprError(); } + setDeductionArg(DeductionArg); if (!ParamType->isMemberPointerType()) { if (CheckTemplateArgumentAddressOfObjectOrFunction( @@ -7276,6 +7313,8 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, return Arg; } + setDeductionArg(DeductionArg); + if (ParamType->isPointerType()) { // -- for a non-type template-parameter of type pointer to // object, qualification conversions (4.4) and the @@ -7284,6 +7323,7 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, assert(ParamType->getPointeeType()->isIncompleteOrObjectType() && "Only object pointers allowed here"); + // FIXME: Deal with pack expansions here. if (CheckTemplateArgumentAddressOfObjectOrFunction( *this, Param, ParamType, Arg, SugaredConverted, CanonicalConverted)) return ExprError(); @@ -7300,6 +7340,7 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, assert(ParamRefType->getPointeeType()->isIncompleteOrObjectType() && "Only object references allowed here"); + // FIXME: Deal with pack expansions here. if (Arg->getType() == Context.OverloadTy) { if (FunctionDecl *Fn = ResolveAddressOfOverloadedFunction(Arg, ParamRefType->getPointeeType(), @@ -7324,17 +7365,18 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, // Deal with parameters of type std::nullptr_t. if (ParamType->isNullPtrType()) { - if (Arg->isTypeDependent() || Arg->isValueDependent()) { + if (DeductionArg->isTypeDependent() || DeductionArg->isValueDependent()) { SugaredConverted = TemplateArgument(Arg); CanonicalConverted = Context.getCanonicalTemplateArgument(SugaredConverted); return Arg; } - switch (isNullPointerValueTemplateArgument(*this, Param, ParamType, Arg)) { + switch (isNullPointerValueTemplateArgument(*this, Param, ParamType, + DeductionArg)) { case NPV_NotNullPointer: Diag(Arg->getExprLoc(), diag::err_template_arg_not_convertible) - << Arg->getType() << ParamType; + << DeductionArg->getType() << ParamType; NoteTemplateParameterLocation(*Param); return ExprError(); @@ -7343,10 +7385,17 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, case NPV_NullPointer: Diag(Arg->getExprLoc(), diag::warn_cxx98_compat_template_arg_null); - SugaredConverted = TemplateArgument(ParamType, - /*isNullPtr=*/true); - CanonicalConverted = TemplateArgument(Context.getCanonicalType(ParamType), + if (ArgPE) { + SugaredConverted = TemplateArgument(Arg); + CanonicalConverted = + Context.getCanonicalTemplateArgument(SugaredConverted); + } else { + SugaredConverted = TemplateArgument(ParamType, /*isNullPtr=*/true); + CanonicalConverted = + TemplateArgument(Context.getCanonicalType(ParamType), + /*isNullPtr=*/true); + } return Arg; } } @@ -7355,6 +7404,7 @@ ExprResult Sema::CheckTemplateArgument(NonTypeTemplateParmDecl *Param, // member, qualification conversions (4.4) are applied. assert(ParamType->isMemberPointerType() && "Only pointers to members remain"); + // FIXME: Deal with pack expansions here. if (CheckTemplateArgumentPointerToMember( *this, Param, ParamType, Arg, SugaredConverted, CanonicalConverted)) return ExprError(); diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp index ab6e18aee7206..004b36a94c6e5 100644 --- a/clang/lib/Sema/SemaTemplateDeduction.cpp +++ b/clang/lib/Sema/SemaTemplateDeduction.cpp @@ -2826,16 +2826,8 @@ TemplateDeductionResult Sema::DeduceTemplateArguments( } /// Determine whether two template arguments are the same. -static bool isSameTemplateArg(ASTContext &Context, - TemplateArgument X, - const TemplateArgument &Y, - bool PartialOrdering, - bool PackExpansionMatchesPack = false) { - // If we're checking deduced arguments (X) against original arguments (Y), - // we will have flattened packs to non-expansions in X. - if (PackExpansionMatchesPack && X.isPackExpansion() && !Y.isPackExpansion()) - X = X.getPackExpansionPattern(); - +static bool isSameTemplateArg(ASTContext &Context, const TemplateArgument &X, + const TemplateArgument &Y) { if (X.getKind() != Y.getKind()) return false; @@ -2875,28 +2867,12 @@ static bool isSameTemplateArg(ASTContext &Context, case TemplateArgument::Pack: { unsigned PackIterationSize = X.pack_size(); - if (X.pack_size() != Y.pack_size()) { - if (!PartialOrdering) - return false; - - // C++0x [temp.deduct.type]p9: - // During partial ordering, if Ai was originally a pack expansion: - // - if P does not contain a template argument corresponding to Ai - // then Ai is ignored; - bool XHasMoreArg = X.pack_size() > Y.pack_size(); - if (!(XHasMoreArg && X.pack_elements().back().isPackExpansion()) && - !(!XHasMoreArg && Y.pack_elements().back().isPackExpansion())) - return false; - - if (XHasMoreArg) - PackIterationSize = Y.pack_size(); - } - + if (X.pack_size() != Y.pack_size()) + return false; ArrayRef<TemplateArgument> XP = X.pack_elements(); ArrayRef<TemplateArgument> YP = Y.pack_elements(); for (unsigned i = 0; i < PackIterationSize; ++i) - if (!isSameTemplateArg(Context, XP[i], YP[i], PartialOrdering, - PackExpansionMatchesPack)) + if (!isSameTemplateArg(Context, XP[i], YP[i])) return false; return true; } @@ -3074,22 +3050,16 @@ ConvertDeducedTemplateArgument(Sema &S, NamedDecl *Param, return ConvertArg(Arg, 0); } -// FIXME: This should not be a template, but -// ClassTemplatePartialSpecializationDecl sadly does not derive from -// TemplateDecl. /// \param IsIncomplete When used, we only consider template parameters that /// were deduced, disregarding any default arguments. After the function /// finishes, the object pointed at will contain a value indicating if the /// conversion was actually incomplete. -template <typename TemplateDeclT> static TemplateDeductionResult ConvertDeducedTemplateArguments( - Sema &S, TemplateDeclT *Template, bool IsDeduced, - SmallVectorImpl<DeducedTemplateArgument> &Deduced, + Sema &S, NamedDecl *Template, TemplateParameterList *TemplateParams, + bool IsDeduced, SmallVectorImpl<DeducedTemplateArgument> &Deduced, TemplateDeductionInfo &Info, Sema::CheckTemplateArgumentInfo &CTAI, LocalInstantiationScope *CurrentInstantiationScope, unsigned NumAlreadyConverted, bool *IsIncomplete) { - TemplateParameterList *TemplateParams = Template->getTemplateParameters(); - for (unsigned I = 0, N = TemplateParams->size(); I != N; ++I) { NamedDecl *Param = TemplateParams->getParam(I); @@ -3234,34 +3204,30 @@ template<> struct IsPartialSpecialization<VarTemplatePartialSpecializationDecl> { static constexpr bool value = true; }; -template <typename TemplateDeclT> -static bool DeducedArgsNeedReplacement(TemplateDeclT *Template) { - return false; -} -template <> -bool DeducedArgsNeedReplacement<VarTemplatePartialSpecializationDecl>( - VarTemplatePartialSpecializationDecl *Spec) { - return !Spec->isClassScopeExplicitSpecialization(); -} -template <> -bool DeducedArgsNeedReplacement<ClassTemplatePartialSpecializationDecl>( - ClassTemplatePartialSpecializationDecl *Spec) { - return !Spec->isClassScopeExplicitSpecialization(); -} -template <typename TemplateDeclT> static TemplateDeductionResult -CheckDeducedArgumentConstraints(Sema &S, TemplateDeclT *Template, +CheckDeducedArgumentConstraints(Sema &S, NamedDecl *Template, ArrayRef<TemplateArgument> SugaredDeducedArgs, ArrayRef<TemplateArgument> CanonicalDeducedArgs, TemplateDeductionInfo &Info) { llvm::SmallVector<AssociatedConstraint, 3> AssociatedConstraints; - Template->getAssociatedConstraints(AssociatedConstraints); + bool DeducedArgsNeedReplacement = false; + if (auto *TD = dyn_cast<ClassTemplatePartialSpecializationDecl>(Template)) { + TD->getAssociatedConstraints(AssociatedConstraints); + DeducedArgsNeedReplacement = !TD->isClassScopeExplicitSpecialization(); + } else if (auto *TD = + dyn_cast<VarTemplatePartialSpecializationDecl>(Template)) { + TD->getAssociatedConstraints(AssociatedConstraints); + DeducedArgsNeedReplacement = !TD->isClassScopeExplicitSpecialization(); + } else { + cast<TemplateDecl>(Template)->getAssociatedConstraints( + AssociatedConstraints); + } std::optional<ArrayRef<TemplateArgument>> Innermost; // If we don't need to replace the deduced template arguments, // we can add them immediately as the inner-most argument list. - if (!DeducedArgsNeedReplacement(Template)) + if (!DeducedArgsNeedReplacement) Innermost = CanonicalDeducedArgs; MultiLevelTemplateArgumentList MLTAL = S.getTemplateInstantiationArgs( @@ -3288,73 +3254,60 @@ CheckDeducedArgumentConstraints(Sema &S, TemplateDeclT *Template, return TemplateDeductionResult::Success; } -/// Complete template argument deduction for a partial specialization. -template <typename T> -static std::enable_if_t<IsPartialSpecialization<T>::value, - TemplateDeductionResult> -FinishTemplateArgumentDeduction( - Sema &S, T *Partial, bool IsPartialOrdering, - ArrayRef<TemplateArgument> TemplateArgs, +/// Complete template argument deduction. +static TemplateDeductionResult FinishTemplateArgumentDeduction( + Sema &S, NamedDecl *Entity, TemplateParameterList *EntityTPL, + TemplateDecl *Template, bool PartialOrdering, + ArrayRef<TemplateArgumentLoc> Ps, ArrayRef<TemplateArgument> As, SmallVectorImpl<DeducedTemplateArgument> &Deduced, - TemplateDeductionInfo &Info) { + TemplateDeductionInfo &Info, bool CopyDeducedArgs) { // Unevaluated SFINAE context. EnterExpressionEvaluationContext Unevaluated( S, Sema::ExpressionEvaluationContext::Unevaluated); - Sema::SFINAETrap Trap(S); - Sema::ContextRAII SavedContext(S, getAsDeclContextOrEnclosing(Partial)); + Sema::ContextRAII SavedContext(S, getAsDeclContextOrEnclosing(Entity)); // C++ [temp.deduct.type]p2: // [...] or if any template argument remains neither deduced nor // explicitly specified, template argument deduction fails. - Sema::CheckTemplateArgumentInfo CTAI(IsPartialOrdering); + Sema::CheckTemplateArgumentInfo CTAI(PartialOrdering); if (auto Result = ConvertDeducedTemplateArguments( - S, Partial, IsPartialOrdering, Deduced, Info, CTAI, - /*CurrentInstantiationScope=*/nullptr, /*NumAlreadyConverted=*/0, - /*IsIncomplete=*/nullptr); + S, Entity, EntityTPL, /*IsDeduced=*/PartialOrdering, Deduced, Info, + CTAI, + /*CurrentInstantiationScope=*/nullptr, + /*NumAlreadyConverted=*/0U, /*IsIncomplete=*/nullptr); Result != TemplateDeductionResult::Success) return Result; - // Form the template argument list from the deduced template arguments. - TemplateArgumentList *SugaredDeducedArgumentList = - TemplateArgumentList::CreateCopy(S.Context, CTAI.SugaredConverted); - TemplateArgumentList *CanonicalDeducedArgumentList = - TemplateArgumentList::CreateCopy(S.Context, CTAI.CanonicalConverted); + if (CopyDeducedArgs) { + // Form the template argument list from the deduced template arguments. + TemplateArgumentList *SugaredDeducedArgumentList = + TemplateArgumentList::CreateCopy(S.Context, CTAI.SugaredConverted); + TemplateArgumentList *CanonicalDeducedArgumentList = + TemplateArgumentList::CreateCopy(S.Context, CTAI.CanonicalConverted); + Info.reset(SugaredDeducedArgumentList, CanonicalDeducedArgumentList); + } - Info.reset(SugaredDeducedArgumentList, CanonicalDeducedArgumentList); + TemplateParameterList *TPL = Template->getTemplateParameters(); + TemplateArgumentListInfo InstArgs(TPL->getLAngleLoc(), TPL->getRAngleLoc()); + MultiLevelTemplateArgumentList MLTAL(Entity, CTAI.SugaredConverted, + /*Final=*/true); + MLTAL.addOuterRetainedLevels(TPL->getDepth()); - // Substitute the deduced template arguments into the template - // arguments of the class template partial specialization, and - // verify that the instantiated template arguments are both valid - // and are equivalent to the template arguments originally provided - // to the class template. - LocalInstantiationScope InstScope(S); - auto *Template = Partial->getSpecializedTemplate(); - const ASTTemplateArgumentListInfo *PartialTemplArgInfo = - Partial->getTemplateArgsAsWritten(); - - TemplateArgumentListInfo InstArgs(PartialTemplArgInfo->LAngleLoc, - PartialTemplArgInfo->RAngleLoc); - - if (S.SubstTemplateArguments( - PartialTemplArgInfo->arguments(), - MultiLevelTemplateArgumentList(Partial, CTAI.SugaredConverted, - /*Final=*/true), - InstArgs)) { + if (S.SubstTemplateArguments(Ps, MLTAL, InstArgs)) { unsigned ArgIdx = InstArgs.size(), ParamIdx = ArgIdx; - if (ParamIdx >= Partial->getTemplateParameters()->size()) - ParamIdx = Partial->getTemplateParameters()->size() - 1; + if (ParamIdx >= TPL->size()) + ParamIdx = TPL->size() - 1; - Decl *Param = const_cast<NamedDecl *>( - Partial->getTemplateParameters()->getParam(ParamIdx)); + Decl *Param = const_cast<NamedDecl *>(TPL->getParam(ParamIdx)); Info.Param = makeTemplateParameter(Param); - Info.FirstArg = (*PartialTemplArgInfo)[ArgIdx].getArgument(); + Info.FirstArg = Ps[ArgIdx].getArgument(); return TemplateDeductionResult::SubstitutionFailure; } bool ConstraintsNotSatisfied; Sema::CheckTemplateArgumentInfo InstCTAI; - if (S.CheckTemplateArgumentList(Template, Partial->getLocation(), InstArgs, + if (S.CheckTemplateArgumentList(Template, Template->getLocation(), InstArgs, /*DefaultArgs=*/{}, false, InstCTAI, /*UpdateArgsWithConversions=*/true, &ConstraintsNotSatisfied)) @@ -3362,59 +3315,9 @@ FinishTemplateArgumentDeduction( ? TemplateDeductionResult::ConstraintsNotSatisfied : TemplateDeductionResult::SubstitutionFailure; - TemplateParameterList *TemplateParams = Template->getTemplateParameters(); - for (unsigned I = 0, E = TemplateParams->size(); I != E; ++I) { - TemplateArgument InstArg = InstCTAI.SugaredConverted.data()[I]; - if (!isSameTemplateArg(S.Context, TemplateArgs[I], InstArg, - IsPartialOrdering)) { - Info.Param = makeTemplateParameter(TemplateParams->getParam(I)); - Info.FirstArg = TemplateArgs[I]; - Info.SecondArg = InstArg; - return TemplateDeductionResult::NonDeducedMismatch; - } - } - - if (Trap.hasErrorOccurred()) - return TemplateDeductionResult::SubstitutionFailure; - - if (!IsPartialOrdering) { - if (auto Result = CheckDeducedArgumentConstraints( - S, Partial, CTAI.SugaredConverted, CTAI.CanonicalConverted, Info); - Result != TemplateDeductionResult::Success) - return Result; - } - - return TemplateDeductionResult::Success; -} - -/// Complete template argument deduction for a class or variable template, -/// when partial ordering against a partial specialization. -// FIXME: Factor out duplication with partial specialization version above. -static TemplateDeductionResult FinishTemplateArgumentDeduction( - Sema &S, TemplateDecl *Template, bool PartialOrdering, - ArrayRef<TemplateArgument> TemplateArgs, - SmallVectorImpl<DeducedTemplateArgument> &Deduced, - TemplateDeductionInfo &Info) { - // Unevaluated SFINAE context. - EnterExpressionEvaluationContext Unevaluated( - S, Sema::ExpressionEvaluationContext::Unevaluated); - - Sema::ContextRAII SavedContext(S, getAsDeclContextOrEnclosing(Template)); - - // C++ [temp.deduct.type]p2: - // [...] or if any template argument remains neither deduced nor - // explicitly specified, template argument deduction fails. - Sema::CheckTemplateArgumentInfo CTAI(PartialOrdering); - if (auto Result = ConvertDeducedTemplateArguments( - S, Template, /*IsDeduced=*/PartialOrdering, Deduced, Info, CTAI, - /*CurrentInstantiationScope=*/nullptr, - /*NumAlreadyConverted=*/0U, /*IsIncomplete=*/nullptr); - Result != TemplateDeductionResult::Success) - return Result; - // Check that we produced the correct argument list. - SmallVector<ArrayRef<TemplateArgument>, 4> PsStack{TemplateArgs}, - AsStack{CTAI.CanonicalConverted}; + SmallVector<ArrayRef<TemplateArgument>, 4> PsStack{InstCTAI.SugaredConverted}, + AsStack{As}; for (;;) { auto take = [](SmallVectorImpl<ArrayRef<TemplateArgument>> &Stack) -> std::tuple<ArrayRef<TemplateArgument> &, TemplateArgument> { @@ -3443,13 +3346,11 @@ static TemplateDeductionResult FinishTemplateArgumentDeduction( break; TemplateArgument PP = P.isPackExpansion() ? P.getPackExpansionPattern() : P, PA = A.isPackExpansion() ? A.getPackExpansionPattern() : A; - if (!isSameTemplateArg(S.Context, PP, PA, /*PartialOrdering=*/false)) { + if (!isSameTemplateArg(S.Context, PP, PA)) { if (!P.isPackExpansion() && !A.isPackExpansion()) { - Info.Param = - makeTemplateParameter(Template->getTemplateParameters()->getParam( - (AsStack.empty() ? CTAI.CanonicalConverted.end() - : AsStack.front().begin()) - - 1 - CTAI.CanonicalConverted.begin())); + Info.Param = makeTemplateParameter(TPL->getParam( + (AsStack.empty() ? As.end() : AsStack.back().begin()) - + As.begin())); Info.FirstArg = P; Info.SecondArg = A; return TemplateDeductionResult::NonDeducedMismatch; @@ -3471,13 +3372,28 @@ static TemplateDeductionResult FinishTemplateArgumentDeduction( if (!PartialOrdering) { if (auto Result = CheckDeducedArgumentConstraints( - S, Template, CTAI.SugaredConverted, CTAI.CanonicalConverted, Info); + S, Entity, CTAI.SugaredConverted, CTAI.CanonicalConverted, Info); Result != TemplateDeductionResult::Success) return Result; } return TemplateDeductionResult::Success; } +static TemplateDeductionResult FinishTemplateArgumentDeduction( + Sema &S, NamedDecl *Entity, TemplateParameterList *EntityTPL, + TemplateDecl *Template, bool PartialOrdering, ArrayRef<TemplateArgument> Ps, + ArrayRef<TemplateArgument> As, + SmallVectorImpl<DeducedTemplateArgument> &Deduced, + TemplateDeductionInfo &Info, bool CopyDeducedArgs) { + TemplateParameterList *TPL = Template->getTemplateParameters(); + SmallVector<TemplateArgumentLoc, 8> PsLoc(Ps.size()); + for (unsigned I = 0, N = Ps.size(); I != N; ++I) + PsLoc[I] = S.getTrivialTemplateArgumentLoc(Ps[I], QualType(), + TPL->getParam(I)->getLocation()); + return FinishTemplateArgumentDeduction(S, Entity, EntityTPL, Template, + PartialOrdering, PsLoc, As, Deduced, + Info, CopyDeducedArgs); +} /// Complete template argument deduction for DeduceTemplateArgumentsFromType. /// FIXME: this is mostly duplicated with the above two versions. Deduplicate @@ -3497,7 +3413,8 @@ static TemplateDeductionResult FinishTemplateArgumentDeduction( // explicitly specified, template argument deduction fails. Sema::CheckTemplateArgumentInfo CTAI; if (auto Result = ConvertDeducedTemplateArguments( - S, TD, /*IsDeduced=*/false, Deduced, Info, CTAI, + S, TD, TD->getTemplateParameters(), /*IsDeduced=*/false, Deduced, + Info, CTAI, /*CurrentInstantiationScope=*/nullptr, /*NumAlreadyConverted=*/0, /*IsIncomplete=*/nullptr); Result != TemplateDeductionResult::Success) @@ -3553,9 +3470,12 @@ DeduceTemplateArguments(Sema &S, T *Partial, TemplateDeductionResult Result; S.runWithSufficientStackSpace(Info.getLocation(), [&] { - Result = ::FinishTemplateArgumentDeduction(S, Partial, - /*IsPartialOrdering=*/false, - TemplateArgs, Deduced, Info); + Result = ::FinishTemplateArgumentDeduction( + S, Partial, Partial->getTemplateParameters(), + Partial->getSpecializedTemplate(), + /*IsPartialOrdering=*/false, + Partial->getTemplateArgsAsWritten()->arguments(), TemplateArgs, Deduced, + Info, /*CopyDeducedArgs=*/true); }); if (Result != TemplateDeductionResult::Success) @@ -4062,9 +3982,9 @@ TemplateDeductionResult Sema::FinishTemplateArgumentDeduction( bool IsIncomplete = false; CheckTemplateArgumentInfo CTAI(PartialOrdering); if (auto Result = ConvertDeducedTemplateArguments( - *this, FunctionTemplate, /*IsDeduced=*/true, Deduced, Info, CTAI, - CurrentInstantiationScope, NumExplicitlySpecified, - PartialOverloading ? &IsIncomplete : nullptr); + *this, FunctionTemplate, FunctionTemplate->getTemplateParameters(), + /*IsDeduced=*/true, Deduced, Info, CTAI, CurrentInstantiationScope, + NumExplicitlySpecified, PartialOverloading ? &IsIncomplete : nullptr); Result != TemplateDeductionResult::Success) return Result; @@ -5677,7 +5597,8 @@ static TemplateDeductionResult FinishTemplateArgumentDeduction( bool IsIncomplete = false; Sema::CheckTemplateArgumentInfo CTAI(/*PartialOrdering=*/true); if (auto Result = ConvertDeducedTemplateArguments( - S, FTD, /*IsDeduced=*/true, Deduced, Info, CTAI, + S, FTD, FTD->getTemplateParameters(), /*IsDeduced=*/true, Deduced, + Info, CTAI, /*CurrentInstantiationScope=*/nullptr, /*NumAlreadyConverted=*/0, &IsIncomplete); Result != TemplateDeductionResult::Success) @@ -6250,9 +6171,10 @@ FunctionDecl *Sema::getMoreConstrainedFunction(FunctionDecl *FD1, /// TemplateDecl or {Class,Var}TemplatePartialSpecializationDecl. /// \param T1 The injected-class-name of P1 (faked for a variable template). /// \param T2 The injected-class-name of P2 (faked for a variable template). -template<typename TemplateLikeDecl> +template <typename TemplateLikeDecl> static bool isAtLeastAsSpecializedAs(Sema &S, QualType T1, QualType T2, TemplateLikeDecl *P2, + TemplateDecl *Template, TemplateDeductionInfo &Info) { // C++ [temp.class.order]p1: // For two class template partial specializations, the first is at least as @@ -6295,15 +6217,18 @@ static bool isAtLeastAsSpecializedAs(Sema &S, QualType T1, QualType T2, if (Inst.isInvalid()) return false; - const auto *TST1 = cast<TemplateSpecializationType>(T1); + ArrayRef<TemplateArgument> + Ps = cast<TemplateSpecializationType>(T2)->template_arguments(), + As = cast<TemplateSpecializationType>(T1)->template_arguments(); Sema::SFINAETrap Trap(S); TemplateDeductionResult Result; S.runWithSufficientStackSpace(Info.getLocation(), [&] { Result = ::FinishTemplateArgumentDeduction( - S, P2, /*IsPartialOrdering=*/true, TST1->template_arguments(), Deduced, - Info); + S, P2, P2->getTemplateParameters(), Template, + /*IsPartialOrdering=*/true, Ps, As, Deduced, Info, + /*CopyDeducedArgs=*/false); }); if (Result != TemplateDeductionResult::Success) @@ -6407,11 +6332,18 @@ getMoreSpecialized(Sema &S, QualType T1, QualType T2, TemplateLikeDecl *P1, constexpr bool IsMoreSpecialThanPrimaryCheck = !std::is_same_v<TemplateLikeDecl, PrimaryDel>; - bool Better1 = isAtLeastAsSpecializedAs(S, T1, T2, P2, Info); + TemplateDecl *P2T; + if constexpr (IsMoreSpecialThanPrimaryCheck) + P2T = P2; + else + P2T = P2->getSpecializedTemplate(); + + bool Better1 = isAtLeastAsSpecializedAs(S, T1, T2, P2, P2T, Info); if (IsMoreSpecialThanPrimaryCheck && !Better1) return nullptr; - bool Better2 = isAtLeastAsSpecializedAs(S, T2, T1, P1, Info); + bool Better2 = isAtLeastAsSpecializedAs(S, T2, T1, P1, + P1->getSpecializedTemplate(), Info); if (IsMoreSpecialThanPrimaryCheck && !Better2) return P1; @@ -6666,8 +6598,9 @@ bool Sema::isTemplateTemplateParameterAtLeastAsSpecializedAs( TemplateDeductionResult TDK; runWithSufficientStackSpace(Info.getLocation(), [&] { - TDK = ::FinishTemplateArgumentDeduction(*this, AArg, PartialOrdering, PArgs, - Deduced, Info); + TDK = ::FinishTemplateArgumentDeduction( + *this, AArg, AArg->getTemplateParameters(), AArg, PartialOrdering, + AArgs, PArgs, Deduced, Info, /*CopyDeducedArgs=*/false); }); switch (TDK) { case TemplateDeductionResult::Success: diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index dd493a083d86d..d2408a94ad0ab 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -2383,10 +2383,10 @@ TemplateInstantiator::TransformSubstNonTypeTemplateParmExpr( // The call to CheckTemplateArgument here produces the ImpCast. TemplateArgument SugaredConverted, CanonicalConverted; if (SemaRef - .CheckTemplateArgument( - E->getParameter(), SubstType, SubstReplacement.get(), - SugaredConverted, CanonicalConverted, - /*PartialOrderingTTP=*/false, Sema::CTAK_Specified) + .CheckTemplateArgument(E->getParameter(), SubstType, + SubstReplacement.get(), SugaredConverted, + CanonicalConverted, + /*StrictCheck=*/false, Sema::CTAK_Specified) .isInvalid()) return true; return transformNonTypeTemplateParmRef( diff --git a/clang/lib/Sema/SemaTemplateVariadic.cpp b/clang/lib/Sema/SemaTemplateVariadic.cpp index ef0e6ee23e942..3d4a245eb8bd5 100644 --- a/clang/lib/Sema/SemaTemplateVariadic.cpp +++ b/clang/lib/Sema/SemaTemplateVariadic.cpp @@ -745,8 +745,7 @@ ExprResult Sema::CheckPackExpansion(Expr *Pattern, SourceLocation EllipsisLoc, } // Create the pack expansion expression and source-location information. - return new (Context) - PackExpansionExpr(Context.DependentTy, Pattern, EllipsisLoc, NumExpansions); + return new (Context) PackExpansionExpr(Pattern, EllipsisLoc, NumExpansions); } bool Sema::CheckParameterPacksForExpansion( diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 237c5a9ef501b..87ec6ac7825ac 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -16128,8 +16128,7 @@ TreeTransform<Derived>::TransformSizeOfPackExpr(SizeOfPackExpr *E) { if (DRE.isInvalid()) return ExprError(); ArgStorage = new (getSema().Context) - PackExpansionExpr(getSema().Context.DependentTy, DRE.get(), - E->getPackLoc(), std::nullopt); + PackExpansionExpr(DRE.get(), E->getPackLoc(), std::nullopt); } PackArgs = ArgStorage; } diff --git a/clang/test/Import/pack-expansion-expr/test.cpp b/clang/test/Import/pack-expansion-expr/test.cpp index 6866c41cfbcf4..3aa108b2ab897 100644 --- a/clang/test/Import/pack-expansion-expr/test.cpp +++ b/clang/test/Import/pack-expansion-expr/test.cpp @@ -1,7 +1,7 @@ // RUN: clang-import-test -dump-ast -import %S/Inputs/F.cpp -expression %s | FileCheck %s // CHECK: PackExpansionExpr -// CHECK-SAME: '<dependent type>' +// CHECK-SAME: 'T' // CHECK-NEXT: DeclRefExpr // CHECK-SAME: 'T' // CHECK-SAME: ParmVar diff --git a/clang/test/SemaTemplate/attributes.cpp b/clang/test/SemaTemplate/attributes.cpp index dea19d09745ca..020cfd291a502 100644 --- a/clang/test/SemaTemplate/attributes.cpp +++ b/clang/test/SemaTemplate/attributes.cpp @@ -80,7 +80,7 @@ void UseStmtAnnotations() { HasStmtAnnotations<int>(); } // CHECK-NEXT: NonTypeTemplateParmDecl {{.*}} referenced 'int' depth 0 index 0 ... Is // CHECK-NEXT: FunctionDecl {{.*}} HasPackAnnotations 'void ()' // CHECK-NEXT: AnnotateAttr {{.*}} "ANNOTATE_BAZ" -// CHECK-NEXT: PackExpansionExpr {{.*}} '<dependent type>' +// CHECK-NEXT: PackExpansionExpr {{.*}} 'int' // CHECK-NEXT: DeclRefExpr {{.*}} 'int' NonTypeTemplateParm {{.*}} 'Is' 'int' // CHECK-NEXT: FunctionDecl {{.*}} used HasPackAnnotations 'void ()' // CHECK-NEXT: TemplateArgument{{.*}} pack @@ -111,7 +111,7 @@ void UsePackAnnotations() { HasPackAnnotations<1, 2, 3>(); } // CHECK-NEXT: FunctionDecl {{.*}} HasStmtPackAnnotations 'void ()' // CHECK: AttributedStmt {{.*}} // CHECK-NEXT: AnnotateAttr {{.*}} "ANNOTATE_QUUX" -// CHECK-NEXT: PackExpansionExpr {{.*}} '<dependent type>' +// CHECK-NEXT: PackExpansionExpr {{.*}} 'int' // CHECK-NEXT: DeclRefExpr {{.*}} 'int' NonTypeTemplateParm {{.*}} 'Is' 'int' // CHECK: FunctionDecl {{.*}} used HasStmtPackAnnotations 'void ()' // CHECK-NEXT: TemplateArgument{{.*}} pack @@ -152,7 +152,7 @@ void UseOnlyPackAnnotations() { // CHECK-NEXT: MoveAssignment // CHECK-NEXT: Destructor // CHECK-NEXT: AnnotateAttr {{.*}} "ANNOTATE_FOZ" -// CHECK-NEXT: PackExpansionExpr {{.*}} '<dependent type>' +// CHECK-NEXT: PackExpansionExpr {{.*}} 'int' // CHECK-NEXT: DeclRefExpr {{.*}} 'int' NonTypeTemplateParm {{.*}} 'Is' 'int' // CHECK-NEXT: CXXRecordDecl {{.*}} implicit struct AnnotatedPackTemplateStruct // CHECK-NEXT: ClassTemplateSpecializationDecl {{.*}} struct AnnotatedPackTemplateStruct definition @@ -285,7 +285,7 @@ void UseOnlyPackAnnotations() { // CHECK-NEXT: DeclRefExpr {{.*}} 'int' NonTypeTemplateParm {{.*}} 'Is' 'int' // CHECK-NEXT: NonTypeTemplateParmDecl {{.*}} referenced 'int' depth 0 index 0 ... Is // CHECK-NEXT: AnnotateAttr {{.*}} "ANNOTATE_BOO" -// CHECK-NEXT: PackExpansionExpr {{.*}} '<dependent type>' +// CHECK-NEXT: PackExpansionExpr {{.*}} 'int' // CHECK-NEXT: DeclRefExpr {{.*}} 'int' NonTypeTemplateParm {{.*}} 'Is' 'int' // CHECK-NEXT: CXXRecordDecl {{.*}} implicit struct AnnotatedPackTemplateStruct // CHECK-NEXT: ClassTemplatePartialSpecializationDecl {{.*}} struct AnnotatedPackTemplateStruct definition @@ -428,7 +428,7 @@ void UseAnnotatedPackTemplateStructSpecializations() { // CHECK-NEXT: DeclRefExpr {{.*}} 'int' NonTypeTemplateParm {{.*}} 'Is' 'int' // CHECK-NEXT: NonTypeTemplateParmDecl {{.*}} referenced 'int' depth 0 index 0 ... Is // CHECK-NEXT: AnnotateAttr {{.*}} "ANNOTATE_BIR" -// CHECK-NEXT: PackExpansionExpr {{.*}} '<dependent type>' +// CHECK-NEXT: PackExpansionExpr {{.*}} 'int' // CHECK-NEXT: DeclRefExpr {{.*}} 'int' NonTypeTemplateParm {{.*}} 'Is' 'int' // CHECK-NEXT: CXXRecordDecl {{.*}} implicit struct InvalidAnnotatedPackTemplateStruct // CHECK-NEXT: ClassTemplatePartialSpecializationDecl {{.*}} struct InvalidAnnotatedPackTemplateStruct definition @@ -478,7 +478,7 @@ void UseInvalidAnnotatedPackTemplateStruct() { // CHECK-NEXT: NonTypeTemplateParmDecl {{.*}} referenced 'int' depth 0 index 0 ... Is // CHECK-NEXT: FunctionDecl {{.*}} RedeclaredAnnotatedFunc 'void ()' // CHECK-NEXT: AnnotateAttr {{.*}} "ANNOTATE_FAR" -// CHECK-NEXT: PackExpansionExpr {{.*}} '<dependent type>' +// CHECK-NEXT: PackExpansionExpr {{.*}} 'int' // CHECK-NEXT: DeclRefExpr {{.*}} 'int' NonTypeTemplateParm {{.*}} 'Is' 'int' // CHECK-NEXT: FunctionDecl {{.*}} used RedeclaredAnnotatedFunc 'void ()' // CHECK-NEXT: TemplateArgument{{.*}} pack @@ -517,20 +517,20 @@ void UseInvalidAnnotatedPackTemplateStruct() { // CHECK-NEXT: NonTypeTemplateParmDecl {{.*}} referenced 'int' depth 0 index 0 ... Is // CHECK-NEXT: FunctionDecl {{.*}} prev {{.*}} RedeclaredAnnotatedFunc 'void ()' // CHECK-NEXT: AnnotateAttr {{.*}} Inherited "ANNOTATE_FAR" -// CHECK-NEXT: PackExpansionExpr {{.*}} '<dependent type>' +// CHECK-NEXT: PackExpansionExpr {{.*}} 'int' // CHECK-NEXT: DeclRefExpr {{.*}} 'int' NonTypeTemplateParm {{.*}} 'Is' 'int' // CHECK-NEXT: AnnotateAttr {{.*}} "ANNOTATE_BOZ" -// CHECK-NEXT: PackExpansionExpr {{.*}} '<dependent type>' +// CHECK-NEXT: PackExpansionExpr {{.*}} 'int' // CHECK-NEXT: DeclRefExpr {{.*}} 'int' NonTypeTemplateParm {{.*}} 'Is' 'int' // CHECK-NEXT: Function {{.*}} 'RedeclaredAnnotatedFunc' 'void ()' // CHECK-NEXT: FunctionTemplateDecl {{.*}} prev {{.*}} RedeclaredAnnotatedFunc // CHECK-NEXT: NonTypeTemplateParmDecl {{.*}} 'int' depth 0 index 0 ... Is // CHECK-NEXT: FunctionDecl {{.*}} prev {{.*}} RedeclaredAnnotatedFunc 'void ()' // CHECK-NEXT: AnnotateAttr {{.*}} Inherited "ANNOTATE_FAR" -// CHECK-NEXT: PackExpansionExpr {{.*}} '<dependent type>' +// CHECK-NEXT: PackExpansionExpr {{.*}} 'int' // CHECK-NEXT: DeclRefExpr {{.*}} 'int' NonTypeTemplateParm {{.*}} 'Is' 'int' // CHECK-NEXT: AnnotateAttr {{.*}} Inherited "ANNOTATE_BOZ" -// CHECK-NEXT: PackExpansionExpr {{.*}} '<dependent type>' +// CHECK-NEXT: PackExpansionExpr {{.*}} 'int' // CHECK-NEXT: DeclRefExpr {{.*}} 'int' NonTypeTemplateParm {{.*}} 'Is' 'int' // CHECK-NEXT: AnnotateAttr {{.*}} "ANNOTATE_FIZ" // CHECK-NEXT: ConstantExpr {{.*}} 'int' @@ -545,7 +545,7 @@ void UseInvalidAnnotatedPackTemplateStruct() { // CHECK-NEXT: FunctionDecl {{.*}} prev {{.*}} RedeclaredAnnotatedFunc 'void ()' // CHECK-NEXT: CompoundStmt // CHECK-NEXT: AnnotateAttr {{.*}} Inherited "ANNOTATE_FAR" -// CHECK-NEXT: PackExpansionExpr {{.*}} '<dependent type>' +// CHECK-NEXT: PackExpansionExpr {{.*}} 'int' // CHECK-NEXT: DeclRefExpr {{.*}} 'int' NonTypeTemplateParm {{.*}} 'Is' 'int' // CHECK-NEXT: AnnotateAttr {{.*}} Inherited "ANNOTATE_FIZ" // CHECK-NEXT: ConstantExpr {{.*}} 'int' diff --git a/clang/test/SemaTemplate/partial-order.cpp b/clang/test/SemaTemplate/partial-order.cpp index 0a151de390236..db2624d1766bc 100644 --- a/clang/test/SemaTemplate/partial-order.cpp +++ b/clang/test/SemaTemplate/partial-order.cpp @@ -1,6 +1,4 @@ -// RUN: %clang_cc1 -std=c++1z %s -verify - -// expected-no-diagnostics +// RUN: %clang_cc1 -std=c++26 %s -verify namespace hana_enable_if_idiom { template<bool> struct A {}; @@ -12,3 +10,40 @@ namespace hana_enable_if_idiom { }; B<C> b; } + +namespace GH132562 { + struct I { + int v = 0; + }; + + namespace t1 { + template <I... X> struct A; + template <I... X> + requires ((X.v == 0) ||...) + struct A<X...>; + } // namespace t1 + namespace t2 { + template <I... X> struct A; // expected-note {{template is declared here}} + template <int... X> struct A<X...>; + // expected-error@-1 {{is not more specialized than the primary template}} + // expected-note@-2 {{no viable conversion from 'int' to 'I'}} + + template <int... X> struct B; // expected-note {{template is declared here}} + template <I... X> struct B<X...>; + // expected-error@-1 {{is not more specialized than the primary template}} + // expected-note@-2 {{value of type 'const I' is not implicitly convertible to 'int'}} + } // namespace t2 + namespace t3 { + struct J { + int v = 0; + constexpr J(int v) : v(v) {} + }; + template <J... X> struct A; + template <int... X> struct A<X...>; + + template <int... X> struct B; // expected-note {{template is declared here}} + template <J... X> struct B<X...>; + // expected-error@-1 {{is not more specialized than the primary template}} + // expected-note@-2 {{value of type 'const J' is not implicitly convertible to 'int'}} + } // namespace t3 +} // namespace GH132562 diff --git a/clang/test/SemaTemplate/temp_arg_nontype.cpp b/clang/test/SemaTemplate/temp_arg_nontype.cpp index e989e45efb687..9363e748c7028 100644 --- a/clang/test/SemaTemplate/temp_arg_nontype.cpp +++ b/clang/test/SemaTemplate/temp_arg_nontype.cpp @@ -389,10 +389,13 @@ namespace PR17696 { namespace partial_order_different_types { template<int, int, typename T, typename, T> struct A; // expected-note@-1 {{template is declared here}} - template<int N, typename T, typename U, T V> struct A<0, N, T, U, V> {}; - template<int N, typename T, typename U, U V> struct A<0, N, T, U, V>; + template<int N, typename T, typename U, T V> struct A<0, N, T, U, V> {}; // #P1 + template<int N, typename T, typename U, U V> struct A<0, N, T, U, V>; // #P2 // expected-error@-1 {{class template partial specialization is not more specialized than the primary template}} A<0, 0, int, int, 0> a; + // expected-error@-1 {{ambiguous partial specializations}} + // expected-note@#P1 {{partial specialization matches}} + // expected-note@#P2 {{partial specialization matches}} } namespace partial_order_references { @@ -412,19 +415,18 @@ namespace partial_order_references { template<int, int &R> struct B; // expected-note 2{{template}} template<const int &R> struct B<0, R> {}; // expected-error@-1 {{not more specialized than the primary}} - // expected-note@-2 {{'const int' vs 'int &'}} + // expected-note@-2 {{value of type 'const int' is not implicitly convertible to 'int &'}} B<0, N> b; // expected-error {{undefined}} - template<int, const int &R> struct C; // expected-note 2{{template}} + template<int, const int &R> struct C; // expected-note {{template}} + // This partial specialization is more specialized than the primary template. template<int &R> struct C<0, R> {}; - // expected-error@-1 {{not more specialized than the primary}} - // expected-note@-2 {{'int' vs 'const int &'}} C<0, N> c; // expected-error {{undefined}} template<int, const int &R> struct D; // expected-note 2{{template}} template<int N> struct D<0, N> {}; // expected-error@-1 {{not more specialized than the primary}} - // expected-note@-2 {{'int' vs 'const int &'}} + // expected-note@-2 {{conversion from 'int' to 'const int &'}} extern const int K = 5; D<0, K> d; // expected-error {{undefined}} } _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits