Author: Richard Smith Date: 2020-10-21T14:15:54-07:00 New Revision: caf30e7f03566f20c45e5d0895e71c4bff9056ef
URL: https://github.com/llvm/llvm-project/commit/caf30e7f03566f20c45e5d0895e71c4bff9056ef DIFF: https://github.com/llvm/llvm-project/commit/caf30e7f03566f20c45e5d0895e71c4bff9056ef.diff LOG: [c++20] For P0732R2: Give class NTTPs the proper type when examined with 'decltype'. This requires that we track enough information to determine the original type of the parameter in a substituted non-type template parameter, to distinguish the reference-to-class case from the class case. Added: clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.type.decltype/p1.cpp Modified: clang/include/clang/AST/ExprCXX.h clang/lib/AST/ASTImporter.cpp clang/lib/AST/ExprCXX.cpp clang/lib/Sema/SemaTemplateInstantiate.cpp clang/lib/Sema/SemaType.cpp clang/lib/Serialization/ASTReaderStmt.cpp clang/lib/Serialization/ASTWriterStmt.cpp Removed: ################################################################################ diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h index 926021e9c6ed..61a23ddaa368 100644 --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -4233,8 +4233,10 @@ class SubstNonTypeTemplateParmExpr : public Expr { friend class ASTReader; friend class ASTStmtReader; - /// The replaced parameter. - NonTypeTemplateParmDecl *Param; + /// The replaced parameter and a flag indicating if it was a reference + /// parameter. For class NTTPs, we can't determine that based on the value + /// category alone. + llvm::PointerIntPair<NonTypeTemplateParmDecl*, 1, bool> ParamAndRef; /// The replacement expression. Stmt *Replacement; @@ -4245,10 +4247,10 @@ class SubstNonTypeTemplateParmExpr : public Expr { public: SubstNonTypeTemplateParmExpr(QualType Ty, ExprValueKind ValueKind, SourceLocation Loc, - NonTypeTemplateParmDecl *Param, + NonTypeTemplateParmDecl *Param, bool RefParam, Expr *Replacement) : Expr(SubstNonTypeTemplateParmExprClass, Ty, ValueKind, OK_Ordinary), - Param(Param), Replacement(Replacement) { + ParamAndRef(Param, RefParam), Replacement(Replacement) { SubstNonTypeTemplateParmExprBits.NameLoc = Loc; setDependence(computeDependence(this)); } @@ -4261,7 +4263,14 @@ class SubstNonTypeTemplateParmExpr : public Expr { Expr *getReplacement() const { return cast<Expr>(Replacement); } - NonTypeTemplateParmDecl *getParameter() const { return Param; } + NonTypeTemplateParmDecl *getParameter() const { + return ParamAndRef.getPointer(); + } + + bool isReferenceParameter() const { return ParamAndRef.getInt(); } + + /// Determine the substituted type of the template parameter. + QualType getParameterType(const ASTContext &Ctx) const; static bool classof(const Stmt *s) { return s->getStmtClass() == SubstNonTypeTemplateParmExprClass; diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index 23720bf75a65..ce79b99d7043 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -7859,7 +7859,8 @@ ExpectedStmt ASTNodeImporter::VisitSubstNonTypeTemplateParmExpr( return std::move(Err); return new (Importer.getToContext()) SubstNonTypeTemplateParmExpr( - ToType, E->getValueKind(), ToExprLoc, ToParameter, ToReplacement); + ToType, E->getValueKind(), ToExprLoc, ToParameter, + E->isReferenceParameter(), ToReplacement); } ExpectedStmt ASTNodeImporter::VisitTypeTraitExpr(TypeTraitExpr *E) { diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp index 4a421f03e589..c1ec86075772 100644 --- a/clang/lib/AST/ExprCXX.cpp +++ b/clang/lib/AST/ExprCXX.cpp @@ -1565,6 +1565,15 @@ SizeOfPackExpr *SizeOfPackExpr::CreateDeserialized(ASTContext &Context, return new (Storage) SizeOfPackExpr(EmptyShell(), NumPartialArgs); } +QualType SubstNonTypeTemplateParmExpr::getParameterType( + const ASTContext &Context) const { + // Note that, for a class type NTTP, we will have an lvalue of type 'const + // T', so we can't just compute this from the type and value category. + if (isReferenceParameter()) + return Context.getLValueReferenceType(getType()); + return getType().getUnqualifiedType(); +} + SubstNonTypeTemplateParmPackExpr::SubstNonTypeTemplateParmPackExpr( QualType T, ExprValueKind ValueKind, NonTypeTemplateParmDecl *Param, SourceLocation NameLoc, const TemplateArgument &ArgPack) diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index a13118839036..d01189b42ed6 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -1464,9 +1464,12 @@ TemplateInstantiator::TransformTemplateParmRefExpr(DeclRefExpr *E, if (TargetType.isNull()) return ExprError(); + QualType ExprType = TargetType.getNonLValueExprType(SemaRef.Context); + if (TargetType->isRecordType()) + ExprType.addConst(); + return new (SemaRef.Context) SubstNonTypeTemplateParmPackExpr( - TargetType.getNonLValueExprType(SemaRef.Context), - TargetType->isReferenceType() ? VK_LValue : VK_RValue, NTTP, + ExprType, TargetType->isReferenceType() ? VK_LValue : VK_RValue, NTTP, E->getLocation(), Arg); } @@ -1498,15 +1501,39 @@ ExprResult TemplateInstantiator::transformNonTypeTemplateParmRef( SourceLocation loc, TemplateArgument arg) { ExprResult result; - QualType type; - // The template argument itself might be an expression, in which - // case we just return that expression. + // Determine the substituted parameter type. We can usually infer this from + // the template argument, but not always. + auto SubstParamType = [&] { + QualType T; + if (parm->isExpandedParameterPack()) + T = parm->getExpansionType(SemaRef.ArgumentPackSubstitutionIndex); + else + T = parm->getType(); + if (parm->isParameterPack() && isa<PackExpansionType>(T)) + T = cast<PackExpansionType>(T)->getPattern(); + return SemaRef.SubstType(T, TemplateArgs, loc, parm->getDeclName()); + }; + + bool refParam = false; + + // The template argument itself might be an expression, in which case we just + // return that expression. This happens when substituting into an alias + // template. if (arg.getKind() == TemplateArgument::Expression) { Expr *argExpr = arg.getAsExpr(); result = argExpr; - type = argExpr->getType(); - + if (argExpr->isLValue()) { + if (argExpr->getType()->isRecordType()) { + // Check whether the parameter was actually a reference. + QualType paramType = SubstParamType(); + if (paramType.isNull()) + return ExprError(); + refParam = paramType->isReferenceType(); + } else { + refParam = true; + } + } } else if (arg.getKind() == TemplateArgument::Declaration || arg.getKind() == TemplateArgument::NullPtr) { ValueDecl *VD; @@ -1524,36 +1551,25 @@ ExprResult TemplateInstantiator::transformNonTypeTemplateParmRef( VD = nullptr; } - // Derive the type we want the substituted decl to have. This had - // better be non-dependent, or these checks will have serious problems. - if (parm->isExpandedParameterPack()) { - type = parm->getExpansionType(SemaRef.ArgumentPackSubstitutionIndex); - } else if (parm->isParameterPack() && - isa<PackExpansionType>(parm->getType())) { - type = SemaRef.SubstType( - cast<PackExpansionType>(parm->getType())->getPattern(), - TemplateArgs, loc, parm->getDeclName()); - } else { - type = SemaRef.SubstType(VD ? arg.getParamTypeForDecl() : arg.getNullPtrType(), - TemplateArgs, loc, parm->getDeclName()); - } - assert(!type.isNull() && "type substitution failed for param type"); - assert(!type->isDependentType() && "param type still dependent"); - result = SemaRef.BuildExpressionFromDeclTemplateArgument(arg, type, loc); - - if (!result.isInvalid()) type = result.get()->getType(); + QualType paramType = VD ? arg.getParamTypeForDecl() : arg.getNullPtrType(); + assert(!paramType.isNull() && "type substitution failed for param type"); + assert(!paramType->isDependentType() && "param type still dependent"); + result = SemaRef.BuildExpressionFromDeclTemplateArgument(arg, paramType, loc); + refParam = paramType->isReferenceType(); } else { result = SemaRef.BuildExpressionFromIntegralTemplateArgument(arg, loc); - - // Note that this type can be diff erent from the type of 'result', - // e.g. if it's an enum type. - type = arg.getIntegralType(); + assert(result.isInvalid() || + SemaRef.Context.hasSameType(result.get()->getType(), + arg.getIntegralType())); } - if (result.isInvalid()) return ExprError(); + + if (result.isInvalid()) + return ExprError(); Expr *resultExpr = result.get(); return new (SemaRef.Context) SubstNonTypeTemplateParmExpr( - type, resultExpr->getValueKind(), loc, parm, resultExpr); + resultExpr->getType(), resultExpr->getValueKind(), loc, parm, refParam, + resultExpr); } ExprResult diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index 136f03a69b25..4d156634bf48 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -8890,7 +8890,17 @@ static QualType getDecltypeForExpr(Sema &S, Expr *E) { // C++11 [dcl.type.simple]p4: // The type denoted by decltype(e) is defined as follows: - // + + // C++20: + // - if E is an unparenthesized id-expression naming a non-type + // template-parameter (13.2), decltype(E) is the type of the + // template-parameter after performing any necessary type deduction + // Note that this does not pick up the implicit 'const' for a template + // parameter object. This rule makes no diff erence before C++20 so we apply + // it unconditionally. + if (const auto *SNTTPE = dyn_cast<SubstNonTypeTemplateParmExpr>(E)) + return SNTTPE->getParameterType(S.Context); + // - if e is an unparenthesized id-expression or an unparenthesized class // member access (5.2.5), decltype(e) is the type of the entity named // by e. If there is no such entity, or if e names a set of overloaded @@ -8899,6 +8909,8 @@ static QualType getDecltypeForExpr(Sema &S, Expr *E) { // We apply the same rules for Objective-C ivar and property references. if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E)) { const ValueDecl *VD = DRE->getDecl(); + if (auto *TPO = dyn_cast<TemplateParamObjectDecl>(VD)) + return TPO->getType().getUnqualifiedType(); return VD->getType(); } else if (const MemberExpr *ME = dyn_cast<MemberExpr>(E)) { if (const ValueDecl *VD = ME->getMemberDecl()) diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index 363527f884b3..cf335302f7f0 100644 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -2121,7 +2121,8 @@ void ASTStmtReader::VisitSizeOfPackExpr(SizeOfPackExpr *E) { void ASTStmtReader::VisitSubstNonTypeTemplateParmExpr( SubstNonTypeTemplateParmExpr *E) { VisitExpr(E); - E->Param = readDeclAs<NonTypeTemplateParmDecl>(); + E->ParamAndRef.setPointer(readDeclAs<NonTypeTemplateParmDecl>()); + E->ParamAndRef.setInt(Record.readInt()); E->SubstNonTypeTemplateParmExprBits.NameLoc = readSourceLocation(); E->Replacement = Record.readSubExpr(); } diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index 0121f2583207..522cd3247f34 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -2018,6 +2018,7 @@ void ASTStmtWriter::VisitSubstNonTypeTemplateParmExpr( SubstNonTypeTemplateParmExpr *E) { VisitExpr(E); Record.AddDeclRef(E->getParameter()); + Record.push_back(E->isReferenceParameter()); Record.AddSourceLocation(E->getNameLoc()); Record.AddStmt(E->getReplacement()); Code = serialization::EXPR_SUBST_NON_TYPE_TEMPLATE_PARM; diff --git a/clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.type.decltype/p1.cpp b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.type.decltype/p1.cpp new file mode 100644 index 000000000000..a62b73d9915e --- /dev/null +++ b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.type.decltype/p1.cpp @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 -std=c++20 -verify %s +// expected-no-diagnostics + +// bullet 2: decltype(x) where x is a non-type template parameter gives the +// type of X, after deduction, if any. +namespace ClassNTTP { + template<decltype(auto) v, typename ParamT, typename ExprT> void f() { + using U = decltype(v); + using U = ParamT; + + using V = decltype((v)); + using V = ExprT; + } + + // The names of most non-reference NTTPs are prvalues. + template void f<0, int, int>(); + + // The name of a class NTTP of type T is an lvalue of type 'const T'. + struct X {}; + template void f<X{}, X, const X&>(); + + // Ensure we get this right for references to classes too. + template<auto x> auto &TempParamObject = x; + template void f<TempParamObject<X{}>, const X&, const X&>(); + + struct Y {} y; + template void f<(y), Y&, Y&>(); + template void f<y, Y, const Y&>(); +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits