Skip to content

Conversation

@saideepaksana
Copy link

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-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.

…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.
@github-actions
Copy link

github-actions bot commented Nov 8, 2025

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 @ followed by their GitHub username.

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.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Nov 8, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 8, 2025

@llvm/pr-subscribers-clang

Author: Sai Deepak Sana (saideepaksana)

Changes

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-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/167164.diff

6 Files Affected:

  • (modified) clang/lib/Sema/SemaTemplate.cpp (+130-58)
  • (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 --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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

clang: Missing line and file in message error when ‘auto’ is used in a not permitted context (and bad diagnostic)

2 participants