rsmith created this revision.
rsmith added a reviewer: rnk.
Herald added a project: clang.

This is ill-formed per [basic.scope.class]p2.

This is done by tracking all unqualified lookups performed within the
scope of a class definition that find nothing within the class, and
checking for conflicting declarations later introduced into the class.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D80977

Files:
  clang/include/clang/Basic/DiagnosticGroups.td
  clang/include/clang/Basic/DiagnosticSemaKinds.td
  clang/include/clang/Parse/Parser.h
  clang/include/clang/Sema/Lookup.h
  clang/include/clang/Sema/Scope.h
  clang/include/clang/Sema/SemaInternal.h
  clang/lib/Parse/ParseCXXInlineMethods.cpp
  clang/lib/Parse/ParseDeclCXX.cpp
  clang/lib/Sema/Scope.cpp
  clang/lib/Sema/SemaDecl.cpp
  clang/lib/Sema/SemaDeclCXX.cpp
  clang/lib/Sema/SemaExprCXX.cpp
  clang/lib/Sema/SemaLookup.cpp
  clang/lib/Sema/SemaTemplate.cpp
  clang/test/CXX/basic/basic.scope/basic.scope.class/p2.cpp
  clang/test/CXX/dcl.dcl/dcl.spec/dcl.typedef/p4.cpp
  clang/test/CXX/drs/dr2xx.cpp
  clang/test/CXX/expr/expr.prim/expr.prim.general/p3-0x.cpp
  clang/test/Parser/cxx-ambig-init-templ.cpp
  clang/test/Parser/cxx0x-override-control-keywords.cpp
  clang/test/SemaTemplate/alias-templates.cpp
  clang/test/SemaTemplate/deduction-crash.cpp
  clang/test/SemaTemplate/instantiate-exception-spec-cxx11.cpp
  clang/test/SemaTemplate/ms-delayed-default-template-args.cpp

