Skip to content
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

Reapply "[Clang] Implement resolution for CWG1835 (#92957)" #98547

Merged
merged 2 commits into from
Jul 11, 2024

Conversation

sdkrystian
Copy link
Member

@sdkrystian sdkrystian commented Jul 11, 2024

Reapplies #92957, fixing an instance where the template keyword was missing prior to a dependent name in llvm/ADT/ArrayRef.h. An alias-declaration is used to work around a bug affecting GCC releases before 11.1 which rejects the use of the template keyword prior to the nested-name-specifier in the class member access (godbolt example here).

@sdkrystian sdkrystian requested review from Endilll and a team as code owners July 11, 2024 20:55
@llvmbot llvmbot added clang Clang issues not falling into any other category libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules coroutines C++20 coroutines llvm:adt labels Jul 11, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Jul 11, 2024

@llvm/pr-subscribers-llvm-adt
@llvm/pr-subscribers-clang

@llvm/pr-subscribers-libcxx

Author: Krystian Stasiowski (sdkrystian)

Changes

Reapplies #92957, fixing an instance where the template keyword was missing prior to a dependent name in llvm/ADT/ArrayRef.h. An alias-declaration is used to work around [a bug affecting GCC releases before 11.1]](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94799) which rejects the use of the template keyword prior to the nested-name-specifier in the class member access (godbolt example here).


Patch is 156.84 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/98547.diff

47 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+3)
  • (modified) clang/include/clang/AST/ExprCXX.h (+49-43)
  • (modified) clang/include/clang/AST/Stmt.h (+8-7)
  • (modified) clang/include/clang/AST/UnresolvedSet.h (+4)
  • (modified) clang/include/clang/Basic/DiagnosticParseKinds.td (+1-3)
  • (modified) clang/include/clang/Parse/Parser.h (+5-9)
  • (modified) clang/include/clang/Sema/DeclSpec.h (+8)
  • (modified) clang/include/clang/Sema/Lookup.h (+6-2)
  • (modified) clang/include/clang/Sema/Sema.h (+22-32)
  • (modified) clang/lib/AST/ASTImporter.cpp (+9-3)
  • (modified) clang/lib/AST/ExprCXX.cpp (+40-27)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+15-24)
  • (modified) clang/lib/Parse/ParseExpr.cpp (+3-4)
  • (modified) clang/lib/Parse/ParseExprCXX.cpp (+50-45)
  • (modified) clang/lib/Sema/SemaCXXScopeSpec.cpp (+91-104)
  • (modified) clang/lib/Sema/SemaCoroutine.cpp (+2-2)
  • (modified) clang/lib/Sema/SemaDeclCXX.cpp (+23-24)
  • (modified) clang/lib/Sema/SemaExpr.cpp (+1-1)
  • (modified) clang/lib/Sema/SemaExprMember.cpp (+33-41)
  • (modified) clang/lib/Sema/SemaOverload.cpp (+5-7)
  • (modified) clang/lib/Sema/SemaStmtAsm.cpp (+5-3)
  • (modified) clang/lib/Sema/SemaTemplate.cpp (+105-113)
  • (modified) clang/lib/Sema/SemaTemplateInstantiate.cpp (+7-10)
  • (modified) clang/lib/Sema/TreeTransform.h (+131-198)
  • (modified) clang/lib/Serialization/ASTReaderStmt.cpp (+32-31)
  • (modified) clang/lib/Serialization/ASTWriterStmt.cpp (+25-18)
  • (modified) clang/test/CXX/basic/basic.lookup/basic.lookup.classref/p1-cxx11.cpp (+9-5)
  • (modified) clang/test/CXX/basic/basic.lookup/basic.lookup.classref/p1.cpp (+9-5)
  • (added) clang/test/CXX/basic/basic.lookup/basic.lookup.qual/basic.lookup.qual.general/p3-example3.cpp (+27)
  • (added) clang/test/CXX/basic/basic.lookup/basic.lookup.qual/basic.lookup.qual.general/p3.cpp (+98)
  • (modified) clang/test/CXX/class.derived/class.member.lookup/p8.cpp (+2-2)
  • (modified) clang/test/CXX/drs/cwg1xx.cpp (+4-4)
  • (added) clang/test/CXX/temp/temp.names/p3-23.cpp (+237)
  • (modified) clang/test/CXX/temp/temp.res/p3.cpp (+1-1)
  • (modified) clang/test/FixIt/fixit.cpp (+2-2)
  • (modified) clang/test/Misc/warning-flags.c (+1-1)
  • (modified) clang/test/Parser/cxx2a-concepts-requires-expr.cpp (+1-1)
  • (modified) clang/test/SemaCXX/cxx0x-noexcept-expression.cpp (+3-3)
  • (modified) clang/test/SemaCXX/pseudo-destructors.cpp (+9-9)
  • (modified) clang/test/SemaCXX/static-assert-cxx17.cpp (+1-1)
  • (modified) clang/test/SemaTemplate/dependent-base-classes.cpp (+10-10)
  • (modified) clang/test/SemaTemplate/dependent-template-recover.cpp (+6-6)
  • (modified) clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp (+1-1)
  • (modified) clang/test/SemaTemplate/template-id-expr.cpp (+7-7)
  • (modified) clang/test/SemaTemplate/typename-specifier-3.cpp (+1-1)
  • (modified) libcxx/include/regex (+1-1)
  • (modified) llvm/include/llvm/ADT/ArrayRef.h (+5-2)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index d5446f7fd92cc..be2f06766a755 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -310,6 +310,9 @@ Resolutions to C++ Defect Reports
 - Clang now considers ``noexcept(typeid(expr))`` more carefully, instead of always assuming that ``std::bad_typeid`` can be thrown.
   (`CWG2191: Incorrect result for noexcept(typeid(v)) <https://cplusplus.github.io/CWG/issues/2191.html>`_).
 
+- Clang now correctly implements lookup for the terminal name of a member-qualified nested-name-specifier.
+  (`CWG1835: Dependent member lookup before < <https://cplusplus.github.io/CWG/issues/1835.html>`_).
+
 C Language Changes
 ------------------
 
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index c2feac525c1ea..edaea6fe27cc3 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -3676,9 +3676,9 @@ class CXXUnresolvedConstructExpr final
 /// an implicit access if a qualifier is provided.
 class CXXDependentScopeMemberExpr final
     : public Expr,
