-
Notifications
You must be signed in to change notification settings - Fork 15.1k
clang: improve diagnostic for 'auto' used in disallowed local class contexts #167164
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
clang: improve diagnostic for 'auto' used in disallowed local class contexts #167164
Conversation
…classes Fixes llvm#147324 When diagnosing templates inside local classes with abbreviated function templates (auto parameters in C++20+), the error message was missing file name and line number information. This patch improves CheckTemplateDeclScope to detect abbreviated function templates and emit a more specific diagnostic with proper source location.
|
Thank you for submitting a Pull Request (PR) to the LLVM Project! This PR will be automatically labeled and the relevant teams will be notified. If you wish to, you can add reviewers by using the "Reviewers" section on this page. If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers. If you have further questions, they may be answered by the LLVM GitHub User Guide. You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums. |
|
@llvm/pr-subscribers-clang Author: Sai Deepak Sana (saideepaksana) ChangesFixes #147324 Summary: Changes:
Tests Added:
Result: Full diff: https://github.com/llvm/llvm-project/pull/167164.diff 6 Files Affected:
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 4a9e1bc93b918..ca84d6b0c2cda 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,35 +8449,103 @@ 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();
+ }
+
+ 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()) {
+ // check if this is an abbreviated function template (C++20 auto params).
+ // If so, we want to emit the 'auto not allowed' diagnostic instead.
+ bool isAbbrev = false;
+
+ // find the enclosing function and check for auto parameters.
+ {
+ const Decl *d = dyn_cast<Decl>(Ctx);
+ const DeclContext *dc = d ? d->getDeclContext() : nullptr;
+ while (dc && !dc->isFunctionOrMethod())
+ dc = dc->getParent();
+
+ if (dc && dc->isFunctionOrMethod()) {
+ if (const FunctionDecl *fd = dyn_cast<FunctionDecl>(
+ dc->getRedeclContext()->getPrimaryContext())) {
+ for (const ParmVarDecl *p : fd->parameters()) {
+ if (p && p->getType()->getAs<AutoType>()) {
+ isAbbrev = true;
+ break;
+ }
+ }
+ }
+
+ // check function templates in this context.
+ if (!isAbbrev) {
+ for (Decl *d : dc->decls()) {
+ if (const FunctionTemplateDecl *ftd = dyn_cast<FunctionTemplateDecl>(d)) {
+ if (const FunctionDecl *fd = ftd->getTemplatedDecl()) {
+ for (const ParmVarDecl *p : fd->parameters()) {
+ if (p && p->getType()->getAs<AutoType>()) {
+ isAbbrev = true;
+ break;
+ }
+ }
+ if (isAbbrev) break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // fallback check methods of the local class itself.
+ if (!isAbbrev) {
+ for (const CXXMethodDecl *m : RD->methods()) {
+ for (const ParmVarDecl *p : m->parameters()) {
+ if (p && p->getType()->getAs<AutoType>()) {
+ isAbbrev = true;
+ break;
+ }
+ }
+ if (isAbbrev)
+ break;
+ }
+ }
+
+ if (Loc.isInvalid())
+ Loc = TemplateParams->getSourceRange().getBegin();
+
+ if (isAbbrev)
+ return Diag(Loc, diag::err_auto_not_allowed);
+
+ 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();
-}
+ 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
/// is.
static TemplateSpecializationKind getTemplateSpecializationKind(Decl *D) {
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
|
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:
CheckTemplateDeclScopeinSemaTemplate.cppto correctly detect abbreviated function templates and compute accurate source locations.extern "C", and invalid scopes.C++17,C++20, andC++23.Tests Added:
template-local-class.cpp– templates andautomembers inside local classtemplate-abbrev-local-class.cpp– abbreviated function templates across standardstemplate-nested-local-classes.cpp– nested local class templatesabbrev-top-level-fn.cpp– top-level functions withautoparametersResult:
Diagnostics now show correct file, line, and column numbers, and handle auto parameter templates accurately.