https://github.com/mizvekov updated https://github.com/llvm/llvm-project/pull/190495
>From 7b73b23be73d40bb5367d5d2e34d24afd4b04ced Mon Sep 17 00:00:00 2001 From: Matheus Izvekov <[email protected]> Date: Sat, 4 Apr 2026 20:44:35 -0300 Subject: [PATCH] [clang] implement CWG2064: ignore value dependence for decltype The 'decltype' for a value-dependent (but non-type-dependent) should be known, so this patch makes them non-opaque instead. Readds a few test cases from da98651 Fixes #8740 Fixes #61818 Fixes #190388 --- clang/docs/ReleaseNotes.rst | 1 + clang/include/clang/AST/ASTContext.h | 48 +- clang/include/clang/AST/DependenceFlags.h | 8 +- clang/include/clang/AST/TypeBase.h | 3 +- clang/lib/AST/ASTContext.cpp | 517 ++++++++++++++++-- clang/lib/AST/ASTDiagnostic.cpp | 2 +- clang/lib/AST/DeclTemplate.cpp | 4 +- clang/lib/AST/ItaniumMangle.cpp | 15 +- clang/lib/AST/Type.cpp | 41 +- clang/lib/Sema/SemaOverload.cpp | 4 +- clang/lib/Sema/SemaTemplate.cpp | 44 +- clang/test/CXX/drs/cwg20xx.cpp | 17 +- .../test/CXX/temp/temp.decls/temp.mem/p5.cpp | 2 +- clang/test/CodeGenCXX/GH190495.cpp | 29 + clang/test/CodeGenCXX/mangle-subst.cpp | 13 +- clang/test/Sema/invalid-bitwidth-expr.mm | 1 + clang/test/SemaCXX/decltype.cpp | 11 +- clang/test/SemaCXX/source_location.cpp | 7 +- clang/test/SemaCXX/typeof.cpp | 2 +- .../SemaTemplate/concepts-out-of-line-def.cpp | 6 +- clang/test/SemaTemplate/concepts.cpp | 10 + clang/test/SemaTemplate/deduction-guide.cpp | 23 +- clang/test/SemaTemplate/dependent-expr.cpp | 8 +- .../test/SemaTemplate/injected-class-name.cpp | 44 ++ .../SemaTemplate/instantiation-dependence.cpp | 90 ++- .../SemaTemplate/partial-spec-instantiate.cpp | 7 + .../SemaTemplate/temp_arg_template_p0522.cpp | 11 +- 27 files changed, 836 insertions(+), 132 deletions(-) create mode 100644 clang/test/CodeGenCXX/GH190495.cpp diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index b2e62106506f0..af8f7141f6f74 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -435,6 +435,7 @@ Bug Fixes to C++ Support - Fixed a crash when pack expansions are used as arguments for non-pack parameters of built-in templates. (#GH180307) - Fixed a bug where captured variables in non-mutable lambdas were incorrectly treated as mutable when used inside decltype in the return type. (#GH180460) +- `decltype(expr)` now ignores value-dependence on the expression. (#GH61818) - Fixed a crash when evaluating uninitialized GCC vector/ext_vector_type vectors in ``constexpr``. (#GH180044) - Fixed a crash when `explicit(bool)` is used with an incomplete enumeration. (#GH183887) - Fixed a crash on ``typeid`` of incomplete local types during template instantiation. (#GH63242), (#GH176397) diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index ba1b58489c327..2a827376047f9 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -401,6 +401,11 @@ class ASTContext : public RefCountedBase<ASTContext> { /// current file before they are compared locally. unsigned NextStringLiteralVersion = 0; + /// A cache mapping from types to their functionally equivalent canonical + /// type. + mutable llvm::DenseMap<const Type *, QualType> + FunctionallyEquivalentTypeCache; + /// MD5 hash of CUID. It is calculated when first used and cached by this /// data member. mutable std::string CUIDHash; @@ -1975,9 +1980,10 @@ class ASTContext : public RefCountedBase<ASTContext> { Decl *AssociatedDecl, unsigned Index, UnsignedOrNone PackIndex, bool Final) const; - QualType getSubstTemplateTypeParmPackType(Decl *AssociatedDecl, - unsigned Index, bool Final, - const TemplateArgument &ArgPack); + QualType + getSubstTemplateTypeParmPackType(Decl *AssociatedDecl, unsigned Index, + bool Final, + const TemplateArgument &ArgPack) const; QualType getSubstBuiltinTemplatePack(const TemplateArgument &ArgPack); QualType @@ -1993,7 +1999,8 @@ class ASTContext : public RefCountedBase<ASTContext> { getTemplateSpecializationType(ElaboratedTypeKeyword Keyword, TemplateName T, ArrayRef<TemplateArgument> SpecifiedArgs, ArrayRef<TemplateArgument> CanonicalArgs, - QualType Underlying = QualType()) const; + QualType Underlying = QualType(), + bool Unique = false) const; QualType getTemplateSpecializationType(ElaboratedTypeKeyword Keyword, TemplateName T, @@ -2964,6 +2971,15 @@ class ASTContext : public RefCountedBase<ASTContext> { return getCanonicalType(T1) == getCanonicalType(T2); } + QualType getCanonicalType(QualType QT, bool FunctionallyEquivalent) const; + QualType getCanonicalType(QualType QT, bool FunctionallyEquivalent, + bool &AnyNonCanonical) const { + QualType R = getCanonicalType(QT, FunctionallyEquivalent); + AnyNonCanonical |= !R.isCanonical(); + return R; + } + bool hasFunctionallyEquivalentType(QualType T1, QualType T2) const; + /// Determine whether the given expressions \p X and \p Y are equivalent. bool hasSameExpr(const Expr *X, const Expr *Y) const; @@ -3118,14 +3134,30 @@ class ASTContext : public RefCountedBase<ASTContext> { /// The canonical template argument is the simplest template argument /// (which may be a type, value, expression, or declaration) that /// expresses the value of the argument. - TemplateArgument getCanonicalTemplateArgument(const TemplateArgument &Arg) - const; + TemplateArgument getCanonicalTemplateArgument(const TemplateArgument &Arg, + bool FunctionallyEquivalent, + bool &AnyNonCanonical) const; + TemplateArgument + getCanonicalTemplateArgument(const TemplateArgument &Arg, + bool FunctionallyEquivalent = false) const { + bool AnyNonCanonical = false; + return getCanonicalTemplateArgument(Arg, FunctionallyEquivalent, + AnyNonCanonical); + } /// Canonicalize the given template argument list. /// /// Returns true if any arguments were non-canonical, false otherwise. + bool canonicalizeTemplateArguments(MutableArrayRef<TemplateArgument> Args, + bool FunctionallyEquivalent, + bool &AnyNonCanonical) const; bool - canonicalizeTemplateArguments(MutableArrayRef<TemplateArgument> Args) const; + canonicalizeTemplateArguments(MutableArrayRef<TemplateArgument> Args, + bool FunctionallyEquivalent = false) const { + bool AnyNonCanonical = false; + return canonicalizeTemplateArguments(Args, FunctionallyEquivalent, + AnyNonCanonical); + } /// Canonicalize the given TemplateTemplateParmDecl. TemplateTemplateParmDecl * @@ -3268,6 +3300,8 @@ class ASTContext : public RefCountedBase<ASTContext> { // Helper for integer ordering unsigned getIntegerRank(const Type *T) const; + QualType buildFunctionallyEquivalentCanonicalType(const Type *T) const; + public: //===--------------------------------------------------------------------===// // Type Compatibility Predicates diff --git a/clang/include/clang/AST/DependenceFlags.h b/clang/include/clang/AST/DependenceFlags.h index c4395259f0758..739af906a3165 100644 --- a/clang/include/clang/AST/DependenceFlags.h +++ b/clang/include/clang/AST/DependenceFlags.h @@ -194,7 +194,13 @@ class Dependence { TypeDependence type() const { return translate(V, UnexpandedPack, TypeDependence::UnexpandedPack) | translate(V, Instantiation, TypeDependence::Instantiation) | - translate(V, Dependent, TypeDependence::Dependent) | + // There's a non-obvious choice here: Should Value dependence be + // translated to type dependence or not. After CWG2064, `decltype` + // only syntactically depends on the value of the expression. For all + // the other use cases where the value is dependended on + // semantically, this will be modeled through special purpose type + // nodes which are always type dependent anyway. + translate(V, Type, TypeDependence::Dependent) | translate(V, Error, TypeDependence::Error) | translate(V, VariablyModified, TypeDependence::VariablyModified); } diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h index 8802b15d99034..cc561dc1fe424 100644 --- a/clang/include/clang/AST/TypeBase.h +++ b/clang/include/clang/AST/TypeBase.h @@ -7496,7 +7496,8 @@ class TemplateSpecializationType : public TypeWithKeyword, void Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Ctx); static void Profile(llvm::FoldingSetNodeID &ID, ElaboratedTypeKeyword Keyword, TemplateName T, ArrayRef<TemplateArgument> Args, - QualType Underlying, const ASTContext &Context); + bool IsTypeAlias, QualType Underlying, + const ASTContext &Context); static bool classof(const Type *T) { return T->getTypeClass() == TemplateSpecialization; diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index ee7f823b014b2..65d53ea30e2d5 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -3172,21 +3172,34 @@ ASTContext::getASTObjCInterfaceLayout(const ObjCInterfaceDecl *D) const { static auto getCanonicalTemplateArguments(const ASTContext &C, ArrayRef<TemplateArgument> Args, - bool &AnyNonCanonArgs) { + bool &AnyChanged, + bool FunctionallyEquivalent, + bool &AnyNonCanonical) { SmallVector<TemplateArgument, 16> CanonArgs(Args); - AnyNonCanonArgs |= C.canonicalizeTemplateArguments(CanonArgs); + AnyChanged |= C.canonicalizeTemplateArguments( + CanonArgs, FunctionallyEquivalent, AnyNonCanonical); return CanonArgs; } +static auto getCanonicalTemplateArguments(const ASTContext &C, + ArrayRef<TemplateArgument> Args, + bool &AnyChanged) { + bool AnyNonCanonical = false; + return getCanonicalTemplateArguments( + C, Args, AnyChanged, /*FunctionallyEquivalent=*/false, AnyNonCanonical); +} + bool ASTContext::canonicalizeTemplateArguments( - MutableArrayRef<TemplateArgument> Args) const { - bool AnyNonCanonArgs = false; + MutableArrayRef<TemplateArgument> Args, bool FunctionallyEquivalent, + bool &AnyNonCanonical) const { + bool AnyChanged = false; for (auto &Arg : Args) { TemplateArgument OrigArg = Arg; - Arg = getCanonicalTemplateArgument(Arg); - AnyNonCanonArgs |= !Arg.structurallyEquals(OrigArg); + Arg = getCanonicalTemplateArgument(Arg, FunctionallyEquivalent, + AnyNonCanonical); + AnyChanged |= !Arg.structurallyEquals(OrigArg); } - return AnyNonCanonArgs; + return AnyChanged; } //===----------------------------------------------------------------------===// @@ -5861,10 +5874,9 @@ QualType ASTContext::getSubstTemplateTypeParmType(QualType Replacement, return QualType(SubstParm, 0); } -QualType -ASTContext::getSubstTemplateTypeParmPackType(Decl *AssociatedDecl, - unsigned Index, bool Final, - const TemplateArgument &ArgPack) { +QualType ASTContext::getSubstTemplateTypeParmPackType( + Decl *AssociatedDecl, unsigned Index, bool Final, + const TemplateArgument &ArgPack) const { #ifndef NDEBUG for (const auto &P : ArgPack.pack_elements()) assert(P.getKind() == TemplateArgument::Type && "Pack contains a non-type"); @@ -6041,8 +6053,8 @@ QualType ASTContext::getCanonicalTemplateSpecializationType( #endif llvm::FoldingSetNodeID ID; - TemplateSpecializationType::Profile(ID, Keyword, Template, Args, QualType(), - *this); + TemplateSpecializationType::Profile(ID, Keyword, Template, Args, + /*IsTypeAlias=*/false, QualType(), *this); void *InsertPos = nullptr; if (auto *T = TemplateSpecializationTypes.FindNodeOrInsertPos(ID, InsertPos)) return QualType(T, 0); @@ -6063,9 +6075,27 @@ QualType ASTContext::getCanonicalTemplateSpecializationType( QualType ASTContext::getTemplateSpecializationType( ElaboratedTypeKeyword Keyword, TemplateName Template, ArrayRef<TemplateArgument> SpecifiedArgs, - ArrayRef<TemplateArgument> CanonicalArgs, QualType Underlying) const { - const auto *TD = Template.getAsTemplateDecl(/*IgnoreDeduced=*/true); - bool IsTypeAlias = TD && TD->isTypeAlias(); + ArrayRef<TemplateArgument> CanonicalArgs, QualType Underlying, + bool Unique) const { + + bool IsTypeAlias = false; + if (!Underlying.isNull()) { + const auto *TD = Template.getAsTemplateDecl(/*IgnoreDeduced=*/true); + IsTypeAlias = TD && TD->isTypeAlias(); + if (!IsTypeAlias) + Underlying = getCanonicalType(Underlying); + } + + llvm::FoldingSetNodeID ID; + void *InsertPos = nullptr; + if (Unique) { + TemplateSpecializationType::Profile(ID, Keyword, Template, SpecifiedArgs, + IsTypeAlias, Underlying, *this); + if (auto *T = + TemplateSpecializationTypes.FindNodeOrInsertPos(ID, InsertPos)) + return QualType(T, 0); + } + if (Underlying.isNull()) { TemplateName CanonTemplate = getCanonicalTemplateName(Template, /*IgnoreDeduced=*/true); @@ -6087,18 +6117,16 @@ QualType ASTContext::getTemplateSpecializationType( }); } - // We can get here with an alias template when the specialization - // contains a pack expansion that does not match up with a parameter - // pack, or a builtin template which cannot be resolved due to dependency. - assert((!isa_and_nonnull<TypeAliasTemplateDecl>(TD) || - hasAnyPackExpansions(CanonicalArgs)) && - "Caller must compute aliased type"); - IsTypeAlias = false; - Underlying = getCanonicalTemplateSpecializationType( CanonKeyword, CanonTemplate, CanonicalArgs); if (!NonCanonical) return Underlying; + + if (Unique) { + [[maybe_unused]] auto *T = + TemplateSpecializationTypes.FindNodeOrInsertPos(ID, InsertPos); + assert(!T && "broken canonical type"); + } } void *Mem = Allocate(sizeof(TemplateSpecializationType) + sizeof(TemplateArgument) * SpecifiedArgs.size() + @@ -6107,6 +6135,8 @@ QualType ASTContext::getTemplateSpecializationType( auto *Spec = new (Mem) TemplateSpecializationType( Keyword, Template, IsTypeAlias, SpecifiedArgs, Underlying); Types.push_back(Spec); + if (Unique) + TemplateSpecializationTypes.InsertNode(Spec, InsertPos); return QualType(Spec, 0); } @@ -6691,12 +6721,11 @@ QualType ASTContext::getReferenceQualifiedType(const Expr *E) const { /// expression, and would not give a significant memory saving, since there /// is an Expr tree under each such type. QualType ASTContext::getDecltypeType(Expr *E, QualType UnderlyingType) const { - // C++11 [temp.type]p2: - // If an expression e involves a template parameter, decltype(e) denotes a - // unique dependent type. Two such decltype-specifiers refer to the same - // type only if their expressions are equivalent (14.5.6.1). + // C++26 [temp.type]p4: If an expression e is type-dependent, decltype(e) + // denotes a unique dependent type. Two such decltype-specifiers refer to the + // same type only if their expressions are equivalent ([temp.over.link]). QualType CanonType; - if (!E->isInstantiationDependent()) { + if (!E->isTypeDependent()) { CanonType = getCanonicalType(UnderlyingType); } else if (!UnderlyingType.isNull()) { CanonType = getDecltypeType(E, QualType()); @@ -7888,51 +7917,66 @@ bool ASTContext::isSameEntity(const NamedDecl *X, const NamedDecl *Y) const { } TemplateArgument -ASTContext::getCanonicalTemplateArgument(const TemplateArgument &Arg) const { +ASTContext::getCanonicalTemplateArgument(const TemplateArgument &Arg, + bool FunctionallyEquivalent, + bool &AnyNonCanonical) const { switch (Arg.getKind()) { case TemplateArgument::Null: return Arg; case TemplateArgument::Expression: + // FIXME: Do functionally equivalent expressions need different profiling? return TemplateArgument(Arg.getAsExpr(), /*IsCanonical=*/true, Arg.getIsDefaulted()); - case TemplateArgument::Declaration: { - auto *D = cast<ValueDecl>(Arg.getAsDecl()->getCanonicalDecl()); - return TemplateArgument(D, getCanonicalType(Arg.getParamTypeForDecl()), - Arg.getIsDefaulted()); - } + case TemplateArgument::Declaration: + return TemplateArgument( + cast<ValueDecl>(Arg.getAsDecl()->getCanonicalDecl()), + getCanonicalType(Arg.getParamTypeForDecl(), FunctionallyEquivalent, + AnyNonCanonical), + Arg.getIsDefaulted()); case TemplateArgument::NullPtr: - return TemplateArgument(getCanonicalType(Arg.getNullPtrType()), - /*isNullPtr*/ true, Arg.getIsDefaulted()); + return TemplateArgument(getCanonicalType(Arg.getNullPtrType(), + FunctionallyEquivalent, + AnyNonCanonical), + /*isNullPtr=*/true, Arg.getIsDefaulted()); case TemplateArgument::Template: + // FIXME: Implement functional canonicalization of template names. return TemplateArgument(getCanonicalTemplateName(Arg.getAsTemplate()), Arg.getIsDefaulted()); case TemplateArgument::TemplateExpansion: + // FIXME: Implement functional canonicalization of template names. return TemplateArgument( getCanonicalTemplateName(Arg.getAsTemplateOrTemplatePattern()), Arg.getNumTemplateExpansions(), Arg.getIsDefaulted()); case TemplateArgument::Integral: - return TemplateArgument(Arg, getCanonicalType(Arg.getIntegralType())); + return TemplateArgument(Arg, getCanonicalType(Arg.getIntegralType(), + FunctionallyEquivalent, + AnyNonCanonical)); case TemplateArgument::StructuralValue: return TemplateArgument(*this, - getCanonicalType(Arg.getStructuralValueType()), + getCanonicalType(Arg.getStructuralValueType(), + FunctionallyEquivalent, + AnyNonCanonical), Arg.getAsStructuralValue(), Arg.getIsDefaulted()); case TemplateArgument::Type: - return TemplateArgument(getCanonicalType(Arg.getAsType()), - /*isNullPtr*/ false, Arg.getIsDefaulted()); + return TemplateArgument(getCanonicalType(Arg.getAsType(), + FunctionallyEquivalent, + AnyNonCanonical), + /*isNullPtr=*/false, Arg.getIsDefaulted()); case TemplateArgument::Pack: { - bool AnyNonCanonArgs = false; + bool AnyChanged = false; auto CanonArgs = ::getCanonicalTemplateArguments( - *this, Arg.pack_elements(), AnyNonCanonArgs); - if (!AnyNonCanonArgs) + *this, Arg.pack_elements(), AnyChanged, FunctionallyEquivalent, + AnyNonCanonical); + if (!AnyChanged) return Arg; auto NewArg = TemplateArgument::CreatePackCopy( const_cast<ASTContext &>(*this), CanonArgs); @@ -13858,6 +13902,391 @@ unsigned ASTContext::getTargetAddressSpace(LangAS AS) const { return getTargetInfo().getTargetAddressSpace(AS); } +static NestedNameSpecifier +getFunctionallyEquivalentCanonicalQualifier(const ASTContext &Context, + NestedNameSpecifier Qualifier, + bool &AnyNonCanonical) { + if (Qualifier.getKind() == NestedNameSpecifier::Kind::Type) + return NestedNameSpecifier( + Context + .getCanonicalType(QualType(Qualifier.getAsType(), 0), + /*FunctionallyEquivalent=*/true, AnyNonCanonical) + .getTypePtr()); + return Qualifier.getCanonical(); +} + +QualType ASTContext::getCanonicalType(QualType QT, + bool FunctionallyEquivalent) const { + if (!FunctionallyEquivalent) + return QT.getCanonicalType(); + + // A canonical type is functionally equivalent to itself. + if (QT.isCanonical()) + return QT; + + // A non-instantiation-dependent type is functionally equivalent to its + // canonical type. + if (!QT->isInstantiationDependentType()) + return getCanonicalType(QT); + auto [T, Qualifiers] = QT.split(); + + auto It = FunctionallyEquivalentTypeCache.find(T); + if (It != FunctionallyEquivalentTypeCache.end()) + return It->second; + + QualType R = buildFunctionallyEquivalentCanonicalType(T); + assert(hasSameType(QualType(T, 0), R)); + + auto [_, Inserted] = FunctionallyEquivalentTypeCache.try_emplace(T, R); + assert(Inserted && "Unexpected cache entry for type"); + + return getQualifiedType(R, Qualifiers); +} + +QualType +ASTContext::buildFunctionallyEquivalentCanonicalType(const Type *T) const { + assert(!T->isCanonicalUnqualified()); + assert(T->isInstantiationDependentType()); + + // If none of the inputs became non-canonical, just return the canonical type. + // It's not helpful for the applications of this transform to track whether + // the inputs changed, because they will mostly refer to template parameters + // that haven't been canonicalized. + bool AnyNonCanonical = false; + switch (T->getTypeClass()) { + case Type::Builtin: + llvm_unreachable( + "always canonical types should have been handled by this point"); + case Type::PredefinedSugar: + llvm_unreachable("never-instantation-dependent types should have been " + "handled by this point"); + case Type::Complex: + case Type::FunctionNoProto: + case Type::Adjusted: + case Type::Decayed: + case Type::Auto: + case Type::DeducedTemplateSpecialization: + case Type::ObjCObject: + case Type::ObjCInterface: + case Type::ObjCObjectPointer: + case Type::Atomic: + case Type::Record: + case Type::Enum: + case Type::DependentVector: + case Type::Vector: + case Type::ArrayParameter: + case Type::BTFTagAttributed: + case Type::BitInt: + case Type::CountAttributed: + case Type::HLSLAttributedResource: + case Type::HLSLInlineSpirv: + case Type::MacroQualified: + case Type::ObjCTypeParam: + case Type::Pipe: + case Type::TypeOf: + case Type::TypeOfExpr: + case Type::Using: + case Type::DependentBitInt: + case Type::OverflowBehavior: + case Type::SubstBuiltinTemplatePack: + case Type::UnresolvedUsing: + T->dump(); + llvm_unreachable("unimplemented"); + case Type::FunctionProto: { + const auto *TT = cast<FunctionProtoType>(T); + QualType RT = getCanonicalType( + TT->getReturnType(), /*FunctionallyEquivalent=*/true, AnyNonCanonical); + SmallVector<QualType, 8> PTs(TT->getParamTypes().size()); + llvm::transform(TT->getParamTypes(), PTs.begin(), [&](QualType PT) { + return getCanonicalType(PT, /*FunctionallyEquivalent=*/true, + AnyNonCanonical); + }); + if (!AnyNonCanonical) + break; + return getFunctionType(RT, PTs, TT->getExtProtoInfo()); + } + case Type::Pointer: { + const auto *TT = cast<PointerType>(T); + QualType Pointee = getCanonicalType( + TT->getPointeeType(), /*FunctionallyEquivalent=*/true, AnyNonCanonical); + if (!AnyNonCanonical) + break; + return getPointerType(Pointee); + } + case Type::LValueReference: { + const auto *TT = cast<LValueReferenceType>(T); + QualType Pointee = getCanonicalType( + TT->getPointeeType(), /*FunctionallyEquivalent=*/true, AnyNonCanonical); + if (!AnyNonCanonical) + break; + return getLValueReferenceType(Pointee); + } + case Type::RValueReference: { + const auto *TT = cast<RValueReferenceType>(T); + QualType Pointee = getCanonicalType( + TT->getPointeeType(), /*FunctionallyEquivalent=*/true, AnyNonCanonical); + if (!AnyNonCanonical) + break; + return getRValueReferenceType(Pointee); + } + case Type::InjectedClassName: { + const auto *TT = cast<InjectedClassNameType>(T); + + ElaboratedTypeKeyword Keyword = + ::getCanonicalElaboratedTypeKeyword(TT->getKeyword()); + NestedNameSpecifier Qualifier = + ::getFunctionallyEquivalentCanonicalQualifier(*this, TT->getQualifier(), + AnyNonCanonical); + const TagDecl *ND = TT->getDecl()->getCanonicalDecl(); + if (!AnyNonCanonical) + break; + return getTagType(Keyword, Qualifier, ND, /*OwnsTag=*/false); + } + case Type::Attributed: + return getCanonicalType(cast<AttributedType>(T)->desugar(), + /*FunctionallyEquivalent=*/true, AnyNonCanonical); + case Type::Paren: + return getCanonicalType(cast<ParenType>(T)->desugar(), + /*FunctionallyEquivalent=*/true, AnyNonCanonical); + case Type::PackExpansion: { + const auto *TT = cast<PackExpansionType>(T); + QualType Pattern = getCanonicalType( + TT->getPattern(), /*FunctionallyEquivalent=*/true, AnyNonCanonical); + if (!AnyNonCanonical) + break; + return getPackExpansionType(Pattern, TT->getNumExpansions(), + /*ExpectPackInType=*/false); + } + case Type::PackIndexing: { + const auto *TT = cast<PackIndexingType>(T); + + // This type holds on to instantiation-dependence. + assert(!TT->isSugared()); + + QualType Pattern = getCanonicalType( + TT->getPattern(), /*FunctionallyEquivalent=*/true, AnyNonCanonical); + SmallVector<QualType, 8> Expansions(TT->getExpansions().size()); + llvm::transform(TT->getExpansions(), Expansions.begin(), [&](QualType E) { + return getCanonicalType(E, /*FunctionallyEquivalent=*/true, + AnyNonCanonical); + }); + if (!AnyNonCanonical) + break; + return getPackIndexingType(Pattern, TT->getIndexExpr(), + TT->isFullySubstituted(), Expansions, + TT->getSelectedIndex()); + } + case Type::UnaryTransform: + // FIXME: Unimplemented. These appear to have broken canonicalization. + break; + case Type::MemberPointer: { + const auto *TT = cast<MemberPointerType>(T); + const auto *Cls = TT->getMostRecentCXXRecordDecl(); + NestedNameSpecifier Qualifier = + ::getFunctionallyEquivalentCanonicalQualifier(*this, TT->getQualifier(), + AnyNonCanonical); + // If the qualifier became canonical and we have a class (ie it is + // non-dependent), then we can drop the qualifier entirely. + if (Cls && !AnyNonCanonical) + Qualifier = std::nullopt; + QualType Pointee = getCanonicalType( + TT->getPointeeType(), /*FunctionallyEquivalent=*/true, AnyNonCanonical); + if (!AnyNonCanonical) + break; + return getMemberPointerType(Pointee, Qualifier, Cls); + } + case Type::BlockPointer: { + const auto *TT = cast<BlockPointerType>(T); + QualType Pointee = getCanonicalType( + TT->getPointeeType(), /*FunctionallyEquivalent=*/true, AnyNonCanonical); + if (!AnyNonCanonical) + break; + return getBlockPointerType(Pointee); + } + case Type::IncompleteArray: { + const auto *TT = cast<IncompleteArrayType>(T); + QualType ElementType = getCanonicalType( + TT->getElementType(), /*FunctionallyEquivalent=*/true, AnyNonCanonical); + if (!AnyNonCanonical) + break; + return getIncompleteArrayType(ElementType, TT->getSizeModifier(), + TT->getIndexTypeCVRQualifiers()); + } + case Type::ConstantArray: { + const auto *TT = cast<ConstantArrayType>(T); + QualType ElementType = getCanonicalType( + TT->getElementType(), /*FunctionallyEquivalent=*/true, AnyNonCanonical); + if (!AnyNonCanonical) + break; + return getConstantArrayType(ElementType, TT->getSize(), TT->getSizeExpr(), + TT->getSizeModifier(), + TT->getIndexTypeCVRQualifiers()); + } + case Type::VariableArray: { + const auto *TT = cast<VariableArrayType>(T); + QualType ElementType = getCanonicalType( + TT->getElementType(), /*FunctionallyEquivalent=*/true, AnyNonCanonical); + if (!AnyNonCanonical) + break; + return getVariableArrayType(ElementType, TT->getSizeExpr(), + TT->getSizeModifier(), + TT->getIndexTypeCVRQualifiers()); + } + case Type::DependentSizedArray: { + const auto *TT = cast<DependentSizedArrayType>(T); + QualType ElementType = getCanonicalType( + TT->getElementType(), /*FunctionallyEquivalent=*/true, AnyNonCanonical); + if (!AnyNonCanonical) + break; + return getDependentSizedArrayType(ElementType, TT->getSizeExpr(), + TT->getSizeModifier(), + TT->getIndexTypeCVRQualifiers()); + } + case Type::ExtVector: { + const auto *TT = cast<ExtVectorType>(T); + QualType ElementType = getCanonicalType( + TT->getElementType(), /*FunctionallyEquivalent=*/true, AnyNonCanonical); + if (!AnyNonCanonical) + break; + return getExtVectorType(ElementType, TT->getNumElements()); + } + case Type::DependentSizedExtVector: { + const auto *TT = cast<DependentSizedExtVectorType>(T); + QualType ElementType = getCanonicalType( + TT->getElementType(), /*FunctionallyEquivalent=*/true, AnyNonCanonical); + if (!AnyNonCanonical) + break; + return getDependentSizedExtVectorType(ElementType, TT->getSizeExpr(), + TT->getAttributeLoc()); + } + case Type::ConstantMatrix: { + const auto *TT = cast<ConstantMatrixType>(T); + QualType ElementType = getCanonicalType( + TT->getElementType(), /*FunctionallyEquivalent=*/true, AnyNonCanonical); + if (!AnyNonCanonical) + break; + return getConstantMatrixType(ElementType, TT->getNumRows(), + TT->getNumColumns()); + } + case Type::DependentSizedMatrix: { + const auto *TT = cast<DependentSizedMatrixType>(T); + QualType ElementType = getCanonicalType( + TT->getElementType(), /*FunctionallyEquivalent=*/true, AnyNonCanonical); + if (!AnyNonCanonical) + break; + return getDependentSizedMatrixType(ElementType, TT->getRowExpr(), + TT->getColumnExpr(), + TT->getAttributeLoc()); + } + case Type::DependentAddressSpace: { + const auto *TT = cast<DependentAddressSpaceType>(T); + QualType Pointee = getCanonicalType( + TT->getPointeeType(), /*FunctionallyEquivalent=*/true, AnyNonCanonical); + if (!AnyNonCanonical) + break; + return getDependentAddressSpaceType(Pointee, TT->getAddrSpaceExpr(), + TT->getAttributeLoc()); + } + case Type::SubstTemplateTypeParmPack: { + const auto *TT = cast<SubstTemplateTypeParmPackType>(T); + TemplateArgument ArgPack = getCanonicalTemplateArgument( + TT->getArgumentPack(), /*FunctionallyEquivalent=*/true, + AnyNonCanonical); + if (!AnyNonCanonical) + break; + return getSubstTemplateTypeParmPackType( + TT->getAssociatedDecl()->getCanonicalDecl(), TT->getIndex(), + TT->getFinal(), ArgPack); + } + case Type::DependentName: { + const auto *TT = cast<DependentNameType>(T); + ElaboratedTypeKeyword Keyword = + ::getCanonicalElaboratedTypeKeyword(TT->getKeyword()); + NestedNameSpecifier Qualifier = + ::getFunctionallyEquivalentCanonicalQualifier(*this, TT->getQualifier(), + AnyNonCanonical); + if (!AnyNonCanonical) + break; + return getDependentNameType(Keyword, Qualifier, TT->getIdentifier()); + } + case Type::TemplateSpecialization: { + const auto *TT = cast<TemplateSpecializationType>(T); + // For a type alias, we want to keep it only if there are unused + // instantiation-dependent template arguments. + if (TT->isTypeAlias()) { + const TemplateDecl *TD = TT->getTemplateName().getAsTemplateDecl(); + auto As = TT->template_arguments(); + bool AnyUnusedInstantiationDependentArgs = false; + for (const NamedDecl *PD : TD->getTemplateParameters()->asArray()) { + if (As.empty()) + break; + auto CurAs = As; + // If this is a parameter pack, a use of this parameter means all of the + // remaining template arguments are used. + if (!PD->isTemplateParameterPack()) + CurAs = CurAs.take_front(1); + if (!PD->isReferenced()) { + auto Dep = TemplateArgumentDependence::None; + for (const auto &A : CurAs) + Dep |= A.getDependence(); + if (Dep & TemplateArgumentDependence::Instantiation) { + AnyUnusedInstantiationDependentArgs = true; + break; + } + } + As = As.drop_front(CurAs.size()); + } + if (!AnyUnusedInstantiationDependentArgs) + return getCanonicalType(TT->getAliasedType(), + /*FunctionallyEquivalent=*/true, + AnyNonCanonical); + // Otherwise, we are keeping the type alias. This on itself makes the + // resulting type non-canonical. + AnyNonCanonical = true; + } + ElaboratedTypeKeyword Keyword = + ::getCanonicalElaboratedTypeKeyword(TT->getKeyword()); + // FIXME: Implement getFunctionallyEquivalentCanonicalTemplateName and use + // it here. + TemplateName TN = + getCanonicalTemplateName(TT->getTemplateName(), /*IgnoreDeduced=*/true); + // FIXME: We can't avoid rebuilding if nothing changed, because we can't + // rely on this TST to have been uniqued. + bool AnyChanged = false; + auto As = ::getCanonicalTemplateArguments( + *this, TT->template_arguments(), AnyChanged, + /*FunctionallyEquivalent=*/true, AnyNonCanonical); + if (!AnyNonCanonical) + break; + return getTemplateSpecializationType( + Keyword, TN, As, + /*CanonicalArgs=*/ArrayRef<TemplateArgument>(), + TT->desugar().getCanonicalType(), + /*Unique=*/true); + } + case Type::Decltype: { + const auto *TT = cast<DecltypeType>(T); + AnyNonCanonical |= TT->isSugared(); + if (!AnyNonCanonical) + break; + return QualType(TT, 0); + } + // These types don't have other types in their spelling, so they always + // canonicalize to the equivalent form. + case Type::Typedef: + case Type::SubstTemplateTypeParm: + case Type::TemplateTypeParm: + break; + } + return T->getCanonicalTypeInternal(); +} + +bool ASTContext::hasFunctionallyEquivalentType(QualType T1, QualType T2) const { + return hasSameType(T1, T2) && + getCanonicalType(T1, /*FunctionallyEquivalent=*/true) == + getCanonicalType(T2, /*FunctionallyEquivalent=*/true); +} + bool ASTContext::hasSameExpr(const Expr *X, const Expr *Y) const { if (X == Y) return true; diff --git a/clang/lib/AST/ASTDiagnostic.cpp b/clang/lib/AST/ASTDiagnostic.cpp index b8023cb6fa10f..9f79635ad4c54 100644 --- a/clang/lib/AST/ASTDiagnostic.cpp +++ b/clang/lib/AST/ASTDiagnostic.cpp @@ -126,7 +126,7 @@ QualType clang::desugarForDiagnostic(ASTContext &Context, QualType QT, ShouldAKA = true; QT = Context.getTemplateSpecializationType( TST->getKeyword(), TST->getTemplateName(), Args, - /*CanonicalArgs=*/{}, QT); + /*CanonicalArgs=*/{}, QT.getCanonicalType()); } break; } diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp index 99d02fdc99e92..29c10c45e62da 100644 --- a/clang/lib/AST/DeclTemplate.cpp +++ b/clang/lib/AST/DeclTemplate.cpp @@ -1202,11 +1202,13 @@ CanQualType ClassTemplatePartialSpecializationDecl::getCanonicalInjectedSpecializationType( const ASTContext &Ctx) const { if (CanonInjectedTST.isNull()) { + SmallVector<TemplateArgument, 4> CanonicalArgs(getTemplateArgs().asArray()); + Ctx.canonicalizeTemplateArguments(CanonicalArgs); CanonInjectedTST = CanQualType::CreateUnsafe(Ctx.getCanonicalTemplateSpecializationType( ElaboratedTypeKeyword::None, TemplateName(getSpecializedTemplate()->getCanonicalDecl()), - getTemplateArgs().asArray())); + CanonicalArgs)); } return CanonInjectedTST; } diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp index f58faa03bfa8c..1b22e401c5fb7 100644 --- a/clang/lib/AST/ItaniumMangle.cpp +++ b/clang/lib/AST/ItaniumMangle.cpp @@ -3043,6 +3043,11 @@ void CXXNameMangler::mangleType(QualType T) { if (!TST->isTypeAlias()) break; + // instantiation-dependent decltypes are mangled through their + // expressions. + if (T->isInstantiationDependentType() && isa<DecltypeType>(T)) + break; + // FIXME: We presumably shouldn't strip off ElaboratedTypes with // instantation-dependent qualifiers. See // https://github.com/itanium-cxx-abi/cxx-abi/issues/114. @@ -7040,8 +7045,9 @@ static bool hasMangledSubstitutionQualifiers(QualType T) { bool CXXNameMangler::mangleSubstitution(QualType T) { if (!hasMangledSubstitutionQualifiers(T)) { - if (const auto *RD = T->getAsCXXRecordDecl()) - return mangleSubstitution(RD); + if (const auto *TT = dyn_cast<TagType>(T); + isa_and_nonnull<RecordType, InjectedClassNameType>(TT)) + return mangleSubstitution(TT->getDecl()); } uintptr_t TypePtr = reinterpret_cast<uintptr_t>(T.getAsOpaquePtr()); @@ -7210,8 +7216,9 @@ bool CXXNameMangler::mangleStandardSubstitution(const NamedDecl *ND) { void CXXNameMangler::addSubstitution(QualType T) { if (!hasMangledSubstitutionQualifiers(T)) { - if (const auto *RD = T->getAsCXXRecordDecl()) { - addSubstitution(RD); + if (const auto *TT = dyn_cast<TagType>(T); + isa_and_nonnull<RecordType, InjectedClassNameType>(TT)) { + addSubstitution(TT->getDecl()); return; } } diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 78983fd38410d..992e72c8e8e14 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -428,9 +428,16 @@ BitIntType::BitIntType(bool IsUnsigned, unsigned NumBits) NumBits(NumBits) {} DependentBitIntType::DependentBitIntType(bool IsUnsigned, Expr *NumBitsExpr) + // DependentBitIntType must always be type-dependent. + // The expression must be value-dependent, so will also be + // instantiation-dependent. : Type(DependentBitInt, QualType{}, - toTypeDependence(NumBitsExpr->getDependence())), - ExprAndUnsigned(NumBitsExpr, IsUnsigned) {} + toTypeDependence(NumBitsExpr->getDependence()) | + TypeDependence::Dependent), + ExprAndUnsigned(NumBitsExpr, IsUnsigned) { + assert(NumBitsExpr->isValueDependent() && + "NumBitsExpr must be value-dependent"); +} bool DependentBitIntType::isUnsigned() const { return ExprAndUnsigned.getInt(); @@ -4219,15 +4226,10 @@ DecltypeType::DecltypeType(Expr *E, QualType underlyingType, QualType can) // C++11 [temp.type]p2: "If an expression e involves a template parameter, // decltype(e) denotes a unique dependent type." Hence a decltype type is // type-dependent even if its expression is only instantiation-dependent. - : Type(Decltype, can, - toTypeDependence(E->getDependence()) | - (E->isInstantiationDependent() ? TypeDependence::Dependent - : TypeDependence::None) | - (E->getType()->getDependence() & - TypeDependence::VariablyModified)), - E(E), UnderlyingType(underlyingType) {} + : Type(Decltype, can, toTypeDependence(E->getDependence())), E(E), + UnderlyingType(underlyingType) {} -bool DecltypeType::isSugared() const { return !E->isInstantiationDependent(); } +bool DecltypeType::isSugared() const { return !E->isTypeDependent(); } QualType DecltypeType::desugar() const { if (isSugared()) @@ -4711,23 +4713,30 @@ QualType TemplateSpecializationType::getAliasedType() const { } bool clang::TemplateSpecializationType::isSugared() const { - return !isDependentType() || isCurrentInstantiation() || isTypeAlias() || - (isPackProducingBuiltinTemplateName(Template) && - isa<SubstBuiltinTemplatePackType>(*getCanonicalTypeInternal())); + return isTypeAlias() || + !isa<TemplateSpecializationType>(getCanonicalTypeInternal()); } void TemplateSpecializationType::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Ctx) { - Profile(ID, getKeyword(), Template, template_arguments(), - isSugared() ? desugar() : QualType(), Ctx); + Profile(ID, getKeyword(), Template, template_arguments(), isTypeAlias(), + desugar(), Ctx); } void TemplateSpecializationType::Profile(llvm::FoldingSetNodeID &ID, ElaboratedTypeKeyword Keyword, TemplateName T, ArrayRef<TemplateArgument> Args, - QualType Underlying, + bool IsTypeAlias, QualType Underlying, const ASTContext &Context) { + assert(IsTypeAlias || Underlying.isNull() || Underlying.isCanonical()); + if (!Underlying.isNull()) { + if (!IsTypeAlias && isa<TemplateSpecializationType>(Underlying)) + Underlying = QualType(); + } else { + assert(!IsTypeAlias); + } + ID.AddInteger(llvm::to_underlying(Keyword)); T.Profile(ID); Underlying.Profile(ID); diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 11e771bc240f1..0c26e5f5b1426 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -1409,7 +1409,7 @@ static bool IsOverloadOrOverrideImpl(Sema &SemaRef, FunctionDecl *New, bool SameTemplateParameterList = SemaRef.TemplateParameterListsAreEqual( NewTemplate, NewTemplate->getTemplateParameters(), OldTemplate, OldTemplate->getTemplateParameters(), false, Sema::TPL_TemplateMatch); - bool SameReturnType = SemaRef.Context.hasSameType( + bool SameReturnType = SemaRef.Context.hasFunctionallyEquivalentType( Old->getDeclaredReturnType(), New->getDeclaredReturnType()); // FIXME(GH58571): Match template parameter list even for non-constrained // template heads. This currently ensures that the code prior to C++20 is @@ -3583,7 +3583,7 @@ bool Sema::FunctionParamTypesAreEqual(ArrayRef<QualType> Old, QualType NewType = Context.removePtrSizeAddrSpace((New.begin() + J)->getUnqualifiedType()); - if (!Context.hasSameType(OldType, NewType)) { + if (!Context.hasFunctionallyEquivalentType(OldType, NewType)) { if (ArgPos) *ArgPos = Idx; return false; diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index c436b7018a2bd..94fbf3014787b 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -3561,7 +3561,13 @@ static QualType checkBuiltinTemplateIdType( // Synthesize a new template argument list, removing duplicates. for (auto T : Ts.getPackAsArray()) { assert(T.getKind() == clang::TemplateArgument::Type); - if (!Seen.insert(T.getAsType().getCanonicalType()).second) + // FIXME: BTK__builtin_dedup_pack is not considered an alias template, so + // the template specialization cannot store a non-canonical underlying + // type. + // When that is fixed, this can use getCommonSugar so it doesn't preserve + // arbitrary sugar. + T = Context.getCanonicalTemplateArgument(T); + if (!Seen.insert(T.getAsType()).second) continue; OutArgs.push_back(T); } @@ -8907,14 +8913,14 @@ DeclResult Sema::ActOnClassTemplateSpecialization( if (isPartialSpecialization) { if (CheckTemplatePartialSpecializationArgs(TemplateNameLoc, ClassTemplate, TemplateArgs.size(), - CTAI.CanonicalConverted)) + CTAI.SugaredConverted)) return true; // FIXME: Move this to CheckTemplatePartialSpecializationArgs so we // also do it during instantiation. if (!Name.isDependent() && !TemplateSpecializationType::anyDependentTemplateArguments( - TemplateArgs, CTAI.CanonicalConverted)) { + TemplateArgs, CTAI.SugaredConverted)) { Diag(TemplateNameLoc, diag::err_partial_spec_fully_specialized) << ClassTemplate->getDeclName(); isPartialSpecialization = false; @@ -8922,15 +8928,20 @@ DeclResult Sema::ActOnClassTemplateSpecialization( } } + SmallVector<TemplateArgument, 4> FunctionallyEquivalentConverted = + CTAI.SugaredConverted; + Context.canonicalizeTemplateArguments(FunctionallyEquivalentConverted, + /*FunctionallyEquivalent=*/true); + void *InsertPos = nullptr; ClassTemplateSpecializationDecl *PrevDecl = nullptr; if (isPartialSpecialization) PrevDecl = ClassTemplate->findPartialSpecialization( - CTAI.CanonicalConverted, TemplateParams, InsertPos); + FunctionallyEquivalentConverted, TemplateParams, InsertPos); else - PrevDecl = - ClassTemplate->findSpecialization(CTAI.CanonicalConverted, InsertPos); + PrevDecl = ClassTemplate->findSpecialization( + FunctionallyEquivalentConverted, InsertPos); ClassTemplateSpecializationDecl *Specialization = nullptr; @@ -8947,7 +8958,8 @@ DeclResult Sema::ActOnClassTemplateSpecialization( // this explicit specialization or friend declaration. Specialization = ClassTemplateSpecializationDecl::Create( Context, Kind, ClassTemplate->getDeclContext(), KWLoc, TemplateNameLoc, - ClassTemplate, CTAI.CanonicalConverted, CTAI.StrictPackMatch, PrevDecl); + ClassTemplate, FunctionallyEquivalentConverted, CTAI.StrictPackMatch, + PrevDecl); Specialization->setTemplateArgsAsWritten(TemplateArgs); SetNestedNameSpecifier(*this, Specialization, SS); if (TemplateParameterLists.size() > 0) { @@ -8958,14 +8970,13 @@ DeclResult Sema::ActOnClassTemplateSpecialization( if (!PrevDecl) ClassTemplate->AddSpecialization(Specialization, InsertPos); } else { - CanQualType CanonType = CanQualType::CreateUnsafe( - Context.getCanonicalTemplateSpecializationType( - ElaboratedTypeKeyword::None, - TemplateName(ClassTemplate->getCanonicalDecl()), - CTAI.CanonicalConverted)); - if (Context.hasSameType( - CanonType, - ClassTemplate->getCanonicalInjectedSpecializationType(Context)) && + QualType CanonType = Context.getTemplateSpecializationType( + ElaboratedTypeKeyword::None, + TemplateName(ClassTemplate->getCanonicalDecl()), + FunctionallyEquivalentConverted, /*CanonicalArgs=*/{}, + /*Underlying=*/QualType(), /*Unique=*/true); + if (CanonType == + ClassTemplate->getCanonicalInjectedSpecializationType(Context) && (!Context.getLangOpts().CPlusPlus20 || !TemplateParams->hasAssociatedConstraints())) { // C++ [temp.class.spec]p9b3: @@ -8992,7 +9003,8 @@ DeclResult Sema::ActOnClassTemplateSpecialization( ClassTemplatePartialSpecializationDecl *Partial = ClassTemplatePartialSpecializationDecl::Create( Context, Kind, DC, KWLoc, TemplateNameLoc, TemplateParams, - ClassTemplate, CTAI.CanonicalConverted, CanonType, PrevPartial); + ClassTemplate, FunctionallyEquivalentConverted, + Context.getCanonicalType(CanonType), PrevPartial); Partial->setTemplateArgsAsWritten(TemplateArgs); SetNestedNameSpecifier(*this, Partial, SS); if (TemplateParameterLists.size() > 1 && SS.isSet()) { diff --git a/clang/test/CXX/drs/cwg20xx.cpp b/clang/test/CXX/drs/cwg20xx.cpp index 75b4094283db0..f669d460f4d09 100644 --- a/clang/test/CXX/drs/cwg20xx.cpp +++ b/clang/test/CXX/drs/cwg20xx.cpp @@ -64,6 +64,19 @@ namespace cwg2061 { // cwg2061: 2.7 #endif // C++11 } // namespace cwg2061 +namespace cwg2064 { // cwg2064: 23 +#if __cplusplus >= 201103L + template<typename T> struct X { + template<typename U> struct Y {}; + }; + template<typename T> void g() { + X<decltype(sizeof(T))>::Y<int> y; // ok + return X<decltype(sizeof(T))>::f(); + // expected-error@-1 {{no member named 'f' in 'cwg2064::X<unsigned long>'}} + } +#endif +} + namespace cwg2076 { // cwg2076: 13 #if __cplusplus >= 201103L namespace std_example { @@ -86,7 +99,7 @@ namespace cwg2076 { // cwg2076: 13 operator string_view() const; }; - void foo(const string &); // #cwg2076-foo + void foo(const string &); // #cwg2076-foo void bar(string_view); // #cwg2076-bar void func(const string &arg) { @@ -369,7 +382,7 @@ int f() return 0; } } // namespace GH42233 -} // namespace cwg2091 +} // namespace cwg2091 namespace cwg2094 { // cwg2094: 5 struct A { int n; }; diff --git a/clang/test/CXX/temp/temp.decls/temp.mem/p5.cpp b/clang/test/CXX/temp/temp.decls/temp.mem/p5.cpp index 65d8345ecc3aa..a0c9e931c3298 100644 --- a/clang/test/CXX/temp/temp.decls/temp.mem/p5.cpp +++ b/clang/test/CXX/temp/temp.decls/temp.mem/p5.cpp @@ -92,7 +92,7 @@ template X0::operator B<0>() const; // expected-error {{undefined function templ // index expression as non-canonical is extra bad. template X0::operator C<int[1]>() const; // expected-error {{undefined function template 'operator C<type-parameter-0-0[V]>'}} #if __cplusplus >= 201103L -template X0::operator D<int, 0>() const; // expected-error {{undefined function template 'operator D<decltype(value-parameter-0-0), value-parameter-0-0>'}} +template X0::operator D<int, 0>() const; // expected-error {{undefined function template 'operator D<int, value-parameter-0-0>'}} #endif void test_X0(X0 x0, const X0 &x0c) { diff --git a/clang/test/CodeGenCXX/GH190495.cpp b/clang/test/CodeGenCXX/GH190495.cpp new file mode 100644 index 0000000000000..87984f8c34ac1 --- /dev/null +++ b/clang/test/CodeGenCXX/GH190495.cpp @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 -std=c++26 -emit-llvm %s -o - -triple=x86_64-apple-darwin9 + +namespace std { +class partial_ordering {}; +class strong_ordering { +public: + operator partial_ordering(); +}; +using size_t = decltype(sizeof(int)); +template <size_t> struct __priority_tag {}; +namespace __partial_order { +struct __fn { + template <class _Tp, class _Up> + constexpr auto __go(_Tp __t, _Up __u, __priority_tag<2>) + -> decltype(partial_ordering(partial_order(__t, __u))) {} + template <class _Tp, class _Up> auto operator()(_Tp __t, _Up __u) { + __go(__t, __u, __priority_tag<2>()); + } +}; +} +auto partial_order = __partial_order::__fn{}; +namespace { +struct A {}; +strong_ordering partial_order(A, A) { + A a; + std::partial_order(a, a); +} +} +} diff --git a/clang/test/CodeGenCXX/mangle-subst.cpp b/clang/test/CodeGenCXX/mangle-subst.cpp index 524e0febe479a..fa59ca2dfb3d0 100644 --- a/clang/test/CodeGenCXX/mangle-subst.cpp +++ b/clang/test/CodeGenCXX/mangle-subst.cpp @@ -69,7 +69,7 @@ namespace NS { namespace NS { // CHECK: @_ZN2NS1fERNS_1CE - void f(C&) { } + void f(C&) { } } namespace Test1 { @@ -123,3 +123,14 @@ struct Inst : public A::Impl<A::Wrap> {}; void Test() { Inst a; } } + +namespace InstantiationDependentDecltype { + struct a { a(char); }; + struct b { a c(); }; + // FIXME: This mangling is incorrect; the second decltype type should be a + // substitution for the first. + // CHECK: @_ZN30InstantiationDependentDecltype1fINS_1bEEEvDTcvNS_1aEcldtcvT__E1cEEDTcvS2_cldtcvS3__E1cEES3_S3_S2_S2_ + // FIXME: @_ZN30InstantiationDependentDecltype1fINS_1bEEEvDTcvNS_1aEcldtcvT__E1cEES4_S3_S3_S2_S2_ + template<typename d> void f(decltype(a(d().c())), decltype(a(d().c())), d, d, a, a); + void g(a a, b b) { f(a, a, b, b, a, a); } +} diff --git a/clang/test/Sema/invalid-bitwidth-expr.mm b/clang/test/Sema/invalid-bitwidth-expr.mm index 9e577300eb1c8..25930e5d4ef7e 100644 --- a/clang/test/Sema/invalid-bitwidth-expr.mm +++ b/clang/test/Sema/invalid-bitwidth-expr.mm @@ -26,6 +26,7 @@ auto func() { auto func() { // error-bit should be propagated from TemplateArgument to NestNameSpecifier. class Base<decltype(Foo(T()))>::type C; // expected-error {{no matching function for call to 'Foo'}} + // expected-error@-1 {{no class named 'type' in 'Base<bool>'}} return C; } struct Z { diff --git a/clang/test/SemaCXX/decltype.cpp b/clang/test/SemaCXX/decltype.cpp index 45a4c4cf1ac86..004d48415b0e7 100644 --- a/clang/test/SemaCXX/decltype.cpp +++ b/clang/test/SemaCXX/decltype.cpp @@ -135,7 +135,7 @@ namespace GH97646 { template<bool B> void f() { decltype(B) x = false; - !x; + !x; // expected-warning {{expression result unused}} } } @@ -241,6 +241,15 @@ void test() { (void)C::XBitMask<0>; } } #endif +namespace value_dependent { + template<int V> void f() { + decltype(V) x = nullptr; + // expected-error@-1 {{cannot initialize a variable of type 'decltype(V)' (aka 'int') with an rvalue of type 'std::nullptr_t'}} + } + template<typename T> decltype(int(T())) g() {} + template<typename T> decltype(int(T(0))) g() {} +} // namespace value_dependent + template<typename> class conditional { }; diff --git a/clang/test/SemaCXX/source_location.cpp b/clang/test/SemaCXX/source_location.cpp index eaa6cb04c5d1c..13846553421ce 100644 --- a/clang/test/SemaCXX/source_location.cpp +++ b/clang/test/SemaCXX/source_location.cpp @@ -9,7 +9,9 @@ // RUN: %clang_cc1 -std=c++2b -fcxx-exceptions -DUSE_CONSTEVAL -DPAREN_INIT -fexceptions -fexperimental-new-constant-interpreter -DNEW_INTERP -verify %s // RUN: %clang_cc1 -std=c++1z -fcxx-exceptions -fms-extensions -DMS -fexceptions -fexperimental-new-constant-interpreter -DNEW_INTERP -fms-compatibility -verify %s // RUN: %clang_cc1 -std=c++2a -fcxx-exceptions -fms-extensions -DMS -DUSE_CONSTEVAL -fexceptions -fexperimental-new-constant-interpreter -DNEW_INTERP -verify -fms-compatibility %s +#ifndef MS // expected-no-diagnostics +#endif #define assert(...) ((__VA_ARGS__) ? ((void)0) : throw 42) #define CURRENT_FROM_MACRO() SL::current() @@ -1088,6 +1090,9 @@ namespace GH178324 { using e = int; }; void current(const char * = __builtin_FUNCSIG()); - template <class> void c() { decltype(a(current()))::e; } + template <class> void c() { + decltype(a(current()))::e; + // expected-warning@-1 {{declaration does not declare anything}} + } } // namespace GH178324 #endif diff --git a/clang/test/SemaCXX/typeof.cpp b/clang/test/SemaCXX/typeof.cpp index 421cfc59f5311..e6d3dbccf7321 100644 --- a/clang/test/SemaCXX/typeof.cpp +++ b/clang/test/SemaCXX/typeof.cpp @@ -8,6 +8,6 @@ namespace GH97646 { template<bool B> void f() { __typeof__(B) x = false; - !x; + !x; // expected-warning {{expression result unused}} } } diff --git a/clang/test/SemaTemplate/concepts-out-of-line-def.cpp b/clang/test/SemaTemplate/concepts-out-of-line-def.cpp index 9811b18f4301b..0939a2a7fbb5f 100644 --- a/clang/test/SemaTemplate/concepts-out-of-line-def.cpp +++ b/clang/test/SemaTemplate/concepts-out-of-line-def.cpp @@ -533,12 +533,12 @@ template <class T> void X<T>::foo() requires requires { requires is_not_same_v<T, int>; } {} // ok template <class T> -void X<T>::bar(decltype(requires { requires something_interesting<T>; })) {} -// expected-error@-1{{definition of 'bar' does not match any declaration}} -// expected-note@#defined-here{{defined here}} +void X<T>::bar(decltype(requires { requires something_interesting<T>; })) {} // #GH74314-bar-prev-def-here template <class T> void X<T>::bar(decltype(requires { requires is_not_same_v<T, int>; })) {} +// expected-error@-1 {{redefinition of 'bar'}} +// expected-note@#GH74314-bar-prev-def-here {{previous definition is here}} } // namespace GH74314 namespace GH56482 { diff --git a/clang/test/SemaTemplate/concepts.cpp b/clang/test/SemaTemplate/concepts.cpp index ac80d16b4ccf8..7a3bf419eab68 100644 --- a/clang/test/SemaTemplate/concepts.cpp +++ b/clang/test/SemaTemplate/concepts.cpp @@ -1824,6 +1824,16 @@ namespace GH176402 { recursiveLambda(recursiveLambda, 5); } } + +namespace GH61818 { + template <typename T> concept C = true; + template <typename T> struct A; + template <> struct A<bool> { using type = bool; }; + + template <typename T> + void f(A<decltype(C<T>)>::type); // OK, no 'typename' needed +} // namespace GH61818 + namespace GH191016 { template <typename T = int> struct S { diff --git a/clang/test/SemaTemplate/deduction-guide.cpp b/clang/test/SemaTemplate/deduction-guide.cpp index 9e5756ffec3fc..29693e1918263 100644 --- a/clang/test/SemaTemplate/deduction-guide.cpp +++ b/clang/test/SemaTemplate/deduction-guide.cpp @@ -854,17 +854,17 @@ CC c{}; // CHECK-LABEL: Dumping GH133132::<deduction guide for CC>: // CHECK-NEXT: FunctionTemplateDecl {{.+}} implicit <deduction guide for CC> -// CHECK-NEXT: |-NonTypeTemplateParmDecl {{.+}} 'int' depth 0 index 0 N -// CHECK-NEXT: | `-TemplateArgument {{.+}} expr '42' -// CHECK-NEXT: | `-IntegerLiteral {{.+}} 'int' 42 -// CHECK-NEXT: |-TemplateTypeParmDecl {{.+}} class depth 0 index 1 U -// CHECK-NEXT: | `-TemplateArgument type 'A<decltype(N)>' -// CHECK-NEXT: | `-TemplateSpecializationType {{.+}} 'A<decltype(N)>' dependent +// CHECK-NEXT: |-TemplateTypeParmDecl {{.+}} class depth 0 index 0 U +// CHECK-NEXT: | `-TemplateArgument type 'A<decltype(N)>':'GH133132::A<int>' +// CHECK-NEXT: | `-TemplateSpecializationType {{.+}} 'A<decltype(N)>' sugar instantiation_dependent // CHECK-NEXT: | |-name: 'A':'GH133132::A' qualified // CHECK-NEXT: | | `-ClassTemplateDecl {{.+}} A -// CHECK-NEXT: | `-TemplateArgument type 'decltype(N)' -// CHECK-NEXT: | `-DecltypeType {{.+}} 'decltype(N)' dependent -// CHECK-NEXT: | `-DeclRefExpr {{.+}} 'int' NonTypeTemplateParm {{.+}} 'N' 'int' +// CHECK-NEXT: | |-TemplateArgument type 'decltype(N)':'int' +// CHECK-NEXT: | | `-DecltypeType {{.+}} 'decltype(N)' sugar instantiation_dependent +// CHECK-NEXT: | | |-DeclRefExpr {{.+}} 'int' NonTypeTemplateParm {{.+}} 'N' 'int' +// CHECK-NEXT: | | `-BuiltinType {{.+}} 'int' +// CHECK-NEXT: | `-RecordType {{.+}} 'GH133132::A<int>' canonical +// CHECK-NEXT: | `-ClassTemplateSpecialization {{.+}} 'A' // CHECK-NEXT: |-TypeTraitExpr {{.+}} 'bool' __is_deducible // CHECK-NEXT: | |-DeducedTemplateSpecializationType {{.+}} 'GH133132::CC' dependent // CHECK-NEXT: | | `-name: 'GH133132::CC' @@ -872,14 +872,13 @@ CC c{}; // CHECK-NEXT: | `-TemplateSpecializationType {{.+}} 'GH133132::A<U>' dependent // CHECK-NEXT: | |-name: 'GH133132::A' // CHECK-NEXT: | | `-ClassTemplateDecl {{.+}} A -// CHECK-NEXT: | `-TemplateArgument type 'U':'type-parameter-0-1' +// CHECK-NEXT: | `-TemplateArgument type 'U':'type-parameter-0-0' // CHECK-NEXT: | `-SubstTemplateTypeParmType {{.+}} 'U' sugar dependent class depth 0 index 0 _Ty // CHECK-NEXT: | |-FunctionTemplate {{.+}} '<deduction guide for A>' -// CHECK-NEXT: | `-TemplateTypeParmType {{.+}} 'U' dependent depth 0 index 1 +// CHECK-NEXT: | `-TemplateTypeParmType {{.+}} 'U' dependent depth 0 index 0 // CHECK-NEXT: | `-TemplateTypeParm {{.+}} 'U' // CHECK-NEXT: |-CXXDeductionGuideDecl {{.+}} implicit <deduction guide for CC> 'auto () -> GH133132::A<U>' // CHECK-NEXT: `-CXXDeductionGuideDecl {{.+}} implicit used <deduction guide for CC> 'auto () -> GH133132::A<GH133132::A<int>>' implicit_instantiation -// CHECK-NEXT: |-TemplateArgument integral '42' // CHECK-NEXT: `-TemplateArgument type 'GH133132::A<int>' // CHECK-NEXT: `-RecordType {{.+}} 'GH133132::A<int>' // CHECK-NEXT: `-ClassTemplateSpecialization {{.+}} 'A' diff --git a/clang/test/SemaTemplate/dependent-expr.cpp b/clang/test/SemaTemplate/dependent-expr.cpp index ce210d9b74f6d..31ee43ab9501c 100644 --- a/clang/test/SemaTemplate/dependent-expr.cpp +++ b/clang/test/SemaTemplate/dependent-expr.cpp @@ -13,12 +13,12 @@ namespace PR6045 { static const unsigned int member = r; void f(); }; - + template<unsigned int r> const unsigned int A<r>::member; - + template<unsigned int r> - void A<r>::f() + void A<r>::f() { unsigned k; (void)(k % member); @@ -129,7 +129,7 @@ namespace PR45083 { template<typename> void f() { decltype(({})) x; // expected-error {{incomplete type}} } - template void f<int>(); // expected-note {{instantiation of}} + template void f<int>(); template<typename> auto g() { auto c = [](auto, int) -> decltype(({})) {}; diff --git a/clang/test/SemaTemplate/injected-class-name.cpp b/clang/test/SemaTemplate/injected-class-name.cpp index 93a7231b8c7b7..e1a0573393d11 100644 --- a/clang/test/SemaTemplate/injected-class-name.cpp +++ b/clang/test/SemaTemplate/injected-class-name.cpp @@ -69,3 +69,47 @@ namespace ConflictingRedecl { template<typename> struct Nested; // expected-error {{member 'Nested' has the same name as its class}} }; } + +namespace TwoLevels { + template <class> struct A { + template <bool> A f(); + }; + template <class T> template <bool> A<T> A<T>::f() {} +} // namespace TwoLevels + +namespace TwoLevelsAlias1 { + template <class> struct A; + template <class T> using alias = T; + template <class T> struct B { + using type = alias<T>; + template <class> A<type> f(); + }; + template <class T> template <class> + A<typename B<T>::type> B<T>::f() {} +} // namespace TwoLevelsAlias + +namespace TwoLevelsAlias2 { + template <class> struct A; + template <class T> using alias = typename A<T>::type; + template <class T> struct B { + template <class> typename A<T>::type f(); + }; + template <class T> template <class> alias<T> B<T>::f() {} +} // namespace TwoLevelsAlias2 + +namespace TwoLevelsAlias3 { + template <class T> using void_t = void; + template <class T> struct A { // expected-note {{defined here}} + template <int> void_t<typename T::type> f(); + }; + template <class T> template <int> void A<T>::f() {} // expected-error {{does not match}} +} // namspace TwoLevelsAlias3 + +namespace Unique { + template <class T> struct A { + template <class> A<T> f1(); + template <class> A<T> f2(); + }; + template <class T> template <class> A<T> A<T>::f1() {} + template <class T> template <class> A<T> A<T>::f2() {} +} // namespace Unique diff --git a/clang/test/SemaTemplate/instantiation-dependence.cpp b/clang/test/SemaTemplate/instantiation-dependence.cpp index 1a97dbf769ec6..df2acd4525e93 100644 --- a/clang/test/SemaTemplate/instantiation-dependence.cpp +++ b/clang/test/SemaTemplate/instantiation-dependence.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -std=c++23 -verify %s +// RUN: %clang_cc1 -std=c++26 -verify %s // Ensure we substitute into instantiation-dependent but non-dependent // constructs. The poster-child for this is... @@ -48,17 +48,13 @@ namespace PR46791 { // also PR45782 static constexpr int specialization = 0; }; - // FIXME: Per a strict interpretation of the C++ rules, the two void_t<...> - // types below are equivalent -- we only (effectively) do token-by-token - // comparison for *expressions* appearing within types. But all other - // implementations accept this, using rules that are unclear. template<typename T> - struct trait<T, void_t<typename T::value_type>> { // expected-note {{previous}} FIXME-note {{matches}} + struct trait<T, void_t<typename T::value_type>> { // expected-note {{matches}} static constexpr int specialization = 1; }; template<typename T> - struct trait<T, void_t<typename T::element_type>> { // expected-error {{redefinition}} FIXME-note {{matches}} + struct trait<T, void_t<typename T::element_type>> { // expected-note {{matches}} static constexpr int specialization = 2; }; @@ -68,11 +64,9 @@ namespace PR46791 { // also PR45782 struct D : B, C {}; static_assert(trait<A>::specialization == 0); - static_assert(trait<B>::specialization == 1); // FIXME expected-error {{failed}} \ - // expected-note {{evaluates to '0 == 1'}} - static_assert(trait<C>::specialization == 2); // FIXME expected-error {{failed}} \ - // expected-note {{evaluates to '0 == 2'}} - static_assert(trait<D>::specialization == 0); // FIXME-error {{ambiguous partial specialization}} + static_assert(trait<B>::specialization == 1); + static_assert(trait<C>::specialization == 2); + static_assert(trait<D>::specialization == 0); // expected-error {{ambiguous partial specialization}} } namespace TypeQualifier { @@ -103,3 +97,75 @@ namespace MemberOfInstantiationDependentBase { void q(C1<int> *c) { c->f(0); } void q(C2<int> *c) { c->f(0); } } + +namespace GH8740 { + struct A { typedef int T; }; + template<int> struct U { typedef int T; }; + template<typename> struct S { + A a; + int n = decltype(a)::T(); + int m = U<sizeof(a)>::T(); + }; + S<char> s; +} // namespace GH8740 + +namespace NonInstDependentArgs1 { + template<typename T, typename = void> struct X; + template<typename T> struct X<T, void_t<char>> {}; // expected-note {{previous}} + template<typename T> struct X<T, void_t<void>> {}; // expected-error {{redefinition}} +} // namespace NonInstDependentArgs1 + +namespace NonInstDependentArgs2 { + template<typename T, typename = void> struct X; + template<typename T> struct X<T, void_t<T, void>> {}; + template<typename T> struct X<T, void_t<T, char>> {}; +} // namespace NonInstDependentArgs2 + +namespace Level1 { + template<typename T, typename = void> struct X; + template<typename T> struct X<T, void_t<T>> {}; + template<typename T> struct X<T, void_t<T*>> {}; +} // namespace Level1 + +namespace Level2 { + template<typename T, typename = void> struct X; + template<typename T> struct X<T, void_t<void_t<T>>> {}; + template<typename T> struct X<T, void_t<void_t<T*>>> {}; +} // namespace Level2 + +namespace IndirectAlias1 { + template<class T> using alias = void_t<T>; + template<typename T, typename = void> struct X; + template<typename T> struct X<T, void_t<T>> {}; // expected-note {{previous}} + template<typename T> struct X<T, alias<T>> {}; // expected-error {{redefinition}} +} // namspace IndirectAlias1 + +namespace IndirectAlias2 { + template<class T, class> using alias1 = T; + template<class T, class U> using alias2 = alias1<T, U>; + template<typename T, typename = void> struct X; + template<typename T> struct X<T, T> {}; + template<typename T> struct X<T, alias1<T, T>> {}; // expected-note {{previous}} + template<typename T> struct X<T, alias2<T, T>> {}; // expected-error {{redefinition}} +} // namespaceIndirectAlias2 + +namespace PackIndexing { + // FIXME: This should not be a redefinition. + template<class ...Ts> using alias = Ts...[0]; + template<typename T, typename = void> struct X; + template<typename T> struct X<T, T> {}; // expected-note {{previous}} + template<typename T> struct X<T, alias<T, typename T::type>> {}; // expected-error {{redefinition}} +} // namespace PackIndexing + +namespace DeclType { + template<typename T, typename = void> struct X; + template<typename T> struct X<T, decltype(void())> {}; + template<typename T> struct X<T, decltype(void_t<typename T::type>())> {}; // FIXME-note {{previous}} + template<typename T> struct X<T, decltype(void_t<typename T::type>())> {}; // FIXME-error {{redefinition}} +} // namespace DeclType + +namespace UnaryTransformDecay { + template<class T, class U = void> struct X; + template<class T> struct X<T, __decay(int[T()])> {}; // FIXME-note {{previous}} + template<class T> struct X<T, __decay(int[T()])> {}; // FIXME-error {{redefinition}} +} // namespace UnaryTransformDecay diff --git a/clang/test/SemaTemplate/partial-spec-instantiate.cpp b/clang/test/SemaTemplate/partial-spec-instantiate.cpp index 44b58008a1d33..ab48050938991 100644 --- a/clang/test/SemaTemplate/partial-spec-instantiate.cpp +++ b/clang/test/SemaTemplate/partial-spec-instantiate.cpp @@ -165,3 +165,10 @@ namespace GH162855 { template struct C<D<int>>; } // namespace GH162855 #endif + +namespace DependentWithQualifier { + template <class> struct A; + template <class> struct B; + template <class T> struct A<const B<T> > {}; + template struct A<const B<int> >; +} // namespace DependentWithQualifier diff --git a/clang/test/SemaTemplate/temp_arg_template_p0522.cpp b/clang/test/SemaTemplate/temp_arg_template_p0522.cpp index bde811c3bf685..1d97deeb9214a 100644 --- a/clang/test/SemaTemplate/temp_arg_template_p0522.cpp +++ b/clang/test/SemaTemplate/temp_arg_template_p0522.cpp @@ -135,8 +135,17 @@ namespace Auto { int n; template<auto A, decltype(A) B = &n> struct SubstFailure; - TInt<SubstFailure> isf; // FIXME: this should be ill-formed + // expected-error@-1 {{value of type 'int *' is not implicitly convertible to 'decltype(value-parameter-0-0)' (aka 'int')}} + // expected-note@#TInt {{while checking a default template argument used here}} + TInt<SubstFailure> isf; + // expected-note@-1 {{template template argument has different template parameters than its corresponding template template parameter}} TIntPtr<SubstFailure> ipsf; + + template<template<auto A, auto B, decltype(A)> typename C> struct TAutoAutoFirst {}; + template<auto A, auto B, decltype(A)> struct AutoAutoFirst; + template<auto A, auto B, decltype(B)> struct AutoAutoSecond; + TAutoAutoFirst<AutoAutoFirst> aaf; + TAutoAutoFirst<AutoAutoSecond> aas; // FIXME: this should be rejected due to parameter mismatch } namespace GH62529 { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