-      private llvm::TrailingObjects<CXXDependentScopeMemberExpr,
-                                    ASTTemplateKWAndArgsInfo,
-                                    TemplateArgumentLoc, NamedDecl *> {
+      private llvm::TrailingObjects<
+          CXXDependentScopeMemberExpr, NestedNameSpecifierLoc, DeclAccessPair,
+          ASTTemplateKWAndArgsInfo, TemplateArgumentLoc> {
   friend class ASTStmtReader;
   friend class ASTStmtWriter;
   friend TrailingObjects;
@@ -3691,17 +3691,15 @@ class CXXDependentScopeMemberExpr final
   /// implicit accesses.
   QualType BaseType;
 
-  /// The nested-name-specifier that precedes the member name, if any.
-  /// FIXME: This could be in principle store as a trailing object.
-  /// However the performance impact of doing so should be investigated first.
-  NestedNameSpecifierLoc QualifierLoc;
-
   /// The member to which this member expression refers, which
   /// can be name, overloaded operator, or destructor.
   ///
   /// FIXME: could also be a template-id
   DeclarationNameInfo MemberNameInfo;
 
+  /// The location of the '->' or '.' operator.
+  SourceLocation OperatorLoc;
+
   // CXXDependentScopeMemberExpr is followed by several trailing objects,
   // some of which optional. They are in order:
   //
@@ -3721,8 +3719,16 @@ class CXXDependentScopeMemberExpr final
     return CXXDependentScopeMemberExprBits.HasTemplateKWAndArgsInfo;
   }
 
-  bool hasFirstQualifierFoundInScope() const {
-    return CXXDependentScopeMemberExprBits.HasFirstQualifierFoundInScope;
+  unsigned getNumUnqualifiedLookups() const {
+    return CXXDependentScopeMemberExprBits.NumUnqualifiedLookups;
+  }
+
+  unsigned numTrailingObjects(OverloadToken<NestedNameSpecifierLoc>) const {
+    return hasQualifier();
+  }
+
+  unsigned numTrailingObjects(OverloadToken<DeclAccessPair>) const {
+    return getNumUnqualifiedLookups();
   }
 
   unsigned numTrailingObjects(OverloadToken<ASTTemplateKWAndArgsInfo>) const {
@@ -3733,33 +3739,32 @@ class CXXDependentScopeMemberExpr final
     return getNumTemplateArgs();
   }
 
-  unsigned numTrailingObjects(OverloadToken<NamedDecl *>) const {
-    return hasFirstQualifierFoundInScope();
-  }
-
   CXXDependentScopeMemberExpr(const ASTContext &Ctx, Expr *Base,
                               QualType BaseType, bool IsArrow,
                               SourceLocation OperatorLoc,
                               NestedNameSpecifierLoc QualifierLoc,
                               SourceLocation TemplateKWLoc,
-                              NamedDecl *FirstQualifierFoundInScope,
+                              ArrayRef<DeclAccessPair> UnqualifiedLookups,
                               DeclarationNameInfo MemberNameInfo,
                               const TemplateArgumentListInfo *TemplateArgs);
 
-  CXXDependentScopeMemberExpr(EmptyShell Empty, bool HasTemplateKWAndArgsInfo,
-                              bool HasFirstQualifierFoundInScope);
+  CXXDependentScopeMemberExpr(EmptyShell Empty, bool HasQualifier,
+                              unsigned NumUnqualifiedLookups,
+                              bool HasTemplateKWAndArgsInfo);
 
 public:
   static CXXDependentScopeMemberExpr *
   Create(const ASTContext &Ctx, Expr *Base, QualType BaseType, bool IsArrow,
          SourceLocation OperatorLoc, NestedNameSpecifierLoc QualifierLoc,
-         SourceLocation TemplateKWLoc, NamedDecl *FirstQualifierFoundInScope,
+         SourceLocation TemplateKWLoc,
+         ArrayRef<DeclAccessPair> UnqualifiedLookups,
          DeclarationNameInfo MemberNameInfo,
          const TemplateArgumentListInfo *TemplateArgs);
 
   static CXXDependentScopeMemberExpr *
-  CreateEmpty(const ASTContext &Ctx, bool HasTemplateKWAndArgsInfo,
-              unsigned NumTemplateArgs, bool HasFirstQualifierFoundInScope);
+  CreateEmpty(const ASTContext &Ctx, bool HasQualifier,
+              unsigned NumUnqualifiedLookups, bool HasTemplateKWAndArgsInfo,
+              unsigned NumTemplateArgs);
 
   /// True if this is an implicit access, i.e. one in which the
   /// member being accessed was not written in the source.  The source
@@ -3784,34 +3789,35 @@ class CXXDependentScopeMemberExpr final
   bool isArrow() const { return CXXDependentScopeMemberExprBits.IsArrow; }
 
   /// Retrieve the location of the '->' or '.' operator.
-  SourceLocation getOperatorLoc() const {
-    return CXXDependentScopeMemberExprBits.OperatorLoc;
+  SourceLocation getOperatorLoc() const { return OperatorLoc; }
+
+  /// Determines whether this member expression had a nested-name-specifier
+  /// prior to the name of the member, e.g., x->Base::foo.
+  bool hasQualifier() const {
+    return CXXDependentScopeMemberExprBits.HasQualifier;
   }
 
-  /// Retrieve the nested-name-specifier that qualifies the member name.
-  NestedNameSpecifier *getQualifier() const {
-    return QualifierLoc.getNestedNameSpecifier();
+  /// If the member name was qualified, retrieves the nested-name-specifier
+  /// that precedes the member name, with source-location information.
+  NestedNameSpecifierLoc getQualifierLoc() const {
+    if (!hasQualifier())
+      return NestedNameSpecifierLoc();
+    return *getTrailingObjects<NestedNameSpecifierLoc>();
   }
 
-  /// Retrieve the nested-name-specifier that qualifies the member
-  /// name, with source location information.
-  NestedNameSpecifierLoc getQualifierLoc() const { return QualifierLoc; }
+  /// If the member name was qualified, retrieves the
+  /// nested-name-specifier that precedes the member name. Otherwise, returns
+  /// NULL.
+  NestedNameSpecifier *getQualifier() const {
+    return getQualifierLoc().getNestedNameSpecifier();
+  }
 
-  /// Retrieve the first part of the nested-name-specifier that was
-  /// found in the scope of the member access expression when the member access
-  /// was initially parsed.
-  ///
-  /// This function only returns a useful result when member access expression
-  /// uses a qualified member name, e.g., "x.Base::f". Here, the declaration
-  /// returned by this function describes what was found by unqualified name
-  /// lookup for the identifier "Base" within the scope of the member access
-  /// expression itself. At template instantiation time, this information is
-  /// combined with the results of name lookup into the type of the object
-  /// expression itself (the class type of x).
-  NamedDecl *getFirstQualifierFoundInScope() const {
-    if (!hasFirstQualifierFoundInScope())
-      return nullptr;
-    return *getTrailingObjects<NamedDecl *>();
+  /// Retrieve the declarations found by unqualified lookup for the first
+  /// component name of the nested-name-specifier, if any.
+  ArrayRef<DeclAccessPair> unqualified_lookups() const {
+    if (!getNumUnqualifiedLookups())
+      return std::nullopt;
+    return {getTrailingObjects<DeclAccessPair>(), getNumUnqualifiedLookups()};
   }
 
   /// Retrieve the name of the member that this expression refers to.
diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h
index 9cd7a364cd3f1..257a61c97c9c6 100644
--- a/clang/include/clang/AST/Stmt.h
+++ b/clang/include/clang/AST/Stmt.h
@@ -1020,18 +1020,19 @@ class alignas(void *) Stmt {
     LLVM_PREFERRED_TYPE(bool)
     unsigned IsArrow : 1;
 
+    /// True if this member expression used a nested-name-specifier to
+    /// refer to the member, e.g., "x->Base::f".
+    LLVM_PREFERRED_TYPE(bool)
+    unsigned HasQualifier : 1;
+
     /// Whether this member expression has info for explicit template
     /// keyword and arguments.
     LLVM_PREFERRED_TYPE(bool)
     unsigned HasTemplateKWAndArgsInfo : 1;
 
-    /// See getFirstQualifierFoundInScope() and the comment listing
-    /// the trailing objects.
-    LLVM_PREFERRED_TYPE(bool)
-    unsigned HasFirstQualifierFoundInScope : 1;
-
-    /// The location of the '->' or '.' operator.
-    SourceLocation OperatorLoc;
+    /// Number of declarations found by unqualified lookup for the
+    /// first component name of the nested-name-specifier.
+    unsigned NumUnqualifiedLookups;
   };
 
   class OverloadExprBitfields {
diff --git a/clang/include/clang/AST/UnresolvedSet.h b/clang/include/clang/AST/UnresolvedSet.h
index 1369725ab4e96..ef44499ce5926 100644
--- a/clang/include/clang/AST/UnresolvedSet.h
+++ b/clang/include/clang/AST/UnresolvedSet.h
@@ -97,6 +97,10 @@ class UnresolvedSetImpl {
     decls().push_back(DeclAccessPair::make(D, AS));
   }
 
+  void addAllDecls(ArrayRef<DeclAccessPair> Other) {
+    append(iterator(Other.begin()), iterator(Other.end()));
+  }
+
   /// Replaces the given declaration with the new one, once.
   ///
   /// \return true if the set changed
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 12aab09f28556..0bd2e35bf2e31 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -895,9 +895,7 @@ def missing_template_arg_list_after_template_kw : Extension<
   "keyword">, InGroup<DiagGroup<"missing-template-arg-list-after-template-kw">>,
   DefaultError;
 
-def err_missing_dependent_template_keyword : Error<
-  "use 'template' keyword to treat '%0' as a dependent template name">;
-def warn_missing_dependent_template_keyword : ExtWarn<
+def ext_missing_dependent_template_keyword : ExtWarn<
   "use 'template' keyword to treat '%0' as a dependent template name">;
 
 def ext_extern_template : Extension<
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 6880fa4bb0b03..b4f5270a35956 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -3368,15 +3368,11 @@ class Parser : public CodeCompletionHandler {
   BaseResult ParseBaseSpecifier(Decl *ClassDecl);
   AccessSpecifier getAccessSpecifierIfPresent() const;
 
-  bool ParseUnqualifiedIdTemplateId(CXXScopeSpec &SS,
-                                    ParsedType ObjectType,
-                                    bool ObjectHadErrors,
-                                    SourceLocation TemplateKWLoc,
-                                    IdentifierInfo *Name,
-                                    SourceLocation NameLoc,
-                                    bool EnteringContext,
-                                    UnqualifiedId &Id,
-                                    bool AssumeTemplateId);
+  bool ParseUnqualifiedIdTemplateId(
+      CXXScopeSpec &SS, ParsedType ObjectType, bool ObjectHadErrors,
+      SourceLocation TemplateKWLoc, SourceLocation TildeLoc,
+      IdentifierInfo *Name, SourceLocation NameLoc, bool EnteringContext,
+      UnqualifiedId &Id, bool AssumeTemplateId);
   bool ParseUnqualifiedIdOperator(CXXScopeSpec &SS, bool EnteringContext,
                                   ParsedType ObjectType,
                                   UnqualifiedId &Result);
diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h
index 425b6e2a0b30c..9c22c35535ede 100644
--- a/clang/include/clang/Sema/DeclSpec.h
+++ b/clang/include/clang/Sema/DeclSpec.h
@@ -75,6 +75,7 @@ class CXXScopeSpec {
   SourceRange Range;
   NestedNameSpecifierLocBuilder Builder;
   ArrayRef<TemplateParameterList *> TemplateParamLists;
+  ArrayRef<DeclAccessPair> UnqualifiedLookups;
 
 public:
   SourceRange getRange() const { return Range; }
@@ -91,6 +92,13 @@ class CXXScopeSpec {
     return TemplateParamLists;
   }
 
+  void setUnqualifiedLookups(ArrayRef<DeclAccessPair> Found) {
+    UnqualifiedLookups = Found;
+  }
+  ArrayRef<DeclAccessPair> getUnqualifiedLookups() const {
+    return UnqualifiedLookups;
+  }
+
   /// Retrieve the representation of the nested-name-specifier.
   NestedNameSpecifier *getScopeRep() const {
     return Builder.getRepresentation();
diff --git a/clang/include/clang/Sema/Lookup.h b/clang/include/clang/Sema/Lookup.h
index b0a08a05ac6a0..6b765ef3c980f 100644
--- a/clang/include/clang/Sema/Lookup.h
+++ b/clang/include/clang/Sema/Lookup.h
@@ -483,11 +483,15 @@ class LookupResult {
     ResultKind = Found;
   }
 
+  void addAllDecls(ArrayRef<DeclAccessPair> Other) {
+    Decls.addAllDecls(Other);
+    ResultKind = Found;
+  }
+
   /// Add all the declarations from another set of lookup
   /// results.
   void addAllDecls(const LookupResult &Other) {
-    Decls.append(Other.Decls.begin(), Other.Decls.end());
-    ResultKind = Found;
+    addAllDecls(Other.Decls.pairs());
   }
 
   /// Determine whether no result was found because we could not
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 57994f4033922..6be6f6725e5b7 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2802,7 +2802,8 @@ class Sema final : public SemaBase {
   /// (e.g., Base::), perform name lookup for that identifier as a
   /// nested-name-specifier within the given scope, and return the result of
   /// that name lookup.
-  NamedDecl *FindFirstQualifierInScope(Scope *S, NestedNameSpecifier *NNS);
+  bool LookupFirstQualifierInScope(Scope *S, NestedNameSpecifier *NNS,
+                                   UnresolvedSetImpl &R);
 
   /// Keeps information about an identifier in a nested-name-spec.
   ///
@@ -2842,9 +2843,6 @@ class Sema final : public SemaBase {
   /// \param EnteringContext If true, enter the context specified by the
   ///        nested-name-specifier.
   /// \param SS Optional nested name specifier preceding the identifier.
-  /// \param ScopeLookupResult Provides the result of name lookup within the
-  ///        scope of the nested-name-specifier that was computed at template
-  ///        definition time.
   /// \param ErrorRecoveryLookup Specifies if the method is called to improve
   ///        error recovery and what kind of recovery is performed.
   /// \param IsCorrectedToColon If not null, suggestion of replace '::' -> ':'
@@ -2853,11 +2851,6 @@ class Sema final : public SemaBase {
   ///        not '::'.
   /// \param OnlyNamespace If true, only considers namespaces in lookup.
   ///
-  /// This routine differs only slightly from ActOnCXXNestedNameSpecifier, in
-  /// that it contains an extra parameter \p ScopeLookupResult, which provides
-  /// the result of name lookup within the scope of the nested-name-specifier
-  /// that was computed at template definition time.
-  ///
   /// If ErrorRecoveryLookup is true, then this call is used to improve error
   /// recovery.  This means that it should not emit diagnostics, it should
   /// just return true on failure.  It also means it should only return a valid
@@ -2866,7 +2859,6 @@ class Sema final : public SemaBase {
   /// specifier.
   bool BuildCXXNestedNameSpecifier(Scope *S, NestedNameSpecInfo &IdInfo,
                                    bool EnteringContext, CXXScopeSpec &SS,
-                                   NamedDecl *ScopeLookupResult,
                                    bool ErrorRecoveryLookup,
                                    bool *IsCorrectedToColon = nullptr,
                                    bool OnlyNamespace = false);
@@ -8566,11 +8558,12 @@ class Sema final : public SemaBase {
                           const TemplateArgumentListInfo *TemplateArgs,
                           bool IsDefiniteInstance, const Scope *S);
 
-  ExprResult ActOnDependentMemberExpr(
-      Expr *Base, QualType BaseType, bool IsArrow, SourceLocation OpLoc,
-      const CXXScopeSpec &SS, SourceLocation TemplateKWLoc,
-      NamedDecl *FirstQualifierInScope, const DeclarationNameInfo &NameInfo,
-      const TemplateArgumentListInfo *TemplateArgs);
+  ExprResult
+  ActOnDependentMemberExpr(Expr *Base, QualType BaseType, bool IsArrow,
+                           SourceLocation OpLoc, const CXXScopeSpec &SS,
+                           SourceLocation TemplateKWLoc,
+                           const DeclarationNameInfo &NameInfo,
+                           const TemplateArgumentListInfo *TemplateArgs);
 
   /// The main callback when the parser finds something like
   ///   expression . [nested-name-specifier] identifier
@@ -8626,15 +8619,14 @@ class Sema final : public SemaBase {
   ExprResult BuildMemberReferenceExpr(
       Expr *Base, QualType BaseType, SourceLocation OpLoc, bool IsArrow,
       CXXScopeSpec &SS, SourceLocation TemplateKWLoc,
-      NamedDecl *FirstQualifierInScope, const DeclarationNameInfo &NameInfo,
+      const DeclarationNameInfo &NameInfo,
       const TemplateArgumentListInfo *TemplateArgs, const Scope *S,
       ActOnMemberAccessExtraArgs *ExtraArgs = nullptr);
 
   ExprResult
   BuildMemberReferenceExpr(Expr *Base, QualType BaseType, SourceLocation OpLoc,
                            bool IsArrow, const CXXScopeSpec &SS,
-                           SourceLocation TemplateKWLoc,
-                           NamedDecl *FirstQualifierInScope, LookupResult &R,
+                           SourceLocation TemplateKWLoc, LookupResult &R,
                            const TemplateArgumentListInfo *TemplateArgs,
                            const Scope *S, bool SuppressQualifierCheck = false,
                            ActOnMemberAccessExtraArgs *ExtraArgs = nullptr);
@@ -11122,15 +11114,14 @@ class Sema final : public SemaBase {
                      QualType ObjectType, bool EnteringContext,
                      RequiredTemplateKind RequiredTemplate = SourceLocation(),
                      AssumedTemplateKind *ATK = nullptr,
-                     bool AllowTypoCorrection = true);
+                     bool AllowTypoCorrection = true, bool MayBeNNS = false);
 
-  TemplateNameKind isTemplateName(Scope *S, CXXScopeSpec &SS,
-                                  bool hasTemplateKeyword,
-                                  const UnqualifiedId &Name,
-                                  ParsedType ObjectType, bool EnteringContext,
-                                  TemplateTy &Template,
-                                  bool &MemberOfUnknownSpecialization,
-                                  bool Disambiguation = false);
+  TemplateNameKind
+  isTemplateName(Scope *S, CXXScopeSpec &SS, bool hasTemplateKeyword,
+                 const UnqualifiedId &Name, ParsedType ObjectType,
+                 bool EnteringContext, TemplateTy &Template,
+                 bool &MemberOfUnknownSpecialization,
+                 bool Disambiguation = false, bool MayBeNNS = false);
 
   /// Try to resolve an undeclared template name as a type template.
   ///
@@ -11459,12 +11450,11 @@ class Sema final : public SemaBase {
   /// For example, given "x.MetaFun::template apply", the scope specifier
   /// \p SS will be "MetaFun::", \p TemplateKWLoc contains the location
   /// of the "template" keyword, and "apply" is the \p Name.
-  TemplateNameKind ActOnTemplateName(Scope *S, CXXScopeSpec &SS,
-                                     SourceLocation TemplateKWLoc,
-                                     const UnqualifiedId &Name,
-                                     ParsedType ObjectType,
-                                     bool EnteringContext, TemplateTy &Template,
-                                     bool AllowInjectedClassName = false);
+  TemplateNameKind
+  ActOnTemplateName(Scope *S, CXXScopeSpec &SS, SourceLocation TemplateKWLoc,
+                    const UnqualifiedId &Name, ParsedType ObjectType,
+                    bool EnteringContext, TemplateTy &Temp...
[truncated]

@llvmbot
Copy link
Collaborator

llvmbot commented Jul 11, 2024

@llvm/pr-subscribers-clang-modules

Author: Krystian Stasiowski (sdkrystian)

Changes

Reapplies #92957, fixing an instance where the template keyword was missing prior to a dependent name in llvm/ADT/ArrayRef.h. An alias-declaration is used to work around [a bug affecting GCC releases before 11.1]](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94799) which rejects the use of the template keyword prior to the nested-name-specifier in the class member access (godbolt example here).


Patch is 156.84 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/98547.diff

47 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+3)
  • (modified) clang/include/clang/AST/ExprCXX.h (+49-43)
  • (modified) clang/include/clang/AST/Stmt.h (+8-7)
  • (modified) clang/include/clang/AST/UnresolvedSet.h (+4)
  • (modified) clang/include/clang/Basic/DiagnosticParseKinds.td (+1-3)
  • (modified) clang/include/clang/Parse/Parser.h (+5-9)
  • (modified) clang/include/clang/Sema/DeclSpec.h (+8)
  • (modified) clang/include/clang/Sema/Lookup.h (+6-2)
  • (modified) clang/include/clang/Sema/Sema.h (+22-32)
  • (modified) clang/lib/AST/ASTImporter.cpp (+9-3)
  • (modified) clang/lib/AST/ExprCXX.cpp (+40-27)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+15-24)
  • (modified) clang/lib/Parse/ParseExpr.cpp (+3-4)
  • (modified) clang/lib/Parse/ParseExprCXX.cpp (+50-45)
  • (modified) clang/lib/Sema/SemaCXXScopeSpec.cpp (+91-104)
  • (modified) clang/lib/Sema/SemaCoroutine.cpp (+2-2)
  • (modified) clang/lib/Sema/SemaDeclCXX.cpp (+23-24)
  • (modified) clang/lib/Sema/SemaExpr.cpp (+1-1)
  • (modified) clang/lib/Sema/SemaExprMember.cpp (+33-41)
  • (modified) clang/lib/Sema/SemaOverload.cpp (+5-7)
  • (modified) clang/lib/Sema/SemaStmtAsm.cpp (+5-3)
  • (modified) clang/lib/Sema/SemaTemplate.cpp (+105-113)
  • (modified) clang/lib/Sema/SemaTemplateInstantiate.cpp (+7-10)
  • (modified) clang/lib/Sema/TreeTransform.h (+131-198)
  • (modified) clang/lib/Serialization/ASTReaderStmt.cpp (+32-31)
  • (modified) clang/lib/Serialization/ASTWriterStmt.cpp (+25-18)
  • (modified) clang/test/CXX/basic/basic.lookup/basic.lookup.classref/p1-cxx11.cpp (+9-5)
  • (modified) clang/test/CXX/basic/basic.lookup/basic.lookup.classref/p1.cpp (+9-5)
  • (added) clang/test/CXX/basic/basic.lookup/basic.lookup.qual/basic.lookup.qual.general/p3-example3.cpp (+27)
  • (added) clang/test/CXX/basic/basic.lookup/basic.lookup.qual/basic.lookup.qual.general/p3.cpp (+98)
  • (modified) clang/test/CXX/class.derived/class.member.lookup/p8.cpp (+2-2)
  • (modified) clang/test/CXX/drs/cwg1xx.cpp (+4-4)
  • (added) clang/test/CXX/temp/temp.names/p3-23.cpp (+237)
  • (modified) clang/test/CXX/temp/temp.res/p3.cpp (+1-1)
  • (modified) clang/test/FixIt/fixit.cpp (+2-2)
  • (modified) clang/test/Misc/warning-flags.c (+1-1)
  • (modified) clang/test/Parser/cxx2a-concepts-requires-expr.cpp (+1-1)
  • (modified) clang/test/SemaCXX/cxx0x-noexcept-expression.cpp (+3-3)
  • (modified) clang/test/SemaCXX/pseudo-destructors.cpp (+9-9)
  • (modified) clang/test/SemaCXX/static-assert-cxx17.cpp (+1-1)
  • (modified) clang/test/SemaTemplate/dependent-base-classes.cpp (+10-10)
  • (modified) clang/test/SemaTemplate/dependent-template-recover.cpp (+6-6)
  • (modified) clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp (+1-1)
  • (modified) clang/test/SemaTemplate/template-id-expr.cpp (+7-7)
  • (modified) clang/test/SemaTemplate/typename-specifier-3.cpp (+1-1)
  • (modified) libcxx/include/regex (+1-1)
  • (modified) llvm/include/llvm/ADT/ArrayRef.h (+5-2)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index d5446f7fd92cc..be2f06766a755 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -310,6 +310,9 @@ Resolutions to C++ Defect Reports
 - Clang now considers ``noexcept(typeid(expr))`` more carefully, instead of always assuming that ``std::bad_typeid`` can be thrown.
   (`CWG2191: Incorrect result for noexcept(typeid(v)) <https://cplusplus.github.io/CWG/issues/2191.html>`_).
 
+- Clang now correctly implements lookup for the terminal name of a member-qualified nested-name-specifier.
+  (`CWG1835: Dependent member lookup before < <https://cplusplus.github.io/CWG/issues/1835.html>`_).
+
 C Language Changes
 ------------------
 
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index c2feac525c1ea..edaea6fe27cc3 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -3676,9 +3676,9 @@ class CXXUnresolvedConstructExpr final
 /// an implicit access if a qualifier is provided.
 class CXXDependentScopeMemberExpr final
     : public Expr,
-      private llvm::TrailingObjects<CXXDependentScopeMemberExpr,
-                                    ASTTemplateKWAndArgsInfo,
-                                    TemplateArgumentLoc, NamedDecl *> {
+      private llvm::TrailingObjects<
+          CXXDependentScopeMemberExpr, NestedNameSpecifierLoc, DeclAccessPair,
+          ASTTemplateKWAndArgsInfo, TemplateArgumentLoc> {
   friend class ASTStmtReader;
   friend class ASTStmtWriter;
   friend TrailingObjects;
@@ -3691,17 +3691,15 @@ class CXXDependentScopeMemberExpr final
   /// implicit accesses.
   QualType BaseType;
 
-  /// The nested-name-specifier that precedes the member name, if any.
-  /// FIXME: This could be in principle store as a trailing object.
-  /// However the performance impact of doing so should be investigated first.
-  NestedNameSpecifierLoc QualifierLoc;
-
   /// The member to which this member expression refers, which
   /// can be name, overloaded operator, or destructor.
   ///
   /// FIXME: could also be a template-id
   DeclarationNameInfo MemberNameInfo;
 
+  /// The location of the '->' or '.' operator.
+  SourceLocation OperatorLoc;
+
   // CXXDependentScopeMemberExpr is followed by several trailing objects,
   // some of which optional. They are in order:
   //
@@ -3721,8 +3719,16 @@ class CXXDependentScopeMemberExpr final
     return CXXDependentScopeMemberExprBits.HasTemplateKWAndArgsInfo;
   }
 
-  bool hasFirstQualifierFoundInScope() const {
-    return CXXDependentScopeMemberExprBits.HasFirstQualifierFoundInScope;
+  unsigned getNumUnqualifiedLookups() const {
+    return CXXDependentScopeMemberExprBits.NumUnqualifiedLookups;
+  }
+
+  unsigned numTrailingObjects(OverloadToken<NestedNameSpecifierLoc>) const {
+    return hasQualifier();
+  }
+
+  unsigned numTrailingObjects(OverloadToken<DeclAccessPair>) const {
+    return getNumUnqualifiedLookups();
   }
 
   unsigned numTrailingObjects(OverloadToken<ASTTemplateKWAndArgsInfo>) const {
@@ -3733,33 +3739,32 @@ class CXXDependentScopeMemberExpr final
     return getNumTemplateArgs();
   }
 
-  unsigned numTrailingObjects(OverloadToken<NamedDecl *>) const {
-    return hasFirstQualifierFoundInScope();
-  }
-
   CXXDependentScopeMemberExpr(const ASTContext &Ctx, Expr *Base,
                               QualType BaseType, bool IsArrow,
                               SourceLocation OperatorLoc,
                               NestedNameSpecifierLoc QualifierLoc,
                               SourceLocation TemplateKWLoc,
-                              NamedDecl *FirstQualifierFoundInScope,
+                              ArrayRef<DeclAccessPair> UnqualifiedLookups,
                               DeclarationNameInfo MemberNameInfo,
                               const TemplateArgumentListInfo *TemplateArgs);
 
-  CXXDependentScopeMemberExpr(EmptyShell Empty, bool HasTemplateKWAndArgsInfo,
-                              bool HasFirstQualifierFoundInScope);
+  CXXDependentScopeMemberExpr(EmptyShell Empty, bool HasQualifier,
+                              unsigned NumUnqualifiedLookups,
+                              bool HasTemplateKWAndArgsInfo);
 
 public:
   static CXXDependentScopeMemberExpr *
   Create(const ASTContext &Ctx, Expr *Base, QualType BaseType, bool IsArrow,
          SourceLocation OperatorLoc, NestedNameSpecifierLoc QualifierLoc,
-         SourceLocation TemplateKWLoc, NamedDecl *FirstQualifierFoundInScope,
+         SourceLocation TemplateKWLoc,
+         ArrayRef<DeclAccessPair> UnqualifiedLookups,
          DeclarationNameInfo MemberNameInfo,
          const TemplateArgumentListInfo *TemplateArgs);
 
   static CXXDependentScopeMemberExpr *
-  CreateEmpty(const ASTContext &Ctx, bool HasTemplateKWAndArgsInfo,
-              unsigned NumTemplateArgs, bool HasFirstQualifierFoundInScope);
+  CreateEmpty(const ASTContext &Ctx, bool HasQualifier,
+              unsigned NumUnqualifiedLookups, bool HasTemplateKWAndArgsInfo,
+              unsigned NumTemplateArgs);
 
   /// True if this is an implicit access, i.e. one in which the
   /// member being accessed was not written in the source.  The source
@@ -3784,34 +3789,35 @@ class CXXDependentScopeMemberExpr final
   bool isArrow() const { return CXXDependentScopeMemberExprBits.IsArrow; }
 
   /// Retrieve the location of the '->' or '.' operator.
-  SourceLocation getOperatorLoc() const {
-    return CXXDependentScopeMemberExprBits.OperatorLoc;
+  SourceLocation getOperatorLoc() const { return OperatorLoc; }
+
+  /// Determines whether this member expression had a nested-name-specifier
+  /// prior to the name of the member, e.g., x->Base::foo.
+  bool hasQualifier() const {
+    return CXXDependentScopeMemberExprBits.HasQualifier;
   }
 
-  /// Retrieve the nested-name-specifier that qualifies the member name.
-  NestedNameSpecifier *getQualifier() const {
-    return QualifierLoc.getNestedNameSpecifier();
+  /// If the member name was qualified, retrieves the nested-name-specifier
+  /// that precedes the member name, with source-location information.
+  NestedNameSpecifierLoc getQualifierLoc() const {
+    if (!hasQualifier())
+      return NestedNameSpecifierLoc();
+    return *getTrailingObjects<NestedNameSpecifierLoc>();
   }
 
-  /// Retrieve the nested-name-specifier that qualifies the member
-  /// name, with source location information.
-  NestedNameSpecifierLoc getQualifierLoc() const { return QualifierLoc; }
+  /// If the member name was qualified, retrieves the
+  /// nested-name-specifier that precedes the member name. Otherwise, returns
+  /// NULL.
+  NestedNameSpecifier *getQualifier() const {
+    return getQualifierLoc().getNestedNameSpecifier();
+  }
 
-  /// Retrieve the first part of the nested-name-specifier that was
-  /// found in the scope of the member access expression when the member access
-  /// was initially parsed.
-  ///
-  /// This function only returns a useful result when member access expression
-  /// uses a qualified member name, e.g., "x.Base::f". Here, the declaration
-  /// returned by this function describes what was found by unqualified name
-  /// lookup for the identifier "Base" within the scope of the member access
-  /// expression itself. At template instantiation time, this information is
-  /// combined with the results of name lookup into the type of the object
-  /// expression itself (the class type of x).
-  NamedDecl *getFirstQualifierFoundInScope() const {
-    if (!hasFirstQualifierFoundInScope())
-      return nullptr;
-    return *getTrailingObjects<NamedDecl *>();
+  /// Retrieve the declarations found by unqualified lookup for the first
+  /// component name of the nested-name-specifier, if any.
+  ArrayRef<DeclAccessPair> unqualified_lookups() const {
+    if (!getNumUnqualifiedLookups())
+      return std::nullopt;
+    return {getTrailingObjects<DeclAccessPair>(), getNumUnqualifiedLookups()};
   }
 
   /// Retrieve the name of the member that this expression refers to.
diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h
index 9cd7a364cd3f1..257a61c97c9c6 100644
--- a/clang/include/clang/AST/Stmt.h
+++ b/clang/include/clang/AST/Stmt.h
@@ -1020,18 +1020,19 @@ class alignas(void *) Stmt {
     LLVM_PREFERRED_TYPE(bool)
     unsigned IsArrow : 1;
 
+    /// True if this member expression used a nested-name-specifier to
+    /// refer to the member, e.g., "x->Base::f".
+    LLVM_PREFERRED_TYPE(bool)
+    unsigned HasQualifier : 1;
+
     /// Whether this member expression has info for explicit template
     /// keyword and arguments.
     LLVM_PREFERRED_TYPE(bool)
     unsigned HasTemplateKWAndArgsInfo : 1;
 
-    /// See getFirstQualifierFoundInScope() and the comment listing
-    /// the trailing objects.
-    LLVM_PREFERRED_TYPE(bool)
-    unsigned HasFirstQualifierFoundInScope : 1;
-
-    /// The location of the '->' or '.' operator.
-    SourceLocation OperatorLoc;
+    /// Number of declarations found by unqualified lookup for the
+    /// first component name of the nested-name-specifier.
+    unsigned NumUnqualifiedLookups;
   };
 
   class OverloadExprBitfields {
diff --git a/clang/include/clang/AST/UnresolvedSet.h b/clang/include/clang/AST/UnresolvedSet.h
index 1369725ab4e96..ef44499ce5926 100644
--- a/clang/include/clang/AST/UnresolvedSet.h
+++ b/clang/include/clang/AST/UnresolvedSet.h
@@ -97,6 +97,10 @@ class UnresolvedSetImpl {
     decls().push_back(DeclAccessPair::make(D, AS));
   }
 
+  void addAllDecls(ArrayRef<DeclAccessPair> Other) {
+    append(iterator(Other.begin()), iterator(Other.end()));
+  }
+
   /// Replaces the given declaration with the new one, once.
   ///
   /// \return true if the set changed
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 12aab09f28556..0bd2e35bf2e31 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -895,9 +895,7 @@ def missing_template_arg_list_after_template_kw : Extension<
   "keyword">, InGroup<DiagGroup<"missing-template-arg-list-after-template-kw">>,
   DefaultError;
 
-def err_missing_dependent_template_keyword : Error<
-  "use 'template' keyword to treat '%0' as a dependent template name">;
-def warn_missing_dependent_template_keyword : ExtWarn<
+def ext_missing_dependent_template_keyword : ExtWarn<
   "use 'template' keyword to treat '%0' as a dependent template name">;
 
 def ext_extern_template : Extension<
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 6880fa4bb0b03..b4f5270a35956 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -3368,15 +3368,11 @@ class Parser : public CodeCompletionHandler {
   BaseResult ParseBaseSpecifier(Decl *ClassDecl);
   AccessSpecifier getAccessSpecifierIfPresent() const;
 
-  bool ParseUnqualifiedIdTemplateId(CXXScopeSpec &SS,
-                                    ParsedType ObjectType,
-                                    bool ObjectHadErrors,
-                                    SourceLocation TemplateKWLoc,
-                                    IdentifierInfo *Name,
-                                    SourceLocation NameLoc,
-                                    bool EnteringContext,
-                                    UnqualifiedId &Id,
-                                    bool AssumeTemplateId);
+  bool ParseUnqualifiedIdTemplateId(
+      CXXScopeSpec &SS, ParsedType ObjectType, bool ObjectHadErrors,
+      SourceLocation TemplateKWLoc, SourceLocation TildeLoc,
+      IdentifierInfo *Name, SourceLocation NameLoc, bool EnteringContext,
+      UnqualifiedId &Id, bool AssumeTemplateId);
   bool ParseUnqualifiedIdOperator(CXXScopeSpec &SS, bool EnteringContext,
                                   ParsedType ObjectType,
                                   UnqualifiedId &Result);
diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h
index 425b6e2a0b30c..9c22c35535ede 100644
--- a/clang/include/clang/Sema/DeclSpec.h
+++ b/clang/include/clang/Sema/DeclSpec.h
@@ -75,6 +75,7 @@ class CXXScopeSpec {
   SourceRange Range;
   NestedNameSpecifierLocBuilder Builder;
   ArrayRef<TemplateParameterList *> TemplateParamLists;
+  ArrayRef<DeclAccessPair> UnqualifiedLookups;
 
 public:
   SourceRange getRange() const { return Range; }
@@ -91,6 +92,13 @@ class CXXScopeSpec {
     return TemplateParamLists;
   }
 
+  void setUnqualifiedLookups(ArrayRef<DeclAccessPair> Found) {
+    UnqualifiedLookups = Found;
+  }
+  ArrayRef<DeclAccessPair> getUnqualifiedLookups() const {
+    return UnqualifiedLookups;
+  }
+
   /// Retrieve the representation of the nested-name-specifier.
   NestedNameSpecifier *getScopeRep() const {
     return Builder.getRepresentation();
diff --git a/clang/include/clang/Sema/Lookup.h b/clang/include/clang/Sema/Lookup.h
index b0a08a05ac6a0..6b765ef3c980f 100644
--- a/clang/include/clang/Sema/Lookup.h
+++ b/clang/include/clang/Sema/Lookup.h
@@ -483,11 +483,15 @@ class LookupResult {
     ResultKind = Found;
   }
 
+  void addAllDecls(ArrayRef<DeclAccessPair> Other) {
+    Decls.addAllDecls(Other);
+    ResultKind = Found;
+  }
+
   /// Add all the declarations from another set of lookup
   /// results.
   void addAllDecls(const LookupResult &Other) {
-    Decls.append(Other.Decls.begin(), Other.Decls.end());
-    ResultKind = Found;
+    addAllDecls(Other.Decls.pairs());
   }
 
   /// Determine whether no result was found because we could not
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 57994f4033922..6be6f6725e5b7 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2802,7 +2802,8 @@ class Sema final : public SemaBase {
   /// (e.g., Base::), perform name lookup for that identifier as a
   /// nested-name-specifier within the given scope, and return the result of
   /// that name lookup.
-  NamedDecl *FindFirstQualifierInScope(Scope *S, NestedNameSpecifier *NNS);
+  bool LookupFirstQualifierInScope(Scope *S, NestedNameSpecifier *NNS,
+                                   UnresolvedSetImpl &R);
 
   /// Keeps information about an identifier in a nested-name-spec.
   ///
@@ -2842,9 +2843,6 @@ class Sema final : public SemaBase {
   /// \param EnteringContext If true, enter the context specified by the
   ///        nested-name-specifier.
   /// \param SS Optional nested name specifier preceding the identifier.
-  /// \param ScopeLookupResult Provides the result of name lookup within the
-  ///        scope of the nested-name-specifier that was computed at template
-  ///        definition time.
   /// \param ErrorRecoveryLookup Specifies if the method is called to improve
   ///        error recovery and what kind of recovery is performed.
   /// \param IsCorrectedToColon If not null, suggestion of replace '::' -> ':'
@@ -2853,11 +2851,6 @@ class Sema final : public SemaBase {
   ///        not '::'.
   /// \param OnlyNamespace If true, only considers namespaces in lookup.
   ///
-  /// This routine differs only slightly from ActOnCXXNestedNameSpecifier, in
-  /// that it contains an extra parameter \p ScopeLookupResult, which provides
-  /// the result of name lookup within the scope of the nested-name-specifier
-  /// that was computed at template definition time.
-  ///
   /// If ErrorRecoveryLookup is true, then this call is used to improve error
   /// recovery.  This means that it should not emit diagnostics, it should
   /// just return true on failure.  It also means it should only return a valid
@@ -2866,7 +2859,6 @@ class Sema final : public SemaBase {
   /// specifier.
   bool BuildCXXNestedNameSpecifier(Scope *S, NestedNameSpecInfo &IdInfo,
                                    bool EnteringContext, CXXScopeSpec &SS,
-                                   NamedDecl *ScopeLookupResult,
                                    bool ErrorRecoveryLookup,
                                    bool *IsCorrectedToColon = nullptr,
                                    bool OnlyNamespace = false);
@@ -8566,11 +8558,12 @@ class Sema final : public SemaBase {
                           const TemplateArgumentListInfo *TemplateArgs,
                           bool IsDefiniteInstance, const Scope *S);
 
-  ExprResult ActOnDependentMemberExpr(
-      Expr *Base, QualType BaseType, bool IsArrow, SourceLocation OpLoc,
-      const CXXScopeSpec &SS, SourceLocation TemplateKWLoc,
-      NamedDecl *FirstQualifierInScope, const DeclarationNameInfo &NameInfo,
-      const TemplateArgumentListInfo *TemplateArgs);
+  ExprResult
+  ActOnDependentMemberExpr(Expr *Base, QualType BaseType, bool IsArrow,
+                           SourceLocation OpLoc, const CXXScopeSpec &SS,
+                           SourceLocation TemplateKWLoc,
+                           const DeclarationNameInfo &NameInfo,
+                           const TemplateArgumentListInfo *TemplateArgs);
 
   /// The main callback when the parser finds something like
   ///   expression . [nested-name-specifier] identifier
@@ -8626,15 +8619,14 @@ class Sema final : public SemaBase {
   ExprResult BuildMemberReferenceExpr(
       Expr *Base, QualType BaseType, SourceLocation OpLoc, bool IsArrow,
       CXXScopeSpec &SS, SourceLocation TemplateKWLoc,
-      NamedDecl *FirstQualifierInScope, const DeclarationNameInfo &NameInfo,
+      const DeclarationNameInfo &NameInfo,
       const TemplateArgumentListInfo *TemplateArgs, const Scope *S,
       ActOnMemberAccessExtraArgs *ExtraArgs = nullptr);
 
   ExprResult
   BuildMemberReferenceExpr(Expr *Base, QualType BaseType, SourceLocation OpLoc,
                            bool IsArrow, const CXXScopeSpec &SS,
-                           SourceLocation TemplateKWLoc,
-                           NamedDecl *FirstQualifierInScope, LookupResult &R,
+                           SourceLocation TemplateKWLoc, LookupResult &R,
                            const TemplateArgumentListInfo *TemplateArgs,
                            const Scope *S, bool SuppressQualifierCheck = false,
                            ActOnMemberAccessExtraArgs *ExtraArgs = nullptr);
@@ -11122,15 +11114,14 @@ class Sema final : public SemaBase {
                      QualType ObjectType, bool EnteringContext,
                      RequiredTemplateKind RequiredTemplate = SourceLocation(),
                      AssumedTemplateKind *ATK = nullptr,
-                     bool AllowTypoCorrection = true);
+                     bool AllowTypoCorrection = true, bool MayBeNNS = false);
 
-  TemplateNameKind isTemplateName(Scope *S, CXXScopeSpec &SS,
-                                  bool hasTemplateKeyword,
-                                  const UnqualifiedId &Name,
-                                  ParsedType ObjectType, bool EnteringContext,
-                                  TemplateTy &Template,
-                                  bool &MemberOfUnknownSpecialization,
-                                  bool Disambiguation = false);
+  TemplateNameKind
+  isTemplateName(Scope *S, CXXScopeSpec &SS, bool hasTemplateKeyword,
+                 const UnqualifiedId &Name, ParsedType ObjectType,
+                 bool EnteringContext, TemplateTy &Template,
+                 bool &MemberOfUnknownSpecialization,
+                 bool Disambiguation = false, bool MayBeNNS = false);
 
   /// Try to resolve an undeclared template name as a type template.
   ///
@@ -11459,12 +11450,11 @@ class Sema final : public SemaBase {
   /// For example, given "x.MetaFun::template apply", the scope specifier
   /// \p SS will be "MetaFun::", \p TemplateKWLoc contains the location
   /// of the "template" keyword, and "apply" is the \p Name.
-  TemplateNameKind ActOnTemplateName(Scope *S, CXXScopeSpec &SS,
-                                     SourceLocation TemplateKWLoc,
-                                     const UnqualifiedId &Name,
-                                     ParsedType ObjectType,
-                                     bool EnteringContext, TemplateTy &Template,
-                                     bool AllowInjectedClassName = false);
+  TemplateNameKind
+  ActOnTemplateName(Scope *S, CXXScopeSpec &SS, SourceLocation TemplateKWLoc,
+                    const UnqualifiedId &Name, ParsedType ObjectType,
+                    bool EnteringContext, TemplateTy &Temp...
[truncated]

Copy link
Contributor

@Endilll Endilll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes to DR tests look good.

@sdkrystian
Copy link
Member Author

@chapuni This fixes the Ubuntu-20.04 g++ build issues you brought up in #98252.

@sdkrystian sdkrystian requested a review from cor3ntin July 11, 2024 21:52
Copy link
Contributor

@chapuni chapuni left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've confirmed it's fine. Thanks!

@sdkrystian sdkrystian merged commit ce4aada into llvm:main Jul 11, 2024
48 of 59 checks passed
@llvm-ci
Copy link
Collaborator

llvm-ci commented Jul 11, 2024

LLVM Buildbot has detected a new failure on builder openmp-offload-libc-amdgpu-runtime running on omp-vega20-1 while building clang,libcxx,llvm at step 10 "Add check check-offload".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/73/builds/1674

Here is the relevant piece of the build log for the reference:

Step 10 (Add check check-offload) failure: 1200 seconds without output running [b'ninja', b'-j 32', b'check-offload'], attempting to kill
...
PASS: libomptarget :: x86_64-pc-linux-gnu-LTO :: offloading/test_libc.cpp (776 of 786)
PASS: libomptarget :: x86_64-pc-linux-gnu-LTO :: offloading/bug49779.cpp (777 of 786)
PASS: libomptarget :: x86_64-pc-linux-gnu-LTO :: offloading/bug50022.cpp (778 of 786)
PASS: libomptarget :: x86_64-pc-linux-gnu-LTO :: offloading/bug53727.cpp (779 of 786)
PASS: libomptarget :: x86_64-pc-linux-gnu-LTO :: offloading/wtime.c (780 of 786)
PASS: libomptarget :: x86_64-pc-linux-gnu :: offloading/bug49021.cpp (781 of 786)
PASS: libomptarget :: x86_64-pc-linux-gnu :: offloading/std_complex_arithmetic.cpp (782 of 786)
PASS: libomptarget :: x86_64-pc-linux-gnu-LTO :: offloading/complex_reduction.cpp (783 of 786)
PASS: libomptarget :: x86_64-pc-linux-gnu-LTO :: offloading/std_complex_arithmetic.cpp (784 of 786)
PASS: libomptarget :: x86_64-pc-linux-gnu-LTO :: offloading/bug49021.cpp (785 of 786)
command timed out: 1200 seconds without output running [b'ninja', b'-j 32', b'check-offload'], attempting to kill
process killed by signal 9
program finished with exit code -1
elapsedTime=1228.515257

@glandium
Copy link
Contributor

glandium commented Jul 12, 2024

This broke building abseil-cpp (cd75cb4ae32c46c84cef9a9c78b42174f22ed0ac as of writing).

STR:

In file included from /tmp/abseil-cpp/absl/status/status.cc:14:
In file included from /tmp/abseil-cpp/absl/status/status.h:66:
In file included from /tmp/abseil-cpp/absl/status/internal/status_internal.h:26:
In file included from /tmp/abseil-cpp/absl/container/inlined_vector.h:53:
In file included from /tmp/abseil-cpp/absl/container/internal/inlined_vector.h:32:
/tmp/abseil-cpp/absl/container/internal/compressed_tuple.h:251:42: error: no member named 'get' in the global namespace
  251 |     return std::move(*this).StorageT<I>::get();
      |                                        ~~^
/tmp/abseil-cpp/absl/container/internal/compressed_tuple.h:256:42: error: no member named 'get' in the global namespace
  256 |     return std::move(*this).StorageT<I>::get();
      |                                        ~~^
2 errors generated.

@glandium
Copy link
Contributor

Well, I guess this is working as intended, as adding template before StorageT fixes it.

@sdkrystian
Copy link
Member Author

@glandium Yes, clang considers std::move(*this) to be dependent so template is required there.

@glandium
Copy link
Contributor

The sad part is that MSVC and some old versions of GCC don't like template being added here :(

@pranavk
Copy link
Contributor

pranavk commented Jul 12, 2024

Could we perhaps put this behind a flag to help with the transition?

Some cases can already be silenced with -Wno-missing-dependent-template-keyword but cases like above in absl are hard errors and there are no workarounds.

@sdkrystian
Copy link
Member Author

sdkrystian commented Jul 13, 2024

@pranavk see #98613.

edit: sorry, the error recovery that is currently performed will not detect cases where the template argument list does not contain a a type. i can look into this monday.

@lukel97
Copy link
Contributor

lukel97 commented Jul 15, 2024

I'm also seeing a hard "missing 'template' keyword prior to dependent template name" error when building the 510.parest_r benchmark from SPEC CPU 2017

@cor3ntin
Copy link
Contributor

@opensdd You might want to know that CWG1835 is causing some disruption in the wild

I wonder if we should consider deploying that change NOT as a DR such that it would only affect C++23+ conformance,
that might alleviate some of the pain points.

@sdkrystian
Copy link
Member Author

sdkrystian commented Jul 15, 2024

@opensdd You might want to know that CWG1835 is causing some disruption in the wild

*@opensdh

@opensdh
Copy link

opensdh commented Jul 15, 2024

You might want to know that CWG1835 is causing some disruption in the wild

I believe it, although the alternative (having to write (it->end) < it->end in the issue's example) seems like it would be just as user-hostile.

I wonder if we should consider deploying that change NOT as a DR such that it would only affect C++23+ conformance, that might alleviate some of the pain points.

If you think that would create fewer problems (like skew between libraries and their clients) than it solves, I'm not opposed to bringing that up in CWG.

@sdkrystian
Copy link
Member Author

sdkrystian commented Jul 15, 2024

I think the next reland needs to cover the non-type template case, so that we have a way to suppress the warning for all cases (this gives developer time to do the cleanup transition).

@hokein The problem with the non-type template case is that we would have to check the validity of constructs outside the potential template-argument-list in order to disambiguate. For example, w.x<y>::z may be a valid expression (interpreted as ((w.x) < y) > ::z) if the type of w is a class type that declares a member x, ::z exists, and both < and > comparisons are valid.

I wonder if we should consider deploying that change NOT as a DR such that it would only affect C++23+ conformance, that might alleviate some of the pain points.

@cor3ntin Yes, this is what I was thinking and is the resolution I prefer. Alternatively we could have a flag to disable the new behavior (or a mix of both, e.g. enable the new behavior via a flag which defaults to ON in C++23+).

@AaronBallman
Copy link
Collaborator

Given the amount of pain involved, I think this should be applied as a C++23 change rather than applied as a DR. I'd like to avoid adding a feature flag for this if possible (I'd prefer warning-default-as-error instead, but that doesn't seem particularly plausible here), but the cross-compiler behavior story may be sufficiently painful to make it worth considering.

@cor3ntin
Copy link
Contributor

I sent an email to core. There seems to be some support to reconsider as gcc is having the same issue we are.

I propose the following:

  • Do nothing for clang 19
  • wait to see what wg21 decides to do - hopefully they come up with something workable in the coming months
  • If the status quo of the standard remains, only apply the change in C++23 mode and try to improve recovery and diagnostics to help with transition.

Hopefully this works for everyone.
Sorry @sdkrystian , I know a lot of work went into this.

@sdkrystian
Copy link
Member Author

@cor3ntin Well, if the alternative is not implementing the resolution at all I'll look into the feasibility of identifying missing template keywords in the non-type template parameter case. If I can get that working, I think it's pretty safe to reland this patch.

Also, I replied to your post on the core reflector. I find it rather unlikely that CWG will implement a mitigation mechanism as doing so would effectively revert the resolution to CWG1835.

@sdkrystian
Copy link
Member Author

I have a new branch ready where:

  • We only apply the resolution to CWG1835 in C++23 and later, and
  • We issue a diagnostic when its application results in a missing template keyword that breaks whatever construct that follows the intended template name. e.g.
template<int I>
struct A {
  int x;
};

template<int I>
struct B : A<I> {
  void f() {
    this->A<I>::f(); // with -std=c++20 and earlier: no error
                     // with -std=c++23 and later: 
                     //     error: no member named 'f' in the global namespace
                     //     error: missing 'template' keyword prior to dependent template name 'A'
  }
};

WDYAT?

@mizvekov
Copy link
Contributor

WDYAT?

We generally try to avoid issuing more than one error diagnostic for the same problem.

Couldn't we instead issue a note note: maybe missing 'template' keyword prior to dependent template name 'A'
attached to the first error instead?

@mizvekov
Copy link
Contributor

Another option would be to apply the resolution to all modes, but recover and issue a warning instead of an error.
How feasible would that be?

@sdkrystian
Copy link
Member Author

Another option would be to apply the resolution to all modes, but recover and issue a warning instead of an error. How feasible would that be?

@mizvekov Without unannotated tentative parsing (to be properly introduced by #96364), issuing a warning is infeasible. Either way, by the time we detect the error we could be who knows how deep into the second operand of a > operator.

@mizvekov
Copy link
Contributor

@mizvekov Without unannotated tentative parsing (to be properly introduced by #96364), issuing a warning is infeasible. Either way, by the time we detect the error we could be who knows how deep into the second operand of a > operator.

So when we are parsing this->A.
Suppose what follows it looks like a template argument list.
Then we see if this->template A would have found a template.
if it does find the template A, we issue the warning and proceed as if the user had written this->template A

Do you think that's workable?

@sdkrystian
Copy link
Member Author

sdkrystian commented Jul 20, 2024

@mizvekov Without unannotated tentative parsing (to be properly introduced by #96364), issuing a warning is infeasible. Either way, by the time we detect the error we could be who knows how deep into the second operand of a > operator.

So when we are parsing this->A. Suppose what follows it looks like a template argument list. Then we see if this->template A would have found a template. if it does find the template A, we issue the warning and proceed as if the user had written this->template A

Do you think that's workable?

We already do that :) the problem is when what follows A doesn't unambiguously look like a template argument list:

int x = 0;

template<int I>
struct f { };

template<typename T>
void g(T t)
{
    t.f<0>::x; // could be interpreted as '((t.f) < 0) > (::x)'
}

@mizvekov
Copy link
Contributor

We already do that :) the problem is when what follows A doesn't unambiguously look like a template argument list:

Sure, but before CWG1835, we would perform the lookup anyway and treat the <0> as a template argument list, even if it isn't unambiguously a template argument list.

So we could try to do this just as well if A doesn't unambiguously look like a template argument list.

That seems better than not trying to apply the new rule at all.

@KyunLFA
Copy link

KyunLFA commented Jul 21, 2024

I'm also seeing a hard "missing 'template' keyword prior to dependent template name" error when building the 510.parest_r benchmark from SPEC CPU 2017

Hitting this same error compiling the ares emulator on 19.

@nico
Copy link
Contributor

nico commented Jul 21, 2024

Independent of the other issues, it looks like this also re-introduces #98878

@sdkrystian
Copy link
Member Author

I'm also seeing a hard "missing 'template' keyword prior to dependent template name" error when building the 510.parest_r benchmark from SPEC CPU 2017

Hitting this same error compiling the ares emulator on 19.

Could either of you provide an example @KyunLFA @lukel97 ?

@sdkrystian
Copy link
Member Author

sdkrystian commented Jul 22, 2024

Independent of the other issues, it looks like this also re-introduces #98878

@nico #98878 still occurs without this patch applied:

struct A 
{
    void f();
};

template<typename T>
void g(T t)
{
    using U = A; // clang 18.1: 'warning: unused type alias 'U''
    t.U::f();
}

I can address this in the next application of this patch

@sdkrystian
Copy link
Member Author

We already do that :) the problem is when what follows A doesn't unambiguously look like a template argument list:

Sure, but before CWG1835, we would perform the lookup anyway and treat the <0> as a template argument list, even if it isn't unambiguously a template argument list.

So we could try to do this just as well if A doesn't unambiguously look like a template argument list.

That seems better than not trying to apply the new rule at all.

I'm not sure I follow. If we "treat the <0> as a template argument list, even if it isn't unambiguously a template argument list.", then aren't applying CWG1835 at all.

@mizvekov
Copy link
Contributor

I'm not sure I follow. If we "treat the <0> as a template argument list, even if it isn't unambiguously a template argument list.", then aren't applying CWG1835 at all.

Yes, in that case. But I am arguing for selectively applying it. I thought that could work, but I am not sure, that's what I am asking.

@sdkrystian
Copy link
Member Author

sdkrystian commented Jul 22, 2024

@mizvekov Consider a slightly altered version of the motivating example in the DR:

template<int I> void end();

template<typename T>
bool Foo(T it0, T it1) 
{
    return it0->end < it1->end || it0->end > it1->end;
}

< it1->end || it0->end > looks like a template-argument-list, but the alternate interpretation ((it0->end < it1->end) || (it0->end > it1->end)) is valid and reasonable.

@sdkrystian
Copy link
Member Author

Ping @cor3ntin @AaronBallman.

@KyunLFA
Copy link

KyunLFA commented Jul 23, 2024

@sdkrystian Sure do.

The offending code lives at https://github.com/ares-emulator/ares/blob/master/nall/shared-pointer.hpp

The error reads:

../nall/shared-pointer.hpp:65:5: error: missing 'template' keyword prior to dependent template name 'operator='
   65 |     operator=<U>(source);
      |     ^        ~~~
../nall/shared-pointer.hpp:70:5: error: missing 'template' keyword prior to dependent template name 'operator='
   70 |     operator=<U>(std::move(source));
      |     ^        ~~~
../nall/shared-pointer.hpp:75:5: error: missing 'template' keyword prior to dependent template name 'operator='
   75 |     operator=<U>(source);
      |     ^        ~~~
3 errors generated.

The offending code block:

template<typename T>
struct shared_pointer {
  template<typename... P> static auto create(P&&... p) {
    return shared_pointer<T>{new T(std::forward<P>(p)...)};
  }

  using type = T;
  shared_pointer_manager* manager = nullptr;

  template<typename U>
  struct is_compatible {
    static constexpr bool value = is_base_of<T, U>::value || is_base_of<U, T>::value;
  };

  shared_pointer() {
  }

  shared_pointer(T* source) {
    operator=(source);
  }

  shared_pointer(T* source, const function<void (T*)>& deleter) {
    operator=(source);
    manager->deleter = function<void (void*)>([=](void* p) {
      deleter((T*)p);
    });
  }

  shared_pointer(const shared_pointer& source) {
    operator=(source);
  }

  shared_pointer(shared_pointer&& source) {
    operator=(std::move(source));
  }

  template<typename U, typename = enable_if_t<is_compatible<U>::value>>
  shared_pointer(const shared_pointer<U>& source) {
    operator=<U>(source);
  }

  template<typename U, typename = enable_if_t<is_compatible<U>::value>>
  shared_pointer(shared_pointer<U>&& source) {
    operator=<U>(std::move(source));
  }

  template<typename U, typename = enable_if_t<is_compatible<U>::value>>
  shared_pointer(const shared_pointer_weak<U>& source) {
    operator=<U>(source);
  }

  template<typename U, typename = enable_if_t<is_compatible<U>::value>>
  shared_pointer(const shared_pointer<U>& source, T* pointer) {
    if((bool)source && (T*)source.manager->pointer == pointer) {
      manager = source.manager;
      manager->strong++;
    }
  }

  ~shared_pointer() {
    reset();
  }

  auto operator=(T* source) -> shared_pointer& {
    reset();
    if(source) {
      manager = new shared_pointer_manager((void*)source);
      manager->strong++;
      if constexpr(is_base_of_v<shared_pointer_this_base, T>) {
        source->weak = *this;
      }
    }
    return *this;
  }

  auto operator=(const shared_pointer& source) -> shared_pointer& {
    if(this != &source) {
      reset();
      if((bool)source) {
        manager = source.manager;
        manager->strong++;
      }
    }
    return *this;
  }

  auto operator=(shared_pointer&& source) -> shared_pointer& {
    if(this != &source) {
      reset();
      manager = source.manager;
      source.manager = nullptr;
    }
    return *this;
  }

  template<typename U, typename = enable_if_t<is_compatible<U>::value>>
  auto operator=(const shared_pointer<U>& source) -> shared_pointer& {
    if((uintptr)this != (uintptr)&source) {
      reset();
      if((bool)source) {
        manager = source.manager;
        manager->strong++;
      }
    }
    return *this;
  }

  template<typename U, typename = enable_if_t<is_compatible<U>::value>>
  auto operator=(shared_pointer&& source) -> shared_pointer& {
    if((uintptr)this != (uintptr)&source) {
      reset();
      manager = source.manager;
      source.manager = nullptr;
    }
    return *this;
  }

  template<typename U, typename = enable_if_t<is_compatible<U>::value>>
  auto operator=(const shared_pointer_weak<U>& source) -> shared_pointer& {
    reset();
    if((bool)source) {
      manager = source.manager;
      manager->strong++;
    }
    return *this;
  }

  auto data() -> T* {
    if(manager) return (T*)manager->pointer;
    return nullptr;
  }

  auto data() const -> const T* {
    if(manager) return (T*)manager->pointer;
    return nullptr;
  }

  auto operator->() -> T* { return data(); }
  auto operator->() const -> const T* { return data(); }

  auto operator*() -> T& { return *data(); }
  auto operator*() const -> const T& { return *data(); }

  auto operator()() -> T& { return *data(); }
  auto operator()() const -> const T& { return *data(); }

  template<typename U>
  auto operator==(const shared_pointer<U>& source) const -> bool {
    return manager == source.manager;
  }

  template<typename U>
  auto operator!=(const shared_pointer<U>& source) const -> bool {
    return manager != source.manager;
  }

  explicit operator bool() const {
    return manager && manager->strong;
  }

  auto unique() const -> bool {
    return manager && manager->strong == 1;
  }

  auto references() const -> u32 {
    return manager ? manager->strong : 0;
  }

  auto reset() -> void {
    if(manager && manager->strong) {
      //pointer may contain weak references; if strong==0 it may destroy manager
      //as such, we must destroy strong before decrementing it to zero
      if(manager->strong == 1) {
        if(manager->deleter) {
          manager->deleter(manager->pointer);
        } else {
          delete (T*)manager->pointer;
        }
        manager->pointer = nullptr;
      }
      if(--manager->strong == 0) {
        if(manager->weak == 0) {
          delete manager;
        }
      }
    }
    manager = nullptr;
  }

  template<typename U>
  auto cast() -> shared_pointer<U> {
    if(auto pointer = dynamic_cast<U*>(data())) {
      return {*this, pointer};
    }
    return {};
  }
};

Errors on 19, does not error on 18.1.8.

@sdkrystian
Copy link
Member Author

sdkrystian commented Jul 23, 2024

@KyunLFA Could you send the commit hash of the clang build which you're using? I'm unable to reproduce the errors.

@KyunLFA
Copy link

KyunLFA commented Jul 23, 2024

@KyunLFA Could you send the commit hash of the clang build which you're using? I'm unable to reproduce the errors.

Sure, it's 4321300 .

Also, just to make sure, have you tried building the whole ares emulator project with make on the root dir of https://github.com/ares-emulator/ares.git ?

@KyunLFA
Copy link

KyunLFA commented Jul 24, 2024

No longer errors for me...

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:modules C++20 modules and Clang Header Modules clang Clang issues not falling into any other category coroutines C++20 coroutines libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. llvm:adt
Projects
None yet
Development

Successfully merging this pull request may close these issues.