Index: clang/test/SemaTemplate/ms-delayed-default-template-args.cpp
===================================================================
--- clang/test/SemaTemplate/ms-delayed-default-template-args.cpp
+++ clang/test/SemaTemplate/ms-delayed-default-template-args.cpp
@@ -24,10 +24,11 @@
 namespace test_inner_class_template {
 struct Outer {
   template <typename T = Baz> // expected-warning {{using the undeclared type 'Baz' as a default template argument is a Microsoft extension}}
+  // expected-warning@-1 {{not yet been declared}}
   struct Foo {
     static_assert(sizeof(T) == 4, "should get int, not double");
   };
-  typedef int Baz;
+  typedef int Baz; // expected-note {{declared here}}
 };
 typedef double Baz;
 template struct Outer::Foo<>;
Index: clang/test/SemaTemplate/instantiate-exception-spec-cxx11.cpp
===================================================================
--- clang/test/SemaTemplate/instantiate-exception-spec-cxx11.cpp
+++ clang/test/SemaTemplate/instantiate-exception-spec-cxx11.cpp
@@ -64,16 +64,18 @@
 }
 
 namespace core_19754_example {
-  template<typename T> T declval() noexcept;
+namespace N {
+template <typename T>
+T declval() noexcept;
 
-  template<typename T, typename = decltype(T(declval<T&&>()))>
-  struct is_movable { static const bool value = true; };
+template <typename T, typename = decltype(T(declval<T &&>()))>
+struct is_movable { static const bool value = true; };
 
-  template<typename T>
-  struct wrap {
-    T val;
-    void irrelevant(wrap &p) noexcept(is_movable<T>::value);
-  };
+template <typename T>
+struct wrap {
+  T val;
+  void irrelevant(wrap &p) noexcept(is_movable<T>::value);
+};
 
   template<typename T>
   struct base {
@@ -93,13 +95,14 @@
   };
 
   struct types {
-     typedef base<types> base;
-     typedef type1<types> type1;
-     typedef type2<types> type2;
+    typedef N::base<types> base;
+    typedef N::type1<types> type1;
+    typedef N::type2<types> type2;
   };
 
   base<types> val = base<types>();
-}
+  } // namespace N
+  } // namespace core_19754_example
 
 namespace pr9485 {
   template <typename T> void f1(T) throw(typename T::exception); // expected-note {{candidate}}
Index: clang/test/SemaTemplate/deduction-crash.cpp
===================================================================
--- clang/test/SemaTemplate/deduction-crash.cpp
+++ clang/test/SemaTemplate/deduction-crash.cpp
@@ -19,7 +19,7 @@
 template<class>
 struct state_machine
 {
-  typedef aaa<int>::ae aaa;
+  typedef ::aaa<int>::ae aaa;
   int start()
   {
     ant(0);
Index: clang/test/SemaTemplate/alias-templates.cpp
===================================================================
--- clang/test/SemaTemplate/alias-templates.cpp
+++ clang/test/SemaTemplate/alias-templates.cpp
@@ -41,7 +41,7 @@
   template<typename T> struct thing {
     typedef T inner;
     typedef ptr<inner> inner_ptr;
-    typedef traits<thing<inner>> traits_type;
+    typedef X::traits<thing<inner>> traits_type;
 
     template<typename U> using rebind = thing<U>;
 
Index: clang/test/Parser/cxx0x-override-control-keywords.cpp
===================================================================
--- clang/test/Parser/cxx0x-override-control-keywords.cpp
+++ clang/test/Parser/cxx0x-override-control-keywords.cpp
@@ -1,5 +1,4 @@
 // RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s
-// expected-no-diagnostics
 
 struct Base {
   virtual void override();
@@ -14,9 +13,10 @@
   virtual void override() override { } 
 };
 
-struct override;
+struct override; // expected-note {{use a qualified name to refer to member of the global namespace}}
 struct Base2 {
-  virtual override override(int override);
+  virtual override        // expected-warning {{not yet been declared}}
+  override(int override); // expected-note {{here}}
 };
 
 struct A : Base2 {
Index: clang/test/Parser/cxx-ambig-init-templ.cpp
===================================================================
--- clang/test/Parser/cxx-ambig-init-templ.cpp
+++ clang/test/Parser/cxx-ambig-init-templ.cpp
@@ -94,16 +94,16 @@
 
 namespace NoAnnotationTokens {
   template<bool> struct Bool { Bool(int); };
-  static const bool in_class = false;
+  static const bool in_class = false; // expected-note {{use a qualified name to refer to member of namespace 'NoAnnotationTokens'}}
 
   struct Test {
     // Check we don't keep around a Bool<false> annotation token here.
     int f(Bool<true> = X<Y, Bool<in_class> >(0));
 
     // But it's OK if we do here.
-    int g(Bool<true> = Z<Y, Bool<in_class> = Bool<false>(0));
+    int g(Bool<true> = Z < Y, Bool<in_class> = Bool<false>(0)); // expected-warning {{not yet been declared}}
 
-    static const bool in_class = true;
+    static const bool in_class = true; // expected-note {{declared here}}
     template<int, typename U> using X = U;
     static const int Y = 0, Z = 0;
   };
Index: clang/test/CXX/expr/expr.prim/expr.prim.general/p3-0x.cpp
===================================================================
--- clang/test/CXX/expr/expr.prim/expr.prim.general/p3-0x.cpp
+++ clang/test/CXX/expr/expr.prim/expr.prim.general/p3-0x.cpp
@@ -150,13 +150,14 @@
   {
   public:
     template <typename... Args>
-    auto operator()(Args&&... args) const -> decltype(wrapped(args...)) // expected-note{{candidate template ignored: substitution failure [with Args = <int>]: use of undeclared identifier 'wrapped'}}
+    auto operator()(Args &&... args) const -> decltype(wrapped(args...)) // expected-note{{candidate template ignored: substitution failure [with Args = <int>]: use of undeclared identifier 'wrapped'}}
+    // expected-warning@-1 {{name 'wrapped' refers to a member of class 'wrap<F>' that has not yet been declared}}
     {
       return wrapped(args...);
     }
-  
+
   private:
-    F wrapped;
+    F wrapped; // expected-note {{declared here}}
   };
 
   void test(wrap<int (*)(int)> w) {
Index: clang/test/CXX/drs/dr2xx.cpp
===================================================================
--- clang/test/CXX/drs/dr2xx.cpp
+++ clang/test/CXX/drs/dr2xx.cpp
@@ -951,11 +951,14 @@
 }
 
 namespace dr283 { // dr283: yes
-  template<typename T> // expected-note 2{{here}}
-  struct S {
-    friend class T; // expected-error {{shadows}}
-    class T; // expected-error {{shadows}}
-  };
+template <typename T> // expected-note {{here}}
+struct S1 {
+  friend class T; // expected-error {{shadows}}
+};
+template <typename T> // expected-note {{here}}
+struct S2 {
+  class T; // expected-error {{shadows}}
+};
 }
 
 namespace dr284 { // dr284: no
Index: clang/test/CXX/dcl.dcl/dcl.spec/dcl.typedef/p4.cpp
===================================================================
--- clang/test/CXX/dcl.dcl/dcl.spec/dcl.typedef/p4.cpp
+++ clang/test/CXX/dcl.dcl/dcl.spec/dcl.typedef/p4.cpp
@@ -2,7 +2,8 @@
 
 struct S {
   typedef struct A {} A; // expected-note {{previous definition is here}}
-  typedef struct B B;
+  typedef struct B       // expected-warning {{name 'B' refers to a member of class 'S' that has not yet been declared}}
+      B;                 // expected-note {{declared here}}
   typedef A A; // expected-error {{redefinition of 'A'}}
 
   struct C { };
Index: clang/test/CXX/basic/basic.scope/basic.scope.class/p2.cpp
===================================================================
--- /dev/null
+++ clang/test/CXX/basic/basic.scope/basic.scope.class/p2.cpp
@@ -0,0 +1,218 @@
+// RUN: %clang_cc1 -std=c++20 -verify %s
+
+// A name N used in a class S shall refer to the same declaration in its
+// context and when re-evaluated in the completed scope of S.
+
+enum Enum {};         // expected-note {{use a qualified name}}
+enum Enum2 {};        // expected-note {{use a qualified name}}
+class Class {};       // expected-note 4{{use a qualified name}}
+class Class2 {};      // expected-note 2{{use a qualified name}}
+class Class3 {};      // expected-note {{use a qualified name}}
+using Typedef = int;  // expected-note {{use a qualified name}}
+using Typedef2 = int; // expected-note {{use a qualified name}}
+int Function();       // expected-note {{use a qualified name}}
+
+struct UseInSelf {
+  Class *    // expected-warning {{name 'Class' refers to a member of class 'UseInSelf' that has not yet been declared}}
+      Class; // expected-note {{declared here after its point of use}}
+};
+
+struct Friends {
+  friend class FriendClass; // expected-warning {{name 'FriendClass' refers to a member of class 'Friends' that has not yet been declared}} expected-note {{injects non-member 'FriendClass'; if a member was intended, remove this friend declaration (befriending a member has no effect)}}
+  class FriendClass;        // expected-note {{member 'FriendClass' declared here after its point of use}}
+
+  friend class Class; // expected-warning {{not yet been declared}}
+  class Class;        // expected-note {{declared here}}
+
+  friend Class2; // expected-warning {{not yet been declared}}
+  class Class2;  // expected-note {{declared here}}
+
+  friend enum Enum;      // expected-warning {{not yet been declared}}
+  enum class Enum : int; // expected-note {{declared here}}
+
+  friend Enum2;           // expected-warning {{not yet been declared}}
+  enum class Enum2 : int; // expected-note {{declared here}}
+
+  friend int FriendFunction();
+  int FriendFunction();
+
+  friend int Function();
+  int Function();
+
+  friend Typedef;      // expected-warning {{not yet been declared}}
+  using Typedef = int; // expected-note {{declared here}}
+
+  friend Typedef2; // expected-warning {{not yet been declared}}
+  class Typedef2;  // expected-note {{declared here}}
+};
+
+struct Elaborated {
+  class Class *p1; // expected-warning {{not yet been declared}}
+  Class *q1;
+  class Class {}; // expected-note {{declared here}}
+
+  class Class2 *p2; // expected-warning {{not yet been declared}}
+  Class2 *q2;
+  using Class2 = int; // expected-note {{declared here}}
+
+  class Class3 *p3;   // expected-warning {{not yet been declared}}
+  using Class3 = int; // expected-note {{declared here}}
+};
+
+struct HasMember { // expected-note 2{{use a qualified name}}
+  using Member = int;
+};
+
+struct NestedNameSpecifierLookup1 {
+  // Here, it would not be OK to warn on the first usage, because the lookup
+  // for HasMember would ignore the member below.
+  HasMember::Member *p;
+  HasMember *q;  // FIXME: would be nice to indicate this usage is erroneous
+  int HasMember; // expected-warning {{declaration of member 'HasMember' after its name has already been used}}
+};
+
+struct NestedNameSpecifierLookup2 {
+  HasMember::Member *p; // expected-warning {{not yet been declared}}
+  HasMember *q;
+  struct HasMember; // expected-note {{declared here}}
+};
+
+struct NestedNameSpecifierLookup3 {
+  HasMember::Member *p; // OK, lookup ignores non-types.
+  int HasMember;
+};
+
+struct NestedNameSpecifierLookup4 {
+  HasMember::Member *p; // expected-warning {{not yet been declared}}
+  struct HasMember;     // expected-note {{declared here}}
+};
+
+struct NestedOuter {
+  struct NestedMiddle {
+    struct Inner {
+      Class *p; // expected-warning {{refers to a member of class 'NestedOuter::NestedMiddle' that has not yet been declared}}
+    };
+    struct Class {}; // expected-note {{declared here}}
+  };
+  struct Class {};
+};
+
+struct Enumerators {
+  int arr[sizeof(Function())]; // expected-warning {{refers to a member of class 'Enumerators'}}
+  enum {
+    Function = 1 // expected-note {{here}}
+  };
+};
+
+struct Operators {};
+int operator+(Operators, Operators);
+struct OperatorMembers {
+  int arr[sizeof(Operators() + Operators())];
+  int operator+(Operators);                   // OK, no conflict with the above
+  friend int operator+(Operators, Operators); // OK, no conflict with the above
+};
+
+namespace Namespace {}
+
+namespace A {
+struct Namespace {};
+
+struct UsingDirective1 {
+  constexpr static int a = [] { using namespace Namespace; return 0; }();
+  struct Namespace *ns;
+  int Namespace; // OK, does not hide either use.
+};
+
+// We get a worse warning here (with no use location) because neither use
+// below has a strictly more permissive IDNS.
+// FIXME: We could improve the diagnostic here: there's no way for a name
+// found by 'using namespace' to be hidden by a class-scope declaration.
+struct UsingDirective2 {
+  constexpr static int a = [] { using namespace Namespace; return 0; }();
+  struct Namespace *ns;
+  struct Namespace {}; // expected-warning {{declaration of member 'Namespace' after its name has already been used within the definition of class 'A::UsingDirective2'}}
+};
+
+struct UsingDirective3 {
+  constexpr static int a = [] { using namespace Namespace; return 0; }();
+  struct Namespace *ns;
+  using Namespace = int; // expected-warning {{declaration of member 'Namespace' after its name has already been used within the definition of class 'A::UsingDirective3'}}
+};
+} // namespace A
+
+struct DeclaredTooLate {
+  // FIXME: In this case it would be prefereable to diagnose that NewUniqueType
+  // has not yet been declared.
+  //
+  // We could in principle get this result by delaying the "undeclared"
+  // diagnostic until the end of the class (or until we want to produce another
+  // error, which might be a follow-on error from that error). It's not clear
+  // that this would be worth the complexity.
+  NewUniqueType f(); // expected-error {{unknown type name}}
+  struct NewUniqueType {};
+
+  static int a = unique_member_function_name(); // expected-error {{undeclared identifier}}
+  static int unique_member_function_name();
+};
+
+struct Undeclared {
+  // Don't also produce a "declared after use" warning here, even though we
+  // look up 'f' as a type before we declare it as a member.
+  f(); // expected-error {{requires a type specifier}}
+};
+
+struct IgnoringNonTypes {
+  template <typename T>
+  auto f(T t) -> decltype(t.template operator()<int>());
+  template <typename T>
+  auto f(T t) -> decltype(t.template g<int>());
+  template <typename T>
+  auto f(T t) -> decltype(t.template T<int>());
+  template <typename T>
+  auto f(T t) -> decltype(t.template U<int>::f());
+
+  template <typename>
+  void operator()();
+  template <typename>
+  void g();
+  template <typename>
+  struct T {};
+  template <typename>
+  struct U {};
+};
+
+namespace TagShadow {
+template <typename T>
+struct A {
+  struct X {}; // expected-note 2{{use a qualified name to refer to member of 'A<T>'}}
+  struct B {
+    X       // expected-warning {{member of class 'TagShadow::A::B' that has not yet been declared}}
+        *X; // expected-note {{declared here after its point of use}}
+  };
+  struct C {
+    using A = int;
+    X       // expected-warning {{not yet been declared}}
+        *X; // expected-note {{declared here}}
+  };
+  friend class Z; // expected-warning {{not yet been declared}} expected-note {{declaration injects non-member 'TagShadow::Z'; if a member was intended, remove}}
+  class Z {};     // expected-note {{declared here}}
+};
+struct W {
+  // FIXME: We should point out that this injects a non-member too, but this
+  // doesn't inject a tag declaration because 'TagShadow::Z' was already
+  // declared by 'A'.
+  friend class Z; // expected-warning {{not yet been declared}}
+  class Z {};     // expected-note {{declared here}}
+};
+
+struct QB; // expected-note {{use a qualified name}}
+struct Q {
+  struct QA *p; // tag lookup, expected-warning {{not yet been declared}} expected-note {{use a standalone forward declaration}}
+  int QA::*q;   // nested name specifier lookup
+  int QB::*r;   // nested name specifier lookup, expected-warning {{not yet been declared}}
+  struct QB *s; // tag lookup
+
+  struct QA {}; // expected-note {{declared here after its point of use}}
+  struct QB {}; // expected-note {{declared here after its point of use}}
+};
+} // namespace TagShadow
Index: clang/lib/Sema/SemaTemplate.cpp
===================================================================
--- clang/lib/Sema/SemaTemplate.cpp
+++ clang/lib/Sema/SemaTemplate.cpp
@@ -448,6 +448,13 @@
   }
 
   if (SS.isEmpty() && (ObjectType.isNull() || Found.empty())) {
+    if (IsDependent) {
+      // FIXME: Conservatively treat this fallback lookup as synthetic. We
+      // don't know whether we will actually want to look in the scope until
+      // instantiation.
+      Found.setSynthetic();
+    }
+
     // 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
Index: clang/lib/Sema/SemaLookup.cpp
===================================================================
--- clang/lib/Sema/SemaLookup.cpp
+++ clang/lib/Sema/SemaLookup.cpp
@@ -1338,6 +1338,12 @@
       return false;
     }
 
+    // If we've just searched a lookup-tracking scope and found nothing, make a
+    // note that declaring a new name in this scope could change the lookup
+    // result.
+    if (R.empty() && !R.isForRedeclaration() && !R.isSynthetic())
+      S->noteEmptyLookup(Name, R.getNameLoc(), R.getIdentifierNamespace());
+
     if (!Ctx && S->isTemplateParamScope() && OutsideOfTemplateParamDC &&
         S->getParent() && !S->getParent()->isTemplateParamScope()) {
       // We've just searched the last template parameter scope and
@@ -2542,6 +2548,8 @@
     CXXRecordDecl *RD = cast<CXXRecordDecl>(
         BaseSpec.getType()->castAs<RecordType>()->getDecl());
     LookupResult Result(*this, R.getLookupNameInfo(), R.getLookupKind());
+    if (R.isSynthetic())
+      Result.setSynthetic();
     Result.setBaseObjectType(Context.getRecordType(Class));
     LookupQualifiedName(Result, RD);
 
@@ -5507,6 +5515,7 @@
 void Sema::ActOnPragmaDump(Scope *S, SourceLocation IILoc, IdentifierInfo *II) {
   DeclarationNameInfo Name(II, IILoc);
   LookupResult R(*this, Name, LookupAnyName, Sema::NotForRedeclaration);
+  R.setSynthetic();
   R.suppressDiagnostics();
   R.setHideTags(false);
   LookupName(R, S);
Index: clang/lib/Sema/SemaExprCXX.cpp
===================================================================
--- clang/lib/Sema/SemaExprCXX.cpp
+++ clang/lib/Sema/SemaExprCXX.cpp
@@ -7872,6 +7872,7 @@
                                   const TypoCorrection &TC) {
   LookupResult R(SemaRef, Consumer.getLookupResult().getLookupNameInfo(),
                  Consumer.getLookupResult().getLookupKind());
+  R.setSynthetic();
   const CXXScopeSpec *SS = Consumer.getSS();
   CXXScopeSpec NewSS;
 
Index: clang/lib/Sema/SemaDeclCXX.cpp
===================================================================
--- clang/lib/Sema/SemaDeclCXX.cpp
+++ clang/lib/Sema/SemaDeclCXX.cpp
@@ -13473,6 +13473,7 @@
     "__builtin_memcpy";
   LookupResult R(S, &S.Context.Idents.get(MemCpyName), Loc,
                  Sema::LookupOrdinaryName);
+  R.setSynthetic();
   S.LookupName(R, S.TUScope, true);
 
   FunctionDecl *MemCpy = R.getAsSingle<FunctionDecl>();
Index: clang/lib/Sema/SemaDecl.cpp
===================================================================
--- clang/lib/Sema/SemaDecl.cpp
+++ clang/lib/Sema/SemaDecl.cpp
@@ -611,6 +611,7 @@
   // Do a tag name lookup in this scope.
   LookupResult R(*this, &II, SourceLocation(), LookupTagName);
   LookupName(R, S, false);
+  R.setSynthetic();
   R.suppressDiagnostics();
   if (R.getResultKind() == LookupResult::Found)
     if (const TagDecl *TD = R.getAsSingle<TagDecl>()) {
@@ -788,6 +789,7 @@
                                     IdentifierInfo *&Name,
                                     SourceLocation NameLoc) {
   LookupResult R(SemaRef, Name, NameLoc, Sema::LookupTagName);
+  R.setSynthetic();
   SemaRef.LookupParsedName(R, S, &SS);
   if (TagDecl *Tag = R.getAsSingle<TagDecl>()) {
     StringRef FixItTagName;
@@ -1427,6 +1429,220 @@
           New->hasAttr<OverloadableAttr>());
 }
 
+/// Get a nested name specifier suitable to name \p TargetContext from \p
+/// CurContext.
+///
+/// This is implemented below in terms of
+/// TypoCorrectionConsumer::NestedNameSpecifierSet.
+static NestedNameSpecifier *
+getRelativeNestedNameSpecifier(Sema &SemaRef, Scope *CurScope,
+                               DeclContext *TargetContext) {
+  llvm::function_ref<NestedNameSpecifier *(DeclContext *)> MakeRec;
+  auto Make = [&](DeclContext *DC) -> NestedNameSpecifier * {
+    if (isa<TranslationUnitDecl>(DC))
+      return NestedNameSpecifier::GlobalSpecifier(SemaRef.Context);
+
+    // If the DeclContext is a type, we're looking for a way to name that type.
+    QualType DCType;
+    if (auto *TD = dyn_cast<TypeDecl>(DC))
+      DCType = SemaRef.Context.getTypeDeclType(TD);
+
+    // If we hit something like a function scope that's not common between the
+    // current context and the target, we can't reasonably build a nested name
+    // specifier.
+    if (!isa<NamespaceDecl>(DC) && DCType.isNull())
+      return nullptr;
+
+    DeclarationName Name = cast<NamedDecl>(DC)->getDeclName();
+
+    // We might be able to refer to this by unqualified name. Try it.
+    LookupResult R(SemaRef, Name, SourceLocation(),
+                   Sema::LookupNestedNameSpecifierName);
+    if (Name) {
+      R.setSynthetic();
+      R.suppressDiagnostics();
+      SemaRef.LookupName(R, CurScope);
+    }
+
+    QualType RAsType;
+    if (auto *TD = R.getAsSingle<TypeDecl>())
+      RAsType = SemaRef.Context.getTypeDeclType(TD);
+
+    NestedNameSpecifier *Prefix = nullptr;
+    if (R.isSingleResult() &&
+        declaresSameEntity(R.getFoundDecl(), cast<Decl>(DC))) {
+      // No prefix: we can name the entity directly in this context.
+    } else if (!DCType.isNull() && !RAsType.isNull() &&
+               SemaRef.Context.hasSameUnqualifiedType(DCType, RAsType)) {
+      // No prefix: we can name the type directly in this context.
+    } else {
+      Prefix = MakeRec(DC->getLookupParent());
+      if (!Prefix)
+        return nullptr;
+
+      // When naming a class template from an enclosing context, switch from
+      // the injected-class-name to the template specialization type.
+      if (!DCType.isNull())
+        if (auto *Injected = DCType->getAs<InjectedClassNameType>())
+          DCType = Injected->getInjectedSpecializationType();
+    }
+
+    if (auto *ND = dyn_cast<NamespaceDecl>(DC)) {
+      // Don't mention inline namespaces if we can help it.
+      // FIXME: Do we ever need to mention them for disambiguation?
+      if (ND->isInline() && Prefix)
+        return Prefix;
+      return NestedNameSpecifier::Create(SemaRef.Context, Prefix, ND);
+    }
+
+    return NestedNameSpecifier::Create(SemaRef.Context, Prefix,
+                                       /*TemplateKW=*/false,
+                                       DCType.getTypePtr());
+  };
+  MakeRec = Make;
+
+  return Make(TargetContext);
+}
+
+static Scope *getTagInjectionScope(Scope *S, const LangOptions &LangOpts);
+
+/// Diagnose the case where we have performed a lookup for the name of a
+/// declaration at class scope and found nothing, then later seen a declaration
+/// of a class member with the given name.
+///
+/// This is ill-formed (no diagnostic required) by [basic.scope.class.p2].
+static void diagnoseEmptyLookupShadow(Sema &SemaRef, Scope *S, NamedDecl *D,
+                                      Scope::EmptyLookup Lookup) {
+  // C++ [basic.scope.class]p2:
+  //   A name N used in a class S shall refer to the same declaration in its
+  //   context and when re-evaluated in the completed scope of S. No diagnostic
+  //   is required for a violation of this rule.
+  assert(SemaRef.getLangOpts().CPlusPlus &&
+         "empty lookup detection should only be done in C++");
+
+  // Suppress diagnostics for this if we hit any errors while processing the
+  // class. For example, if lookup for the name fails entirely, producing a
+  // duplicate warning is likely not useful. Similarly for a case such as
+  // 'struct A { f(); };' where we first look up 'f' as a type and then recover
+  // by injecting it as a member name.
+  if (S->hasUnrecoverableErrorOccurred() || !D->isCXXClassMember())
+    return;
+
+  // This must be a class scope.
+  CXXRecordDecl *RD = cast<CXXRecordDecl>(S->getEntity());
+
+  if (Lookup.FirstUseLoc == D->getLocation()) {
+    // We looked this up but ended up declaring it instead. Don't count
+    // this as an empty lookup. This happens for cases like
+    //   auto m = 0;
+    // in C++98 mode, where we would first look up 'm' as the name of a type,
+    // and then introduce it as a non-type name, and also for things like
+    //   C (f)();
+    //   C (f)(int);
+    // in class 'C', where we look up 'f' to determine if we're declaring a
+    // constructor.
+  } else if (D->isInIdentifierNamespace(Lookup.FirstIDNS)) {
+    SemaRef.Diag(Lookup.FirstUseLoc, diag::ext_member_declared_too_late)
+        << D->getDeclName() << D->getDeclContext()->getRedeclContext();
+
+    // Consider suggesting a qualified name. Reconstruct the original
+    // lookup result to figure out what qualifier to suggest.
+    LookupResult R(SemaRef, D->getDeclName(), Lookup.FirstUseLoc,
+                   Sema::LookupOrdinaryName);
+    R.setSynthetic();
+    R.suppressDiagnostics();
+    R.setIdentifierNamespace(Lookup.FirstIDNS);
+    SemaRef.LookupName(R, S->getParent());
+
+    // Ignore declarations lexically within the class (eg, injected tag
+    // declarations). Those will be invalid too, so don't suggest using a
+    // qualified name to name them.
+    auto Filter = R.makeFilter();
+    while (Filter.hasNext()) {
+      NamedDecl *D = Filter.next();
+      if (D->getLexicalDeclContext()->getRedeclContext()->Equals(RD)) {
+        bool FoundReplacement = false;
+        for (auto *Redecl : D->redecls()) {
+          if (!D->getLexicalDeclContext()->getRedeclContext()->Equals(RD)) {
+            Filter.replace(cast<NamedDecl>(Redecl));
+            FoundReplacement = true;
+            break;
+          }
+        }
+        if (!FoundReplacement)
+          Filter.erase();
+      }
+    }
+    Filter.done();
+
+    // If anything remains, that's likely to be what was intended.
+    if (!R.empty()) {
+      NamedDecl *OldD = R.getRepresentativeDecl();
+
+      // Attempt to form a good fix-it hint describing how to properly name
+      // the declaration we found earlier. This might fail.
+      FixItHint Hint;
+      if (NestedNameSpecifier *NNS = getRelativeNestedNameSpecifier(
+              SemaRef, S, OldD->getDeclContext()->getRedeclContext())) {
+        llvm::SmallString<64> Str;
+        llvm::raw_svector_ostream OS(Str);
+        PrintingPolicy Policy = SemaRef.Context.getPrintingPolicy();
+        Policy.PrintInjectedClassNameWithArguments = false;
+        NNS->print(OS, Policy);
+        Hint = FixItHint::CreateInsertion(Lookup.FirstUseLoc, Str);
+      }
+
+      SemaRef.Diag(OldD->getLocation(),
+                   diag::note_member_declared_too_late_qualify)
+          << OldD->getDeclContext() << Hint;
+    }
+
+    if (R.empty() && (Lookup.FirstIDNS == Decl::IDNS_Type) && isa<TagDecl>(D)) {
+      // This was a failed tag lookup, and has been shadowed by a tag
+      // declaration. It's very likely that the programmer believed that the
+      // tag reference would declare the tag as a member. For example:
+      //   struct A {
+      //     friend class X;
+      //     class X {};
+      //   };
+      // Look for the injected tag declaration to check that this is what
+      // happened.
+      LookupResult TagR(SemaRef, D->getDeclName(), Lookup.FirstUseLoc,
+                        Sema::LookupTagName, Sema::ForVisibleRedeclaration);
+      TagR.setSynthetic();
+      TagR.suppressDiagnostics();
+      SemaRef.LookupName(TagR, getTagInjectionScope(S, SemaRef.getLangOpts()));
+
+      if (auto *FoundTag = TagR.getAsSingle<TagDecl>()) {
+        for (auto *Redecl : FoundTag->redecls()) {
+          if (Redecl->getLocation() == Lookup.FirstUseLoc) {
+            SemaRef.Diag(Lookup.FirstUseLoc,
+                         diag::note_tag_decl_injects_non_member)
+                << Redecl << !!Redecl->getFriendObjectKind();
+            break;
+          }
+        }
+      }
+    }
+
+    SemaRef.Diag(D->getLocation(), diag::note_member_declared_too_late) << D;
+  } else if (D->isInIdentifierNamespace(Lookup.CumulativeIDNS)) {
+    // The first use location was a valid use of an enclosing name, but some
+    // later use would have been interpreted as the class member.
+    SemaRef.Diag(D->getLocation(),
+                 diag::ext_member_declared_too_late_multi_idns)
+        << D << D->getDeclContext()->getRedeclContext();
+  } else {
+    // This declaration does not actually shadow the prior uses.
+    return;
+  }
+
+  // Ensure we don't issue more diagnostics if the name is redeclared in
+  // this class or declared in an enclosing class.
+  for (Scope *InnerS = S; InnerS; InnerS = InnerS->getParent())
+    InnerS->eraseEmptyLookup(D->getDeclName());
+}
+
 /// Add this decl to the scope shadowed decl chains.
 void Sema::PushOnScopeChains(NamedDecl *D, Scope *S, bool AddToContext) {
   // Move up the scope chain until we find the nearest enclosing
@@ -1467,6 +1683,11 @@
     }
   }
 
+  // If we're tracking empty lookups in this context, see if we already tried
+  // and failed to look for this name, and diagnose if so.
+  if (Scope::EmptyLookup Lookup = S->getEmptyLookupForName(D->getDeclName()))
+    diagnoseEmptyLookupShadow(*this, S, D, Lookup);
+
   S->AddDecl(D);
 
   if (isa<LabelDecl>(D) && !cast<LabelDecl>(D)->isGnuLocal()) {
@@ -7614,6 +7835,7 @@
 
   LookupResult R(*this, D->getDeclName(), D->getLocation(),
                  Sema::LookupOrdinaryName, Sema::ForVisibleRedeclaration);
+  R.setSynthetic();
   LookupName(R, S);
   if (NamedDecl *ShadowedDecl = getShadowedDeclaration(D, R))
     CheckShadow(D, ShadowedDecl, R);
@@ -8186,6 +8408,7 @@
                     IsLocalFriend ? Sema::LookupLocalFriendName
                                   : Sema::LookupOrdinaryName,
                     Sema::ForVisibleRedeclaration);
+  Prev.setSynthetic();
 
   NewFD->setInvalidDecl();
   if (IsLocalFriend)
Index: clang/lib/Sema/Scope.cpp
===================================================================
--- clang/lib/Sema/Scope.cpp
+++ clang/lib/Sema/Scope.cpp
@@ -92,6 +92,8 @@
   Entity = nullptr;
   ErrorTrap.reset();
   NRVO.setPointerAndInt(nullptr, 0);
+  assert(!EmptyLookups &&
+         "failed to disable lookup tracking before reusing scope");
 }
 
 bool Scope::containedInPrototypeScope() const {
@@ -118,6 +120,29 @@
   Flags |= FlagsToSet;
 }
 
+void Scope::noteEmptyLookupImpl(DeclarationName N, SourceLocation UseLoc,
+                                unsigned IDNS) {
+  auto Ret = EmptyLookups->insert({N, {UseLoc, IDNS, IDNS}});
+  if (Ret.second)
+    return;
+
+  // We might have performed lookups with different IDNSes. For example:
+  //
+  //   struct S {
+  //     A::B *p;     // #1
+  //     A *q;        // #2
+  //     struct A {}; // #3
+  //   };
+  //
+  // In such cases, preserve the first source location but all of the IDNSes.
+  // We'll diagnose at the first use location if possible and fall back to not
+  // giving a use location if the first use was valid (which is rare).
+  auto &Old = Ret.first->second;
+  if (Old.FirstUseLoc == UseLoc)
+    Old.FirstIDNS |= IDNS;
+  Old.CumulativeIDNS |= IDNS;
+}
+
 void Scope::mergeNRVOIntoParent() {
   if (VarDecl *Candidate = NRVO.getPointer()) {
     if (isDeclScope(Candidate))
Index: clang/lib/Parse/ParseDeclCXX.cpp
===================================================================
--- clang/lib/Parse/ParseDeclCXX.cpp
+++ clang/lib/Parse/ParseDeclCXX.cpp
@@ -3318,6 +3318,8 @@
   BalancedDelimiterTracker T(*this, tok::l_brace);
   T.consumeOpen();
 
+  ParsingDef.startBody();
+
   if (TagDecl)
     Actions.ActOnStartCXXMemberDeclarations(getCurScope(), TagDecl, FinalLoc,
                                             IsFinalSpelledSealed,
@@ -3356,6 +3358,8 @@
                                               T.getOpenLocation(),
                                               T.getCloseLocation(), attrs);
 
+  ParsingDef.finishBody();
+
   // C++11 [class.mem]p2:
   //   Within the class member-specification, the class is regarded as complete
   //   within function bodies, default arguments, exception-specifications, and
Index: clang/lib/Parse/ParseCXXInlineMethods.cpp
===================================================================
--- clang/lib/Parse/ParseCXXInlineMethods.cpp
+++ clang/lib/Parse/ParseCXXInlineMethods.cpp
@@ -1026,6 +1026,12 @@
     TentativeParsingAction Inner(Self);
     Self.ConsumeAndStoreUntil(EndKind, Toks, true, /*ConsumeFinalToken*/false);
     Inner.Revert();
+
+    // Disable lookup tracking; these lookups are going to be reverted, so
+    // don't count.
+    for (Scope *S = Self.getCurScope(); S; S = S->getParent())
+      if (auto *Map = S->disableLookupTracking())
+        OldLookupMaps.push_back({S, Map});
   }
 
   void RevertAnnotations() {
@@ -1042,12 +1048,18 @@
 
       Self.Tok = Toks.front();
     }
+
+    for (auto Map : OldLookupMaps)
+      Map.first->enableLookupTracking(Map.second);
   }
 
 private:
   Parser &Self;
   CachedTokens Toks;
   tok::TokenKind EndKind;
+
+  llvm::SmallVector<std::pair<Scope *, Scope::EmptyLookupMap *>, 4>
+      OldLookupMaps;
 };
 
 /// ConsumeAndStoreInitializer - Consume and store the token at the passed token
Index: clang/include/clang/Sema/SemaInternal.h
===================================================================
--- clang/include/clang/Sema/SemaInternal.h
+++ clang/include/clang/Sema/SemaInternal.h
@@ -103,6 +103,7 @@
         Namespaces(SemaRef.Context, SemaRef.CurContext, SS),
         EnteringContext(EnteringContext), SearchNamespaces(false) {
     Result.suppressDiagnostics();
+    Result.setSynthetic();
     // Arrange for ValidatedCorrections[0] to always be an empty correction.
     ValidatedCorrections.push_back(TypoCorrection());
   }
Index: clang/include/clang/Sema/Scope.h
===================================================================
--- clang/include/clang/Sema/Scope.h
+++ clang/include/clang/Sema/Scope.h
@@ -14,7 +14,9 @@
 #define LLVM_CLANG_SEMA_SCOPE_H
 
 #include "clang/AST/Decl.h"
+#include "clang/AST/DeclarationName.h"
 #include "clang/Basic/Diagnostic.h"
+#include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/PointerIntPair.h"
 #include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/ADT/SmallVector.h"
@@ -136,6 +138,21 @@
     CatchScope = 0x1000000,
   };
 
+  /// A lookup that was performed in this scope and that found nothing here.
+  struct EmptyLookup {
+    /// The point of first use.
+    SourceLocation FirstUseLoc;
+    /// The Decl::IdentifierNamespace mask for the first use.
+    unsigned FirstIDNS : 16;
+    /// The Decl::IdentifierNamespace mask for all uses of this name in this
+    /// scope. Guaranteed to be a superset of FirstIDNS.
+    unsigned CumulativeIDNS : 16;
+    /// Determine whether this represents a lookup that has been attempted and
+    /// failed (\c true) or a lookup that has never been attempted (\c false).
+    explicit operator bool() const { return FirstUseLoc.isValid(); }
+  };
+  using EmptyLookupMap = llvm::SmallDenseMap<DeclarationName, EmptyLookup, 16>;
+
 private:
   /// The parent scope for this scope.  This is null for the translation-unit
   /// scope.
@@ -208,8 +225,17 @@
   /// this scope, or over-defined. The bit is true when over-defined.
   llvm::PointerIntPair<VarDecl *, 1, bool> NRVO;
 
+  /// DeclarationNames that have been looked up in this scope and not found,
+  /// for scopes where that is tracked. This is tracked in C++ class scopes in
+  /// order to diagnose cases where a class member is declared after that
+  /// identifier has already been used with a different meaning.
+  EmptyLookupMap *EmptyLookups = nullptr;
+
   void setFlags(Scope *Parent, unsigned F);
 
+  void noteEmptyLookupImpl(DeclarationName N, SourceLocation UseLoc,
+                           unsigned IDNS);
+
 public:
   Scope(Scope *Parent, unsigned ScopeFlags, DiagnosticsEngine &Diag)
       : ErrorTrap(Diag) {
@@ -278,6 +304,45 @@
     return PrototypeIndex++;
   }
 
+  /// For a class scope, enable tracking of members that are declared after
+  /// their names have already been looked up. This is ill-formed by
+  /// C++ [basic.scope.class]p2.
+  void enableLookupTracking(EmptyLookupMap *Map) {
+    assert(!EmptyLookups && "lookup map set twice");
+    if (!Map)
+      return;
+
+    assert((getFlags() & ClassScope) && "not a class scope");
+    EmptyLookups = Map;
+  }
+
+  EmptyLookupMap *disableLookupTracking() {
+    auto *Map = EmptyLookups;
+    EmptyLookups = nullptr;
+    return Map;
+  }
+
+  /// Note that the given name was looked up within this scope, and nothing was
+  /// found.
+  void noteEmptyLookup(DeclarationName N, SourceLocation UseLoc,
+                       unsigned IDNS) {
+    if (EmptyLookups)
+      noteEmptyLookupImpl(N, UseLoc, IDNS);
+  }
+
+  /// Determine whether a lookup for name N in this scope was attempted and
+  /// found nothing, and if so, what kind of name was looked for and where.
+  EmptyLookup getEmptyLookupForName(DeclarationName N) {
+    return EmptyLookups ? EmptyLookups->lookup(N) : EmptyLookup();
+  }
+
+  // Forget about an empty lookup from a given location (to avoid duplicate
+  // diagnostics).
+  void eraseEmptyLookup(DeclarationName N) {
+    if (EmptyLookups)
+      EmptyLookups->erase(N);
+  }
+
   using decl_range = llvm::iterator_range<DeclSetTy::iterator>;
 
   decl_range decls() const {
Index: clang/include/clang/Sema/Lookup.h
===================================================================
--- clang/include/clang/Sema/Lookup.h
+++ clang/include/clang/Sema/Lookup.h
@@ -298,6 +298,12 @@
 
   bool isTemplateNameLookup() const { return TemplateNameLookup; }
 
+  /// Sets that this is corresponds to a synthetic name lookup (for example,
+  /// during typo correction) rather than any source-level syntax.
+  void setSynthetic() { Synthetic = true; }
+
+  bool isSynthetic() { return Synthetic; }
+
   bool isAmbiguous() const {
     return getResultKind() == Ambiguous;
   }
@@ -378,6 +384,11 @@
     return IDNS;
   }
 
+  /// Override the identifier namespace for this lookup. This should generally
+  /// not be used; the identifier namespace should instead be inferred from the
+  /// LookupNameKind.
+  void setIdentifierNamespace(unsigned IDNS) { this->IDNS = IDNS; }
+
   /// Returns whether these results arose from performing a
   /// lookup into a class.
   bool isClassLookup() const {
@@ -754,6 +765,11 @@
 
   /// True if we're looking up a template-name.
   bool TemplateNameLookup = false;
+
+  /// True if this is a synthetic name lookup (one generated internally and not
+  /// part of the interpretation of the program, such as during typo
+  /// correction).
+  bool Synthetic = false;
 };
 
 /// Consumes visible declarations found when searching for
Index: clang/include/clang/Parse/Parser.h
===================================================================
--- clang/include/clang/Parse/Parser.h
+++ clang/include/clang/Parse/Parser.h
@@ -1412,6 +1412,7 @@
     Parser &P;
     bool Popped;
     Sema::ParsingClassState State;
+    Scope::EmptyLookupMap EmptyLookups;
 
   public:
     ParsingClassDefinition(Parser &P, Decl *TagOrTemplate, bool TopLevelClass,
@@ -1420,9 +1421,22 @@
         State(P.PushParsingClass(TagOrTemplate, TopLevelClass, IsInterface)) {
     }
 
-    /// Pop this class of the stack.
+    void startBody() {
+      assert(!Popped);
+      assert(P.getCurScope()->isClassScope());
+      P.getCurScope()->enableLookupTracking(&EmptyLookups);
+    }
+
+    void finishBody() {
+      assert(!Popped);
+      assert(P.getCurScope()->isClassScope());
+      P.getCurScope()->disableLookupTracking();
+    }
+
+    /// Pop this class off the stack.
     void Pop() {
       assert(!Popped && "Nested class has already been popped");
+      finishBody();
       Popped = true;
       P.PopParsingClass(State);
     }
Index: clang/include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1761,6 +1761,21 @@
   InGroup<MicrosoftPureDefinition>;
 def err_qualified_member_of_unrelated : Error<
   "%q0 is not a member of class %1">;
+def ext_member_declared_too_late : ExtWarn<
+  "name %0 refers to a member of class %1 that has not yet been declared">,
+  InGroup<MemberShadowsUse>;
+def note_tag_decl_injects_non_member : Note<
+  "declaration injects non-member %q0; "
+  "%select{use a standalone forward declaration to introduce a class member|"
+  "if a member was intended, remove this friend declaration (befriending a "
+  "member has no effect)}1">;
+def note_member_declared_too_late_qualify : Note<
+  "use a qualified name to refer to member of %0">;
+def note_member_declared_too_late : Note<
+  "member %0 declared here after its point of use">;
+def ext_member_declared_too_late_multi_idns : ExtWarn<
+  "declaration of member %0 after its name has already been used "
+  "within the definition of class %1">, InGroup<MemberShadowsUse>;
 
 def err_member_function_call_bad_cvr : Error<
   "'this' argument to member function %0 has type %1, but function is not marked "
Index: clang/include/clang/Basic/DiagnosticGroups.td
===================================================================
--- clang/include/clang/Basic/DiagnosticGroups.td
+++ clang/include/clang/Basic/DiagnosticGroups.td
@@ -412,6 +412,7 @@
 def CXX11LongLong : DiagGroup<"c++11-long-long">;
 def LongLong : DiagGroup<"long-long", [CXX11LongLong]>;
 def ImplicitlyUnsignedLiteral : DiagGroup<"implicitly-unsigned-literal">;
+def MemberShadowsUse : DiagGroup<"member-shadows-use">;
 def MethodSignatures : DiagGroup<"method-signatures">;
 def MismatchedParameterTypes : DiagGroup<"mismatched-parameter-types">;
 def MismatchedReturnTypes : DiagGroup<"mismatched-return-types">;
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
  • [PATCH] D80977: D... Richard Smith - zygoloid via Phabricator via cfe-commits

Reply via email to