diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h index 926021e9c6ed6..61a23ddaa368b 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 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(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 23720bf75a656..ce79b99d70435 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 4a421f03e589e..c1ec86075772f 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 a131188390368..d01189b42ed60 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(T)) + T = cast(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(parm->getType())) { - type = SemaRef.SubstType( - cast(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 different 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 136f03a69b255..4d156634bf488 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 difference before C++20 so we apply + // it unconditionally. + if (const auto *SNTTPE = dyn_cast(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(E)) { const ValueDecl *VD = DRE->getDecl(); + if (auto *TPO = dyn_cast(VD)) + return TPO->getType().getUnqualifiedType(); return VD->getType(); } else if (const MemberExpr *ME = dyn_cast(E)) { if (const ValueDecl *VD = ME->getMemberDecl()) diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index 363527f884b33..cf335302f7f08 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(); + E->ParamAndRef.setPointer(readDeclAs()); + 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 0121f25832073..522cd3247f342 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 0000000000000..a62b73d9915ea --- /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 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(); + + // Ensure we get this right for references to classes too. + template auto &TempParamObject = x; + template void f, const X&, const X&>(); + + struct Y {} y; + template void f<(y), Y&, Y&>(); + template void f(); +}