[clang] [Clang] Implement resolution for CWG1835 (PR #92957)

Krystian Stasiowski via cfe-commits cfe-commits at lists.llvm.org
Tue May 21 12:44:02 PDT 2024


https://github.com/sdkrystian created https://github.com/llvm/llvm-project/pull/92957

[CWG1835](https://cplusplus.github.io/CWG/issues/1835.html) was one of the many core issues resolved by [P1787R6: Declarations and where to find them](http://wg21.link/p1787r6). Its resolution changes how [member-qualified names](http://eel.is/c++draft/basic.lookup.qual.general#2) are looked up. This patch is a draft implementation of that resolution.

Previously, an _identifier_ following `.` or `->` would be first looked up in the type of the object expression (i.e. qualified lookup), and then in the context of the _postfix-expression_ (i.e. unqualified lookup) if nothing was found; the result of the second lookup was required to name a class template. Notably, this second lookup would occur even when the object expression was dependent, and its result would be used to determine whether a `<` token is the start of a _template-argument_list_. 

The new wording in [[basic.lookup.qual.general] p2](eel.is/c++draft/basic.lookup.qual.general#2) states:
> A member-qualified name is the (unique) component name, if any, of
> - an _unqualified-id_ or
> - a _nested-name-specifier_ of the form _`type-name ::`_ or _`namespace-name ::`​_
>
> in the id-expression of a class member access expression. A ***qualified name*** is
> - a member-qualified name or
> - the terminal name of
>     - a _qualified-id_,
>     - a _using-declarator_,
>     - a _typename-specifier_,
>     - a _qualified-namespace-specifier_, or
>     - a _nested-name-specifier_, _elaborated-type-specifier_, or _class-or-decltype_ that has a _nested-name-specifier_.
>
>  The _lookup context_ of a member-qualified name is the type of its associated object expression (considered dependent if the object expression is type-dependent). The lookup context of any other qualified name is the type, template, or namespace nominated by the preceding _nested-name-specifier_.

And [[basic.lookup.qual.general] p3](eel.is/c++draft/basic.lookup.qual.general#3) now states:
> _Qualified name lookup_ in a class, namespace, or enumeration performs a search of the scope associated with it except as specified below. Unless otherwise specified, a qualified name undergoes qualified name lookup in its lookup context from the point where it appears unless the lookup context either is dependent and is not the current instantiation or is not a class or class template. If nothing is found by qualified lookup for a member-qualified name that is the terminal name of a _nested-name-specifier_ and is not dependent, it undergoes unqualified lookup.

In non-standardese terms, these two paragraphs essentially state the following:
- A name that immediately follows `.` or `->` in a class member access expression is a member-qualified name
- A member-qualified name will be first looked up in the type of the object expression `T` unless `T` is a dependent type that is _not_ the current instantiation, e.g.
```cpp
template<typename T>
struct A
{
    void f(T* t)
    {
        this->x; // type of the object expression is 'A<T>'. although 'A<T>' is dependent, it is the
                 // current instantiation so we look up 'x' in the template definition context.
        
        t->y; // type of the object expression is 'T' ('->' is transformed to '.' per [expr.ref]). 
              // 'T' is dependent and is *not* the current instantiation, so we lookup 'y' in the 
              // template instantiation context.
    }
};
```
- If the first lookup finds nothing and:
    - the member-qualified name is the first component of a _nested-name-specifier_ (which could be an _identifier_ or a _simple-template-id_), and either:
        - the type of the object expression is the current instantiation and it has no dependent base classes, or
        - the type of the object expression is not dependent

  then we lookup the name again, this time via unqualified lookup.

Although the second (unqualified) lookup is stated not to occur when the member-qualified name is dependent, a dependent name will _not_ be dependent once the template is instantiated, so the second lookup must "occur" during instantiation if qualified lookup does not find anything. This means that we must perform the second (unqualified) lookup during parsing even when the type of the object expression is dependent, but those results are _not_ used to determine whether a `<` token is the start of a _template-argument_list_; they are stored so we can replicate the second lookup during instantiation. 

In even simpler terms (paraphrasing the [meeting minutes from the review of P1787](https://wiki.edg.com/bin/view/Wg21summer2020/P1787%28Lookup%29Review2020-06-15Through2020-06-18)):
- Unqualified lookup always happens for the first name in a _nested-name-specifier_ that follows `.` or `->`
- The result of that lookup is only used to determine whether `<` is the start of a _template-argument-list_ if the first (qualified) lookup found nothing and the lookup context:
    - is not dependent, or 
    - is the current instantiation and has no dependent base classes.

An example:
```cpp
struct A 
{
     void f();
};

template<typename T>
using B = A;

template<typename T>
struct C : A
{
    template<typename U>
    void g();

    void h(T* t)
    {
        this->g<int>(); // ok, '<' is the start of a template-argument-list ('g' was found via qualified lookup in the current instantiation)
        this->B<void>::f(); // ok, '<' is the start of a template-argument-list (current instantiation has no dependent bases, 'B' was found via unqualified lookup)
        t->g<int>(); // error: '<' means less than (unqualified lookup does not occur for a member-qualified name that isn't the first component of a nested-name-specifier)
        t->B<void>::f(); // error: '<' means less than (unqualified lookup does not occur if the name is dependent)
        t->template B<void>::f(); // ok: '<' is the start of a template-argument-list ('template' keyword used)
    }
};
```

Some additional notes:
- Per [[basic.lookup.qual.general] p1](http://eel.is/c++draft/basic.lookup.qual.general#1), lookup for a member-qualified name is type-only if it's an _identifier_ followed by `::`; lookup for the component name of a _simple-template-id_ followed by `::` is _not_ type-only. 
- The wording which specifies when the second unqualified lookup occurs appears to be paradoxical. We are supposed to do it only for the first component name of a _nested-name-specifier_ that follows `.` or `->` when qualified lookup finds nothing. However, when that name is followed by `<` (potentially starting a _simple-template-id_) we don't _know_ whether it will be the start of a _nested-name-specifier_ until we do the lookup -- but we aren't supposed to do the lookup until we know it's part of a _nested-name-specifier_! ***However***, since we only do the second lookup when the first lookup finds nothing (and the name isn't dependent), ***and*** since neither lookup is type-only, the only valid option is for the name to be the _template-name_ in a _simple-template-id_ that is followed by `::` (it can't be an _unqualified-id_ naming a member because we already determined that the lookup context doesn't have a member with that name). Thus, we can lock into the _nested-name-specifier_ interpretation and do the second lookup without having to know whether the _simple-template-id_ will be followed by `::` yet.


>From 4524db5ae7f3133b51328fbabd1f6188d7d3707b Mon Sep 17 00:00:00 2001
From: Krystian Stasiowski <sdkrystian at gmail.com>
Date: Tue, 21 May 2024 13:15:24 -0400
Subject: [PATCH] [WIP][Clang] Implement resolution for CWG1835

---
 clang/include/clang/Sema/DeclSpec.h           |  8 +++
 clang/include/clang/Sema/Sema.h               |  2 +-
 clang/lib/Parse/ParseDeclCXX.cpp              |  2 +-
 clang/lib/Parse/ParseExpr.cpp                 |  9 +--
 clang/lib/Sema/SemaCXXScopeSpec.cpp           | 60 +++++++++++++------
 clang/lib/Sema/SemaExprMember.cpp             | 10 +---
 clang/lib/Sema/SemaPseudoObject.cpp           | 16 ++---
 clang/lib/Sema/SemaTemplate.cpp               | 22 ++++---
 clang/lib/Sema/TreeTransform.h                | 15 +++++
 .../basic.lookup.classref/p1-cxx11.cpp        | 16 +++--
 .../basic.lookup/basic.lookup.classref/p1.cpp | 16 +++--
 .../class.derived/class.member.lookup/p8.cpp  |  4 +-
 clang/test/CXX/drs/cwg1xx.cpp                 |  9 +--
 clang/test/SemaCXX/static-assert-cxx17.cpp    |  2 +-
 .../SemaTemplate/temp_arg_nontype_cxx20.cpp   |  2 +-
 15 files changed, 128 insertions(+), 65 deletions(-)

diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h
index 23bc780e04979..c6d87ca1683a8 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;
+  NamedDecl *FoundFirstQualifierInScope;
 
 public:
   SourceRange getRange() const { return Range; }
@@ -91,6 +92,13 @@ class CXXScopeSpec {
     return TemplateParamLists;
   }
 
+  void setFoundFirstQualifierInScope(NamedDecl *Found) {
+    FoundFirstQualifierInScope = Found;
+  }
+  NamedDecl *getFirstQualifierFoundInScope() const {
+    return FoundFirstQualifierInScope;
+  }
+
   /// Retrieve the representation of the nested-name-specifier.
   NestedNameSpecifier *getScopeRep() const {
     return Builder.getRepresentation();
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 5894239664c15..805d36fd10544 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -6891,7 +6891,7 @@ class Sema final : public SemaBase {
       const TemplateArgumentListInfo *TemplateArgs);
 
   ExprResult ActOnMemberAccessExpr(Scope *S, Expr *Base, SourceLocation OpLoc,
-                                   tok::TokenKind OpKind, CXXScopeSpec &SS,
+                                   bool IsArrow, CXXScopeSpec &SS,
                                    SourceLocation TemplateKWLoc,
                                    UnqualifiedId &Member, Decl *ObjCImpDecl);
 
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 5eaec2b621e6f..b2fa6da002f98 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -720,7 +720,7 @@ Parser::DeclGroupPtrTy Parser::ParseUsingDeclaration(
       return nullptr;
     }
     CXXScopeSpec SS;
-    if (ParseOptionalCXXScopeSpecifier(SS, /*ParsedType=*/nullptr,
+    if (ParseOptionalCXXScopeSpecifier(SS, /*ObjectType=*/nullptr,
                                        /*ObectHasErrors=*/false,
                                        /*EnteringConttext=*/false,
                                        /*MayBePseudoDestructor=*/nullptr,
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index eb7447fa038e4..b3c25f88b403d 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -2254,6 +2254,7 @@ Parser::ParsePostfixExpressionSuffix(ExprResult LHS) {
           }
           break;
         }
+
         ParseOptionalCXXScopeSpecifier(
             SS, ObjectType, LHS.get() && LHS.get()->containsErrors(),
             /*EnteringContext=*/false, &MayBePseudoDestructor);
@@ -2328,10 +2329,10 @@ Parser::ParsePostfixExpressionSuffix(ExprResult LHS) {
       }
 
       if (!LHS.isInvalid())
-        LHS = Actions.ActOnMemberAccessExpr(getCurScope(), LHS.get(), OpLoc,
-                                            OpKind, SS, TemplateKWLoc, Name,
-                                 CurParsedObjCImpl ? CurParsedObjCImpl->Dcl
-                                                   : nullptr);
+        LHS = Actions.ActOnMemberAccessExpr(
+            getCurScope(), LHS.get(), OpLoc, OpKind == tok::arrow, SS,
+            TemplateKWLoc, Name,
+            CurParsedObjCImpl ? CurParsedObjCImpl->Dcl : nullptr);
       if (!LHS.isInvalid()) {
         if (Tok.is(tok::less))
           checkPotentialAngleBracket(LHS);
diff --git a/clang/lib/Sema/SemaCXXScopeSpec.cpp b/clang/lib/Sema/SemaCXXScopeSpec.cpp
index c405fbc0aa421..6efa8925d1446 100644
--- a/clang/lib/Sema/SemaCXXScopeSpec.cpp
+++ b/clang/lib/Sema/SemaCXXScopeSpec.cpp
@@ -397,11 +397,19 @@ NamedDecl *Sema::FindFirstQualifierInScope(Scope *S, NestedNameSpecifier *NNS) {
   while (NNS->getPrefix())
     NNS = NNS->getPrefix();
 
-  if (NNS->getKind() != NestedNameSpecifier::Identifier)
-    return nullptr;
-
-  LookupResult Found(*this, NNS->getAsIdentifier(), SourceLocation(),
-                     LookupNestedNameSpecifierName);
+  const IdentifierInfo *II = NNS->getAsIdentifier();
+  if (!II) {
+    if (const auto *DTST =
+            dyn_cast_if_present<DependentTemplateSpecializationType>(
+                NNS->getAsType()))
+      II = DTST->getIdentifier();
+    else
+      return nullptr;
+  }
+  assert(II && "Missing first qualifier in scope");
+  LookupResult Found(*this, II, SourceLocation(),
+                     NNS->getAsIdentifier() ? LookupNestedNameSpecifierName
+                                            : LookupOrdinaryName);
   LookupName(Found, S);
   assert(!Found.isAmbiguous() && "Cannot handle ambiguities here yet");
 
@@ -409,10 +417,10 @@ NamedDecl *Sema::FindFirstQualifierInScope(Scope *S, NestedNameSpecifier *NNS) {
     return nullptr;
 
   NamedDecl *Result = Found.getFoundDecl();
-  if (isAcceptableNestedNameSpecifier(Result))
-    return Result;
+  // if (isAcceptableNestedNameSpecifier(Result))
+  return Result;
 
-  return nullptr;
+  // return nullptr;
 }
 
 namespace {
@@ -493,12 +501,15 @@ bool Sema::BuildCXXNestedNameSpecifier(Scope *S, NestedNameSpecInfo &IdInfo,
     // x->B::f, and we are looking into the type of the object.
     assert(!SS.isSet() && "ObjectType and scope specifier cannot coexist");
     LookupCtx = computeDeclContext(ObjectType);
-    isDependent = ObjectType->isDependentType();
-  } else if (SS.isSet()) {
+    isDependent = !LookupCtx && ObjectType->isDependentType();
+  } else if (SS.isNotEmpty()) {
     // This nested-name-specifier occurs after another nested-name-specifier,
     // so look into the context associated with the prior nested-name-specifier.
     LookupCtx = computeDeclContext(SS, EnteringContext);
-    isDependent = isDependentScopeSpecifier(SS);
+    isDependent = !LookupCtx && isDependentScopeSpecifier(SS);
+    // The declaration context must be complete.
+    if (LookupCtx && RequireCompleteDeclContext(SS, LookupCtx))
+      return true;
     Found.setContextRange(SS.getRange());
   }
 
@@ -509,14 +520,28 @@ bool Sema::BuildCXXNestedNameSpecifier(Scope *S, NestedNameSpecInfo &IdInfo,
     // expression or the declaration context associated with a prior
     // nested-name-specifier.
 
-    // The declaration context must be complete.
-    if (!LookupCtx->isDependentContext() &&
-        RequireCompleteDeclContext(SS, LookupCtx))
-      return true;
-
     LookupQualifiedName(Found, LookupCtx);
 
-    if (!ObjectType.isNull() && Found.empty()) {
+    isDependent |= Found.wasNotFoundInCurrentInstantiation();
+  }
+
+  bool LookupFirstQualifierInScope =
+      Found.empty() && !ObjectType.isNull() && !isDependent;
+
+  // FIXME: We should still do the lookup if the object expression is dependent,
+  // but instead of using them we should store them via
+  // setFirstQualifierFoundInScope and pretend we found nothing.
+  if (SS.isEmpty() && (ObjectType.isNull() || LookupFirstQualifierInScope)) {
+    if (S)
+      LookupName(Found, S);
+    else if (LookupFirstQualifierInScope && SS.getFirstQualifierFoundInScope())
+      Found.addDecl(SS.getFirstQualifierFoundInScope());
+
+    if (!ObjectType.isNull())
+      ObjectTypeSearchedInScope = true;
+  }
+#if 0
+    if (!ObjectType.isNull() && Found.empty() && !isDependent) {
       // C++ [basic.lookup.classref]p4:
       //   If the id-expression in a class member access is a qualified-id of
       //   the form
@@ -548,6 +573,7 @@ bool Sema::BuildCXXNestedNameSpecifier(Scope *S, NestedNameSpecInfo &IdInfo,
     // Perform unqualified name lookup in the current scope.
     LookupName(Found, S);
   }
+#endif
 
   if (Found.isAmbiguous())
     return true;
diff --git a/clang/lib/Sema/SemaExprMember.cpp b/clang/lib/Sema/SemaExprMember.cpp
index 9aa60204bf29d..05f4d756c202c 100644
--- a/clang/lib/Sema/SemaExprMember.cpp
+++ b/clang/lib/Sema/SemaExprMember.cpp
@@ -1054,7 +1054,7 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
       if (RetryExpr.isUsable() && !Trap.hasErrorOccurred()) {
         CXXScopeSpec TempSS(SS);
         RetryExpr = ActOnMemberAccessExpr(
-            ExtraArgs->S, RetryExpr.get(), OpLoc, tok::arrow, TempSS,
+            ExtraArgs->S, RetryExpr.get(), OpLoc, /*IsArrow=*/true, TempSS,
             TemplateKWLoc, ExtraArgs->Id, ExtraArgs->ObjCImpDecl);
       }
       if (Trap.hasErrorOccurred())
@@ -1767,12 +1767,10 @@ static ExprResult LookupMemberExpr(Sema &S, LookupResult &R,
 ///   decl; this is an ugly hack around the fact that Objective-C
 ///   \@implementations aren't properly put in the context chain
 ExprResult Sema::ActOnMemberAccessExpr(Scope *S, Expr *Base,
-                                       SourceLocation OpLoc,
-                                       tok::TokenKind OpKind,
+                                       SourceLocation OpLoc, bool IsArrow,
                                        CXXScopeSpec &SS,
                                        SourceLocation TemplateKWLoc,
-                                       UnqualifiedId &Id,
-                                       Decl *ObjCImpDecl) {
+                                       UnqualifiedId &Id, Decl *ObjCImpDecl) {
   if (SS.isSet() && SS.isInvalid())
     return ExprError();
 
@@ -1790,8 +1788,6 @@ ExprResult Sema::ActOnMemberAccessExpr(Scope *S, Expr *Base,
   DecomposeUnqualifiedId(Id, TemplateArgsBuffer,
                          NameInfo, TemplateArgs);
 
-  bool IsArrow = (OpKind == tok::arrow);
-
   if (getLangOpts().HLSL && IsArrow)
     return ExprError(Diag(OpLoc, diag::err_hlsl_operator_unsupported) << 2);
 
diff --git a/clang/lib/Sema/SemaPseudoObject.cpp b/clang/lib/Sema/SemaPseudoObject.cpp
index 14ed9590afc6c..fb48a14f12eb6 100644
--- a/clang/lib/Sema/SemaPseudoObject.cpp
+++ b/clang/lib/Sema/SemaPseudoObject.cpp
@@ -1394,10 +1394,10 @@ ExprResult MSPropertyOpBuilder::buildGet() {
   GetterName.setIdentifier(II, RefExpr->getMemberLoc());
   CXXScopeSpec SS;
   SS.Adopt(RefExpr->getQualifierLoc());
-  ExprResult GetterExpr =
-      S.ActOnMemberAccessExpr(S.getCurScope(), InstanceBase, SourceLocation(),
-                              RefExpr->isArrow() ? tok::arrow : tok::period, SS,
-                              SourceLocation(), GetterName, nullptr);
+  ExprResult GetterExpr = S.ActOnMemberAccessExpr(
+      S.getCurScope(), InstanceBase, /*OpLoc=*/SourceLocation(),
+      RefExpr->isArrow(), SS, /*TemplateKWLoc=*/SourceLocation(), GetterName,
+      /*ObjCImpDecl=*/nullptr);
   if (GetterExpr.isInvalid()) {
     S.Diag(RefExpr->getMemberLoc(),
            diag::err_cannot_find_suitable_accessor) << 0 /* getter */
@@ -1423,10 +1423,10 @@ ExprResult MSPropertyOpBuilder::buildSet(Expr *op, SourceLocation sl,
   SetterName.setIdentifier(II, RefExpr->getMemberLoc());
   CXXScopeSpec SS;
   SS.Adopt(RefExpr->getQualifierLoc());
-  ExprResult SetterExpr =
-      S.ActOnMemberAccessExpr(S.getCurScope(), InstanceBase, SourceLocation(),
-                              RefExpr->isArrow() ? tok::arrow : tok::period, SS,
-                              SourceLocation(), SetterName, nullptr);
+  ExprResult SetterExpr = S.ActOnMemberAccessExpr(
+      S.getCurScope(), InstanceBase, /*OpLoc=*/SourceLocation(),
+      RefExpr->isArrow(), SS, /*TemplateKWLoc=*/SourceLocation(), SetterName,
+      /*ObjCImpDecl=*/nullptr);
   if (SetterExpr.isInvalid()) {
     S.Diag(RefExpr->getMemberLoc(),
            diag::err_cannot_find_suitable_accessor) << 1 /* setter */
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 02d9b64c2b14b..a272b1ecb1f36 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -214,6 +214,7 @@ TemplateNameKind Sema::isTemplateName(Scope *S,
                          &AssumedTemplate,
                          /*AllowTypoCorrection=*/!Disambiguation))
     return TNK_Non_template;
+
   MemberOfUnknownSpecialization = R.wasNotFoundInCurrentInstantiation();
 
   if (AssumedTemplate != AssumedTemplateKind::None) {
@@ -430,7 +431,6 @@ bool Sema::LookupTemplateName(LookupResult &Found, Scope *S, CXXScopeSpec &SS,
   }
 
   bool ObjectTypeSearchedInScope = false;
-  bool AllowFunctionTemplatesInLookup = true;
   if (LookupCtx) {
     // Perform "qualified" name lookup into the declaration context we
     // computed, which is either the type of the base of a member access
@@ -449,7 +449,14 @@ bool Sema::LookupTemplateName(LookupResult &Found, Scope *S, CXXScopeSpec &SS,
     IsDependent |= Found.wasNotFoundInCurrentInstantiation();
   }
 
-  if (SS.isEmpty() && (ObjectType.isNull() || Found.empty())) {
+  bool LookupFirstQualifierInScope =
+      Found.getLookupKind() != LookupMemberName && Found.empty() &&
+      !ObjectType.isNull() && !IsDependent;
+
+  // FIXME: We should still do the lookup if the object expression is dependent,
+  // but instead of using them we should store them via
+  // setFirstQualifierFoundInScope and pretend we found nothing.
+  if (SS.isEmpty() && (ObjectType.isNull() || LookupFirstQualifierInScope)) {
     // C++ [basic.lookup.classref]p1:
     //   In a class member access expression (5.2.5), if the . or -> token is
     //   immediately followed by an identifier followed by a <, the
@@ -461,14 +468,11 @@ bool Sema::LookupTemplateName(LookupResult &Found, Scope *S, CXXScopeSpec &SS,
     //   template.
     if (S)
       LookupName(Found, S);
+    else if (LookupFirstQualifierInScope && SS.getFirstQualifierFoundInScope())
+      Found.addDecl(SS.getFirstQualifierFoundInScope());
 
-    if (!ObjectType.isNull()) {
-      //  FIXME: We should filter out all non-type templates here, particularly
-      //  variable templates and concepts. But the exclusion of alias templates
-      //  and template template parameters is a wording defect.
-      AllowFunctionTemplatesInLookup = false;
+    if (!ObjectType.isNull())
       ObjectTypeSearchedInScope = true;
-    }
 
     IsDependent |= Found.wasNotFoundInCurrentInstantiation();
   }
@@ -539,7 +543,7 @@ bool Sema::LookupTemplateName(LookupResult &Found, Scope *S, CXXScopeSpec &SS,
 
   NamedDecl *ExampleLookupResult =
       Found.empty() ? nullptr : Found.getRepresentativeDecl();
-  FilterAcceptableTemplateNames(Found, AllowFunctionTemplatesInLookup);
+  FilterAcceptableTemplateNames(Found);
   if (Found.empty()) {
     if (IsDependent) {
       Found.setNotFoundInCurrentInstantiation();
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 06ed0843ef504..53ba79f86ce76 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -2893,6 +2893,8 @@ class TreeTransform {
 
     CXXScopeSpec SS;
     SS.Adopt(QualifierLoc);
+    if (FirstQualifierInScope)
+      SS.setFoundFirstQualifierInScope(FirstQualifierInScope);
 
     Base = BaseResult.get();
     QualType BaseType = Base->getType();
@@ -3581,6 +3583,9 @@ class TreeTransform {
     CXXScopeSpec SS;
     SS.Adopt(QualifierLoc);
 
+    if (FirstQualifierInScope)
+      SS.setFoundFirstQualifierInScope(FirstQualifierInScope);
+
     return SemaRef.BuildMemberReferenceExpr(BaseE, BaseType,
                                             OperatorLoc, IsArrow,
                                             SS, TemplateKWLoc,
@@ -3604,6 +3609,9 @@ class TreeTransform {
     CXXScopeSpec SS;
     SS.Adopt(QualifierLoc);
 
+    if (FirstQualifierInScope)
+      SS.setFoundFirstQualifierInScope(FirstQualifierInScope);
+
     return SemaRef.BuildMemberReferenceExpr(BaseE, BaseType,
                                             OperatorLoc, IsArrow,
                                             SS, TemplateKWLoc,
@@ -4381,6 +4389,8 @@ NestedNameSpecifierLoc TreeTransform<Derived>::TransformNestedNameSpecifierLoc(
   insertNNS(NNS);
 
   CXXScopeSpec SS;
+  if (FirstQualifierInScope)
+    SS.setFoundFirstQualifierInScope(FirstQualifierInScope);
   while (!Qualifiers.empty()) {
     NestedNameSpecifierLoc Q = Qualifiers.pop_back_val();
     NestedNameSpecifier *QNNS = Q.getNestedNameSpecifier();
@@ -5180,6 +5190,9 @@ TypeSourceInfo *TreeTransform<Derived>::TransformTSIInObjectScope(
   TypeLocBuilder TLB;
   QualType Result;
 
+  if (UnqualLookup)
+    SS.setFoundFirstQualifierInScope(UnqualLookup);
+
   if (isa<TemplateSpecializationType>(T)) {
     TemplateSpecializationTypeLoc SpecTL =
         TL.castAs<TemplateSpecializationTypeLoc>();
@@ -16150,6 +16163,8 @@ TreeTransform<Derived>::RebuildTemplateName(CXXScopeSpec &SS,
   UnqualifiedId TemplateName;
   TemplateName.setIdentifier(&Name, NameLoc);
   Sema::TemplateTy Template;
+  if (FirstQualifierInScope)
+    SS.setFoundFirstQualifierInScope(FirstQualifierInScope);
   getSema().ActOnTemplateName(/*Scope=*/nullptr, SS, TemplateKWLoc,
                               TemplateName, ParsedType::make(ObjectType),
                               /*EnteringContext=*/false, Template,
diff --git a/clang/test/CXX/basic/basic.lookup/basic.lookup.classref/p1-cxx11.cpp b/clang/test/CXX/basic/basic.lookup/basic.lookup.classref/p1-cxx11.cpp
index 1afea99e8895c..476745537f691 100644
--- a/clang/test/CXX/basic/basic.lookup/basic.lookup.classref/p1-cxx11.cpp
+++ b/clang/test/CXX/basic/basic.lookup/basic.lookup.classref/p1-cxx11.cpp
@@ -55,15 +55,21 @@ namespace PR11856 {
 
   template<typename T> T *end(T*);
 
-  class X { };
+  struct X { };
+  struct Y {
+    int end;
+  };
   template <typename T>
   void Foo2() {
     T it1;
-    if (it1->end < it1->end) {
-    }
+    if (it1->end < it1->end) { }
 
     X *x;
-    if (x->end < 7) {  // expected-error{{no member named 'end' in 'PR11856::X'}}
-    }
+    if (x->end < 7) { } // expected-error{{expected '>'}}
+                        // expected-note at -1{{to match this '<'}}
+                        // expected-error at -2{{expected unqualified-id}}
+
+    Y *y;
+    if (y->end < 7) { }
   }
 }
diff --git a/clang/test/CXX/basic/basic.lookup/basic.lookup.classref/p1.cpp b/clang/test/CXX/basic/basic.lookup/basic.lookup.classref/p1.cpp
index e3599db18350b..cabf3f73830bc 100644
--- a/clang/test/CXX/basic/basic.lookup/basic.lookup.classref/p1.cpp
+++ b/clang/test/CXX/basic/basic.lookup/basic.lookup.classref/p1.cpp
@@ -86,15 +86,21 @@ namespace PR11856 {
 
   template<typename T> T *end(T*);
 
-  class X { };
+  struct X { };
+  struct Y {
+    int end;
+  };
   template <typename T>
   void Foo2() {
     T it1;
-    if (it1->end < it1->end) {
-    }
+    if (it1->end < it1->end) { }
 
     X *x;
-    if (x->end < 7) {  // expected-error{{no member named 'end' in 'PR11856::X'}}
-    }
+    if (x->end < 7) { } // expected-error{{expected '>'}}
+                        // expected-note at -1{{to match this '<'}}
+                        // expected-error at -2{{expected unqualified-id}}
+
+    Y *y;
+    if (y->end < 7) { }
   }
 }
diff --git a/clang/test/CXX/class.derived/class.member.lookup/p8.cpp b/clang/test/CXX/class.derived/class.member.lookup/p8.cpp
index 78e83c0ab4566..e9a57f739cc35 100644
--- a/clang/test/CXX/class.derived/class.member.lookup/p8.cpp
+++ b/clang/test/CXX/class.derived/class.member.lookup/p8.cpp
@@ -47,8 +47,8 @@ template<typename T>
 void DerivedT<T>::Inner() {
   Derived1T<T>::Foo();
   Derived2T<T>::Member = 42;
-  this->Derived1T<T>::Foo();
-  this->Derived2T<T>::Member = 42;
+  this->Derived1T<T>::Foo(); // expected-error{{use 'template' keyword to treat 'Derived1T' as a dependent template name}}
+  this->Derived2T<T>::Member = 42; // expected-error{{use 'template' keyword to treat 'Derived2T' as a dependent template name}}
   this->Foo(); // expected-error{{non-static member 'Foo' found in multiple base-class subobjects of type 'BaseT<int>'}}
 }
 
diff --git a/clang/test/CXX/drs/cwg1xx.cpp b/clang/test/CXX/drs/cwg1xx.cpp
index a8f9b705a9866..fe20a9e5dd610 100644
--- a/clang/test/CXX/drs/cwg1xx.cpp
+++ b/clang/test/CXX/drs/cwg1xx.cpp
@@ -518,7 +518,7 @@ namespace cwg136 { // cwg136: 3.4
   void q() {
     j(A(), A()); // ok, has default argument
   }
-  extern "C" void k(int, int, int, int); // #cwg136-k 
+  extern "C" void k(int, int, int, int); // #cwg136-k
   namespace NSA {
   struct A {
     friend void cwg136::k(int, int, int, int = 0);
@@ -618,7 +618,6 @@ namespace cwg141 { // cwg141: 3.1
     // FIXME: we issue a useful diagnostic first, then some bogus ones.
     b.f<int>();
     // expected-error at -1 {{no member named 'f' in 'cwg141::B'}}
-    // expected-error at -2 +{{}}
     (void)b.S<int>::n;
   }
   template<typename T> struct C {
@@ -628,10 +627,12 @@ namespace cwg141 { // cwg141: 3.1
       // expected-error at -1 {{use 'template' keyword to treat 'f' as a dependent template name}}
     }
     void h() {
-      (void)t.S<int>::n; // ok
+      (void)t.S<int>::n;
+      // expected-error at -1 {{use 'template' keyword to treat 'S' as a dependent template name}}
     }
     void i() {
-      (void)t.S<int>(); // ok!
+      (void)t.S<int>();
+      // expected-error at -1 {{use 'template' keyword to treat 'S' as a dependent template name}}
     }
   };
   void h() { C<B>().h(); } // ok
diff --git a/clang/test/SemaCXX/static-assert-cxx17.cpp b/clang/test/SemaCXX/static-assert-cxx17.cpp
index 41a7b025d0eb7..754f4ae5f1d38 100644
--- a/clang/test/SemaCXX/static-assert-cxx17.cpp
+++ b/clang/test/SemaCXX/static-assert-cxx17.cpp
@@ -96,7 +96,7 @@ void foo6() {
   // expected-error at -1{{static assertion failed due to requirement 'static_cast<const X<int> *>(nullptr)'}}
   static_assert((const X<typename T::T>[]){} == nullptr);
   // expected-error at -1{{static assertion failed due to requirement '(const X<int>[0]){} == nullptr'}}
-  static_assert(sizeof(X<decltype(X<typename T::T>().X<typename T::T>::~X())>) == 0);
+  static_assert(sizeof(X<decltype(X<typename T::T>().template X<typename T::T>::~X())>) == 0);
   // expected-error at -1{{static assertion failed due to requirement 'sizeof(X<void>) == 0'}} \
   // expected-note at -1 {{evaluates to '8 == 0'}}
   static_assert(constexpr_return_false<typename T::T, typename T::U>());
diff --git a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp
index ad73daa8e214c..7768d2f03ac5b 100644
--- a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp
+++ b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp
@@ -115,7 +115,7 @@ namespace CopyCounting {
   static_assert(f(X<A{}>()) == 0);
 
   template<A a> struct Y { void f(); };
-  template<A a> void g(Y<a> y) { y.Y<a>::f(); }
+  template<A a> void g(Y<a> y) { y.template Y<a>::f(); }
   void h() { constexpr A a; g<a>(Y<a>{}); }
 
   template<A a> struct Z {



More information about the cfe-commits mailing list