[clang] [Sema] Improve diagnostic for 'auto' in local-class member params (PR #167201)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Nov 8 21:55:37 PST 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Sai Deepak Sana (saideepaksana)
<details>
<summary>Changes</summary>
Fixes #<!-- -->147324
**Summary:**
Fixes missing source location (filename, line, column) in template diagnostics when templates or abbreviated function templates (auto parameters) are declared inside local classes.
**Changes:**
- Updated `CheckTemplateDeclScope` in `SemaTemplate.cpp` to correctly detect abbreviated function templates and compute accurate source locations.
- Improved diagnostic messages for templates inside local classes, `extern "C"`, and invalid scopes.
- Added new test cases for `C++17`, `C++20`, and `C++23`.
**Tests Added:**
- `template-local-class.cpp` – templates and `auto` members inside local class
- `template-local-class-fn.cpp` -
- `template-abbrev-local-class.cpp` – abbreviated function templates across standards
- `template-nested-local-classes.cpp` – nested local class templates
- `abbrev-top-level-fn.cpp` – top-level functions with `auto` parameters
**Result:**
Diagnostics now show correct file, line, and column numbers, and handle auto parameter templates accurately.
---
Full diff: https://github.com/llvm/llvm-project/pull/167201.diff
6 Files Affected:
- (modified) clang/lib/Sema/SemaTemplate.cpp (+77-57)
- (added) clang/test/SemaCXX/abbrev-top-level-fn.cpp (+5)
- (added) clang/test/SemaCXX/template-abbrev-local-class.cpp (+12)
- (added) clang/test/SemaCXX/template-local-class-fn.cpp (+12)
- (added) clang/test/SemaCXX/template-local-class.cpp (+11)
- (added) clang/test/SemaCXX/template-nested-local-classes.cpp (+17)
``````````diff
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 4a9e1bc93b918..e601b7cb11007 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -318,7 +318,7 @@ TemplateNameKind Sema::isTemplateName(Scope *S,
}
}
- if (isPackProducingBuiltinTemplateName(Template) && S &&
+ if (isPackProducingBuiltinTemplateName(Template) &&
S->getTemplateParamParent() == nullptr)
Diag(Name.getBeginLoc(), diag::err_builtin_pack_outside_template) << TName;
// Recover by returning the template, even though we would never be able to
@@ -408,7 +408,9 @@ bool Sema::LookupTemplateName(LookupResult &Found, Scope *S, CXXScopeSpec &SS,
IsDependent = !LookupCtx && ObjectType->isDependentType();
assert((IsDependent || !ObjectType->isIncompleteType() ||
!ObjectType->getAs<TagType>() ||
- ObjectType->castAs<TagType>()->getDecl()->isEntityBeingDefined()) &&
+ ObjectType->castAs<TagType>()
+ ->getOriginalDecl()
+ ->isEntityBeingDefined()) &&
"Caller should have completed object type");
// Template names cannot appear inside an Objective-C class or object type
@@ -949,11 +951,11 @@ static TemplateArgumentLoc translateTemplateArgument(Sema &SemaRef,
switch (Arg.getKind()) {
case ParsedTemplateArgument::Type: {
- TypeSourceInfo *TSI;
- QualType T = SemaRef.GetTypeFromParser(Arg.getAsType(), &TSI);
- if (!TSI)
- TSI = SemaRef.Context.getTrivialTypeSourceInfo(T, Arg.getNameLoc());
- return TemplateArgumentLoc(TemplateArgument(T), TSI);
+ TypeSourceInfo *DI;
+ QualType T = SemaRef.GetTypeFromParser(Arg.getAsType(), &DI);
+ if (!DI)
+ DI = SemaRef.Context.getTrivialTypeSourceInfo(T, Arg.getNameLoc());
+ return TemplateArgumentLoc(TemplateArgument(T), DI);
}
case ParsedTemplateArgument::NonType: {
@@ -1817,7 +1819,7 @@ class ConstraintRefersToContainingTemplateChecker
}
bool VisitTagType(const TagType *T) override {
- return TraverseDecl(T->getDecl());
+ return TraverseDecl(T->getOriginalDecl());
}
bool TraverseDecl(const Decl *D) override {
@@ -2788,7 +2790,7 @@ struct DependencyChecker : DynamicRecursiveASTVisitor {
// An InjectedClassNameType will never have a dependent template name,
// so no need to traverse it.
return TraverseTemplateArguments(
- T->getTemplateArgs(T->getDecl()->getASTContext()));
+ T->getTemplateArgs(T->getOriginalDecl()->getASTContext()));
}
};
} // end anonymous namespace
@@ -2912,7 +2914,7 @@ TemplateParameterList *Sema::MatchTemplateParametersToScopeSpecifier(
if (const EnumType *EnumT = T->getAsCanonical<EnumType>()) {
// FIXME: Forward-declared enums require a TSK_ExplicitSpecialization
// check here.
- EnumDecl *Enum = EnumT->getDecl();
+ EnumDecl *Enum = EnumT->getOriginalDecl();
// Get to the parent type.
if (TypeDecl *Parent = dyn_cast<TypeDecl>(Enum->getParent()))
@@ -3350,7 +3352,7 @@ static QualType builtinCommonTypeImpl(Sema &S, ElaboratedTypeKeyword Keyword,
}
static bool isInVkNamespace(const RecordType *RT) {
- DeclContext *DC = RT->getDecl()->getDeclContext();
+ DeclContext *DC = RT->getOriginalDecl()->getDeclContext();
if (!DC)
return false;
@@ -3367,8 +3369,9 @@ static SpirvOperand checkHLSLSpirvTypeOperand(Sema &SemaRef,
if (auto *RT = OperandArg->getAsCanonical<RecordType>()) {
bool Literal = false;
SourceLocation LiteralLoc;
- if (isInVkNamespace(RT) && RT->getDecl()->getName() == "Literal") {
- auto SpecDecl = dyn_cast<ClassTemplateSpecializationDecl>(RT->getDecl());
+ if (isInVkNamespace(RT) && RT->getOriginalDecl()->getName() == "Literal") {
+ auto SpecDecl =
+ dyn_cast<ClassTemplateSpecializationDecl>(RT->getOriginalDecl());
assert(SpecDecl);
const TemplateArgumentList &LiteralArgs = SpecDecl->getTemplateArgs();
@@ -3379,8 +3382,9 @@ static SpirvOperand checkHLSLSpirvTypeOperand(Sema &SemaRef,
}
if (RT && isInVkNamespace(RT) &&
- RT->getDecl()->getName() == "integral_constant") {
- auto SpecDecl = dyn_cast<ClassTemplateSpecializationDecl>(RT->getDecl());
+ RT->getOriginalDecl()->getName() == "integral_constant") {
+ auto SpecDecl =
+ dyn_cast<ClassTemplateSpecializationDecl>(RT->getOriginalDecl());
assert(SpecDecl);
const TemplateArgumentList &ConstantArgs = SpecDecl->getTemplateArgs();
@@ -3846,14 +3850,13 @@ QualType Sema::CheckTemplateIdType(ElaboratedTypeKeyword Keyword,
// within enable_if in a SFINAE context, dig out the specific
// enable_if condition that failed and present that instead.
if (isEnableIfAliasTemplate(AliasTemplate)) {
- if (SFINAETrap *Trap = getSFINAEContext();
- TemplateDeductionInfo *DeductionInfo =
- Trap ? Trap->getDeductionInfo() : nullptr) {
- if (DeductionInfo->hasSFINAEDiagnostic() &&
- DeductionInfo->peekSFINAEDiagnostic().second.getDiagID() ==
- diag::err_typename_nested_not_found_enable_if &&
- TemplateArgs[0].getArgument().getKind() ==
- TemplateArgument::Expression) {
+ if (auto DeductionInfo = isSFINAEContext()) {
+ if (*DeductionInfo &&
+ (*DeductionInfo)->hasSFINAEDiagnostic() &&
+ (*DeductionInfo)->peekSFINAEDiagnostic().second.getDiagID() ==
+ diag::err_typename_nested_not_found_enable_if &&
+ TemplateArgs[0].getArgument().getKind()
+ == TemplateArgument::Expression) {
Expr *FailedCond;
std::string FailedDescription;
std::tie(FailedCond, FailedDescription) =
@@ -3862,14 +3865,15 @@ QualType Sema::CheckTemplateIdType(ElaboratedTypeKeyword Keyword,
// Remove the old SFINAE diagnostic.
PartialDiagnosticAt OldDiag =
{SourceLocation(), PartialDiagnostic::NullDiagnostic()};
- DeductionInfo->takeSFINAEDiagnostic(OldDiag);
+ (*DeductionInfo)->takeSFINAEDiagnostic(OldDiag);
// Add a new SFINAE diagnostic specifying which condition
// failed.
- DeductionInfo->addSFINAEDiagnostic(
- OldDiag.first,
- PDiag(diag::err_typename_nested_not_found_requirement)
- << FailedDescription << FailedCond->getSourceRange());
+ (*DeductionInfo)->addSFINAEDiagnostic(
+ OldDiag.first,
+ PDiag(diag::err_typename_nested_not_found_requirement)
+ << FailedDescription
+ << FailedCond->getSourceRange());
}
}
}
@@ -3955,7 +3959,6 @@ QualType Sema::CheckTemplateIdType(ElaboratedTypeKeyword Keyword,
if (Decl->getSpecializationKind() == TSK_Undeclared &&
ClassTemplate->getTemplatedDecl()->hasAttrs()) {
- NonSFINAEContext _(*this);
InstantiatingTemplate Inst(*this, TemplateLoc, Decl);
if (!Inst.isInvalid()) {
MultiLevelTemplateArgumentList TemplateArgLists(Template,
@@ -4107,7 +4110,7 @@ TypeResult Sema::ActOnTagTemplateIdType(TagUseKind TUK,
// Check the tag kind
if (const RecordType *RT = Result->getAs<RecordType>()) {
- RecordDecl *D = RT->getDecl();
+ RecordDecl *D = RT->getOriginalDecl();
IdentifierInfo *Id = D->getIdentifier();
assert(Id && "templated class must have an identifier");
@@ -4330,7 +4333,7 @@ void Sema::CheckDeductionGuideTemplate(FunctionTemplateDecl *TD) {
}
DeclResult Sema::ActOnVarTemplateSpecialization(
- Scope *S, Declarator &D, TypeSourceInfo *TSI, LookupResult &Previous,
+ Scope *S, Declarator &D, TypeSourceInfo *DI, LookupResult &Previous,
SourceLocation TemplateKWLoc, TemplateParameterList *TemplateParams,
StorageClass SC, bool IsPartialSpecialization) {
// D must be variable template id.
@@ -4456,8 +4459,8 @@ DeclResult Sema::ActOnVarTemplateSpecialization(
VarTemplatePartialSpecializationDecl *Partial =
VarTemplatePartialSpecializationDecl::Create(
Context, VarTemplate->getDeclContext(), TemplateKWLoc,
- TemplateNameLoc, TemplateParams, VarTemplate, TSI->getType(), TSI,
- SC, CTAI.CanonicalConverted);
+ TemplateNameLoc, TemplateParams, VarTemplate, DI->getType(), DI, SC,
+ CTAI.CanonicalConverted);
Partial->setTemplateArgsAsWritten(TemplateArgs);
if (!PrevPartial)
@@ -4475,7 +4478,7 @@ DeclResult Sema::ActOnVarTemplateSpecialization(
// this explicit specialization or friend declaration.
Specialization = VarTemplateSpecializationDecl::Create(
Context, VarTemplate->getDeclContext(), TemplateKWLoc, TemplateNameLoc,
- VarTemplate, TSI->getType(), TSI, SC, CTAI.CanonicalConverted);
+ VarTemplate, DI->getType(), DI, SC, CTAI.CanonicalConverted);
Specialization->setTemplateArgsAsWritten(TemplateArgs);
if (!PrevDecl)
@@ -5566,11 +5569,12 @@ 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,
/*StrictCheck=*/CTAI.MatchingTTP || CTAI.PartialOrdering, CTAK);
// If the current template argument causes an error, give up now.
- if (Res.isInvalid())
+ if (Res.isInvalid() || CurSFINAEErrors < NumSFINAEErrors)
return nullptr;
CTAI.SugaredConverted.push_back(SugaredResult);
CTAI.CanonicalConverted.push_back(CanonicalResult);
@@ -6379,11 +6383,11 @@ bool UnnamedLocalNoLinkageFinder::VisitDeducedTemplateSpecializationType(
}
bool UnnamedLocalNoLinkageFinder::VisitRecordType(const RecordType* T) {
- return VisitTagDecl(T->getDecl()->getDefinitionOrSelf());
+ return VisitTagDecl(T->getOriginalDecl()->getDefinitionOrSelf());
}
bool UnnamedLocalNoLinkageFinder::VisitEnumType(const EnumType* T) {
- return VisitTagDecl(T->getDecl()->getDefinitionOrSelf());
+ return VisitTagDecl(T->getOriginalDecl()->getDefinitionOrSelf());
}
bool UnnamedLocalNoLinkageFinder::VisitTemplateTypeParmType(
@@ -6408,7 +6412,7 @@ bool UnnamedLocalNoLinkageFinder::VisitTemplateSpecializationType(
bool UnnamedLocalNoLinkageFinder::VisitInjectedClassNameType(
const InjectedClassNameType* T) {
- return VisitTagDecl(T->getDecl()->getDefinitionOrSelf());
+ return VisitTagDecl(T->getOriginalDecl()->getDefinitionOrSelf());
}
bool UnnamedLocalNoLinkageFinder::VisitDependentNameType(
@@ -8421,7 +8425,7 @@ bool Sema::TemplateParameterListsAreEqual(
return true;
}
-bool
+bool
Sema::CheckTemplateDeclScope(Scope *S, TemplateParameterList *TemplateParams) {
if (!S)
return false;
@@ -8445,33 +8449,49 @@ Sema::CheckTemplateDeclScope(Scope *S, TemplateParameterList *TemplateParams) {
}
Ctx = Ctx ? Ctx->getRedeclContext() : nullptr;
- // C++ [temp]p2:
- // A template-declaration can appear only as a namespace scope or
- // class scope declaration.
- // C++ [temp.expl.spec]p3:
- // An explicit specialization may be declared in any scope in which the
- // corresponding primary template may be defined.
- // C++ [temp.class.spec]p6: [P2096]
- // A partial specialization may be declared in any scope in which the
- // corresponding primary template may be defined.
+ // Compute a SourceLocation to use for diagnostics. Prefer the explicit
+ // template location, but fall back to nearby Decl locations when needed.
+ SourceLocation Loc = TemplateParams->getTemplateLoc();
+ if (Loc.isInvalid())
+ Loc = TemplateParams->getSourceRange().getBegin();
+
+ if (Loc.isInvalid() && Ctx) {
+ if (const Decl *D = dyn_cast<Decl>(Ctx))
+ Loc = D->getBeginLoc();
+ }
+
+ // Try to extract class context if present.
+ CXXRecordDecl *RD = Ctx ? dyn_cast<CXXRecordDecl>(Ctx) : nullptr;
+ if (Loc.isInvalid() && RD)
+ Loc = RD->getLocation();
+
if (Ctx) {
if (Ctx->isFileContext())
return false;
- if (CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(Ctx)) {
+
+ if (RD) {
// C++ [temp.mem]p2:
// A local class shall not have member templates.
- if (RD->isLocalClass())
- return Diag(TemplateParams->getTemplateLoc(),
- diag::err_template_inside_local_class)
- << TemplateParams->getSourceRange();
- else
+ if (RD->isLocalClass()) {
+ // when the template location is not valid we are trying to use fallback SourceLocation such that diagnostic prints a usable file:line:col location
+ if (Loc.isInvalid())
+ Loc = TemplateParams->getSourceRange().getBegin();
+
+ return Diag(Loc, diag::err_template_inside_local_class)
+ << TemplateParams->getSourceRange();
+ }
+ else {
return false;
+ }
}
}
- return Diag(TemplateParams->getTemplateLoc(),
- diag::err_template_outside_namespace_or_class_scope)
- << TemplateParams->getSourceRange();
+ // when teplate declared outside the namspace or class scope it Fallbacks and it give valid SourceLocation with file:line info.
+ if (Loc.isInvalid())
+ Loc = TemplateParams->getSourceRange().getBegin();
+
+ return Diag(Loc, diag::err_template_outside_namespace_or_class_scope)
+ << TemplateParams->getSourceRange();
}
/// Determine what kind of template specialization the given declaration
diff --git a/clang/test/SemaCXX/abbrev-top-level-fn.cpp b/clang/test/SemaCXX/abbrev-top-level-fn.cpp
new file mode 100644
index 0000000000000..3f8bbb51e47f9
--- /dev/null
+++ b/clang/test/SemaCXX/abbrev-top-level-fn.cpp
@@ -0,0 +1,5 @@
+// RUN: not %clang_cc1 -std=c++17 -fsyntax-only %s 2>&1 | FileCheck %s --check-prefix=CHECK17
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only %s
+
+void top_fun(auto x) { }
+// CHECK17: {{.+}}:[[@LINE-1]]:14: error: 'auto' not allowed in function prototype
\ No newline at end of file
diff --git a/clang/test/SemaCXX/template-abbrev-local-class.cpp b/clang/test/SemaCXX/template-abbrev-local-class.cpp
new file mode 100644
index 0000000000000..b8d3ac16a59d1
--- /dev/null
+++ b/clang/test/SemaCXX/template-abbrev-local-class.cpp
@@ -0,0 +1,12 @@
+// RUN: not %clang_cc1 -std=c++20 -fsyntax-only %s 2>&1 | FileCheck %s --check-prefix=CHECK20
+// RUN: not %clang_cc1 -std=c++23 -fsyntax-only %s 2>&1 | FileCheck %s --check-prefix=CHECK23
+// RUN: not %clang_cc1 -std=c++17 -fsyntax-only %s 2>&1 | FileCheck %s --check-prefix=CHECK17
+
+int main() {
+ struct A {
+ void foo(auto x) {}
+// CHECK20: {{.+}}:[[@LINE-2]]:3: error: templates cannot be declared inside of a local class
+// CHECK23: {{.+}}:[[@LINE-3]]:3: error: templates cannot be declared inside of a local class
+// CHECK17: {{.+}}:[[@LINE-3]]:14: error: 'auto' not allowed in function prototype
+ };
+}
\ No newline at end of file
diff --git a/clang/test/SemaCXX/template-local-class-fn.cpp b/clang/test/SemaCXX/template-local-class-fn.cpp
new file mode 100644
index 0000000000000..de565ef12e4fc
--- /dev/null
+++ b/clang/test/SemaCXX/template-local-class-fn.cpp
@@ -0,0 +1,12 @@
+// RUN: not %clang_cc1 -std=c++17 -fsyntax-only %s 2>&1 | FileCheck %s --check-prefix=CHECK17
+// RUN: not %clang_cc1 -std=c++20 -fsyntax-only %s 2>&1 | FileCheck %s --check-prefix=CHECK20
+// RUN: not %clang_cc1 -std=c++23 -fsyntax-only %s 2>&1 | FileCheck %s --check-prefix=CHECK23
+
+int main() {
+ struct Local {
+// CHECK20: {{.+}}:[[@LINE-1]]:3: error: templates cannot be declared inside of a local class
+// CHECK23: {{.+}}:[[@LINE-2]]:3: error: templates cannot be declared inside of a local class
+ void mem(auto x) {}
+// CHECK17: {{.+}}:[[@LINE-1]]:14: error: 'auto' not allowed in function prototype
+ };
+}
\ No newline at end of file
diff --git a/clang/test/SemaCXX/template-local-class.cpp b/clang/test/SemaCXX/template-local-class.cpp
new file mode 100644
index 0000000000000..65e3207af4f42
--- /dev/null
+++ b/clang/test/SemaCXX/template-local-class.cpp
@@ -0,0 +1,11 @@
+// RUN: not %clang_cc1 -std=c++20 -fsyntax-only %s 2>&1 | FileCheck %s
+
+int main() {
+ struct S {
+ auto L = [](auto x) { return x; };
+// CHECK: {{.+}}:[[@LINE-1]]:5: error: 'auto' not allowed in non-static struct member
+ template<typename T> void memb(T);
+// CHECK: {{.+}}:[[@LINE-1]]:5: error: templates cannot be declared inside of a local class
+ };
+ return 0;
+}
\ No newline at end of file
diff --git a/clang/test/SemaCXX/template-nested-local-classes.cpp b/clang/test/SemaCXX/template-nested-local-classes.cpp
new file mode 100644
index 0000000000000..94b895cfcfa9e
--- /dev/null
+++ b/clang/test/SemaCXX/template-nested-local-classes.cpp
@@ -0,0 +1,17 @@
+// RUN: not %clang_cc1 -std=c++20 -fsyntax-only %s 2>&1 | FileCheck %s --check-prefix=CHECK20
+// RUN: not %clang_cc1 -std=c++17 -fsyntax-only %s 2>&1 | FileCheck %s --check-prefix=CHECK17
+
+int outer() {
+ struct Outer {
+ void f() {
+ struct Inner {
+ template<typename T> void m(T);
+// CHECK20: {{.+}}:[[@LINE-1]]:9: error: templates cannot be declared inside of a local class
+// CHECK20: {{.+}}:[[@LINE-3]]:7: error: templates cannot be declared inside of a local class
+ void bad(auto x) {}
+// CHECK17: {{.+}}:[[@LINE-1]]:18: error: 'auto' not allowed in function prototype
+ };
+ }
+ };
+ return 0;
+}
\ No newline at end of file
``````````
</details>
https://github.com/llvm/llvm-project/pull/167201
More information about the cfe-commits
mailing list