From b5c7768a74936d4e2c7a484570a638cb74702d8b Mon Sep 17 00:00:00 2001 From: Kaelyn Uhrain Date: Sat, 19 Oct 2013 00:05:00 +0000 Subject: [PATCH] Allow CorrectTypo to replace CXXScopeSpecifiers that refer to classes. Now that CorrectTypo knows how to correctly search classes for typo correction candidates, there is no good reason to only replace an existing CXXScopeSpecifier if it refers to a namespace. While the actual enablement was a matter of changing a single comparison, the fallout from enabling the functionality required a lot more code changes (including my two previous commits). git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@193020 91177308-0d34-0410-b5e6-96231b3b80d8 --- lib/Sema/SemaDeclCXX.cpp | 13 +++++-- lib/Sema/SemaExprMember.cpp | 35 +++++++++++++++++-- lib/Sema/SemaLookup.cpp | 14 +++++++- test/CXX/class.access/class.friend/p1.cpp | 24 ++++++------- test/CXX/class.access/p6.cpp | 4 +-- .../temp.class/temp.mem.enum/p1.cpp | 6 ++-- test/Parser/cxx-using-directive.cpp | 4 +-- test/Parser/switch-recovery.cpp | 6 ++-- test/SemaCXX/missing-members.cpp | 10 ++++-- test/SemaCXX/typo-correction-pt2.cpp | 13 +++++++ test/SemaCXX/typo-correction.cpp | 12 ++++--- test/SemaTemplate/temp_arg_nontype.cpp | 16 ++++----- 12 files changed, 113 insertions(+), 44 deletions(-) diff --git a/lib/Sema/SemaDeclCXX.cpp b/lib/Sema/SemaDeclCXX.cpp index ed5724b88f..f5bb3511f2 100644 --- a/lib/Sema/SemaDeclCXX.cpp +++ b/lib/Sema/SemaDeclCXX.cpp @@ -7301,9 +7301,10 @@ void Sema::HideUsingShadowDecl(Scope *S, UsingShadowDecl *Shadow) { namespace { class UsingValidatorCCC : public CorrectionCandidateCallback { public: - UsingValidatorCCC(bool HasTypenameKeyword, bool IsInstantiation) + UsingValidatorCCC(bool HasTypenameKeyword, bool IsInstantiation, + bool RequireMember) : HasTypenameKeyword(HasTypenameKeyword), - IsInstantiation(IsInstantiation) {} + IsInstantiation(IsInstantiation), RequireMember(RequireMember) {} bool ValidateCandidate(const TypoCorrection &Candidate) LLVM_OVERRIDE { NamedDecl *ND = Candidate.getCorrectionDecl(); @@ -7312,6 +7313,10 @@ public: if (!ND || isa(ND)) return false; + if (RequireMember && !isa(ND) && !isa(ND) && + !isa(ND)) + return false; + // Completely unqualified names are invalid for a 'using' declaration. if (Candidate.WillReplaceSpecifier() && !Candidate.getCorrectionSpecifier()) return false; @@ -7325,6 +7330,7 @@ public: private: bool HasTypenameKeyword; bool IsInstantiation; + bool RequireMember; }; } // end anonymous namespace @@ -7440,7 +7446,8 @@ NamedDecl *Sema::BuildUsingDeclaration(Scope *S, AccessSpecifier AS, // Try to correct typos if possible. if (R.empty()) { - UsingValidatorCCC CCC(HasTypenameKeyword, IsInstantiation); + UsingValidatorCCC CCC(HasTypenameKeyword, IsInstantiation, + CurContext->isRecord()); if (TypoCorrection Corrected = CorrectTypo(R.getLookupNameInfo(), R.getLookupKind(), S, &SS, CCC)){ // We reject any correction for which ND would be NULL. diff --git a/lib/Sema/SemaExprMember.cpp b/lib/Sema/SemaExprMember.cpp index 5970120d01..e9cd537cc8 100644 --- a/lib/Sema/SemaExprMember.cpp +++ b/lib/Sema/SemaExprMember.cpp @@ -538,13 +538,42 @@ bool Sema::CheckQualifiedMemberReference(Expr *BaseExpr, namespace { // Callback to only accept typo corrections that are either a ValueDecl or a -// FunctionTemplateDecl. +// FunctionTemplateDecl and are declared in the current record or, for a C++ +// classes, one of its base classes. class RecordMemberExprValidatorCCC : public CorrectionCandidateCallback { public: + explicit RecordMemberExprValidatorCCC(const RecordType *RTy) + : Record(RTy->getDecl()) {} + virtual bool ValidateCandidate(const TypoCorrection &candidate) { NamedDecl *ND = candidate.getCorrectionDecl(); - return ND && (isa(ND) || isa(ND)); + // Don't accept candidates that cannot be member functions, constants, + // variables, or templates. + if (!ND || !(isa(ND) || isa(ND))) + return false; + + // Accept candidates that occur in the current record. + if (Record->containsDecl(ND)) + return true; + + if (const CXXRecordDecl *RD = dyn_cast(Record)) { + // Accept candidates that occur in any of the current class' base classes. + for (CXXRecordDecl::base_class_const_iterator BS = RD->bases_begin(), + BSEnd = RD->bases_end(); + BS != BSEnd; ++BS) { + if (const RecordType *BSTy = dyn_cast_or_null( + BS->getType().getTypePtrOrNull())) { + if (BSTy->getDecl()->containsDecl(ND)) + return true; + } + } + } + + return false; } + + private: + const RecordDecl *const Record; }; } @@ -600,7 +629,7 @@ LookupMemberExprInRecord(Sema &SemaRef, LookupResult &R, // We didn't find anything with the given name, so try to correct // for typos. DeclarationName Name = R.getLookupName(); - RecordMemberExprValidatorCCC Validator; + RecordMemberExprValidatorCCC Validator(RTy); TypoCorrection Corrected = SemaRef.CorrectTypo(R.getLookupNameInfo(), R.getLookupKind(), NULL, &SS, Validator, DC); diff --git a/lib/Sema/SemaLookup.cpp b/lib/Sema/SemaLookup.cpp index bb1150fc72..cca0f22f14 100644 --- a/lib/Sema/SemaLookup.cpp +++ b/lib/Sema/SemaLookup.cpp @@ -4157,7 +4157,7 @@ TypoCorrection Sema::CorrectTypo(const DeclarationNameInfo &TypoName, // corrections. bool SearchNamespaces = getLangOpts().CPlusPlus && - (IsUnqualifiedLookup || (QualifiedDC && QualifiedDC->isNamespace())); + (IsUnqualifiedLookup || (SS && SS->isSet())); // In a few cases we *only* want to search for corrections based on just // adding or changing the nested name specifier. unsigned TypoLen = Typo->getName().size(); @@ -4400,6 +4400,18 @@ retry_lookup: switch (TmpRes.getResultKind()) { case LookupResult::Found: case LookupResult::FoundOverloaded: { + if (SS && SS->isValid()) { + std::string NewQualified = TC.getAsString(getLangOpts()); + std::string OldQualified; + llvm::raw_string_ostream OldOStream(OldQualified); + SS->getScopeRep()->print(OldOStream, getPrintingPolicy()); + OldOStream << TypoName; + // If correction candidate would be an identical written qualified + // identifer, then the existing CXXScopeSpec probably included a + // typedef that didn't get accounted for properly. + if (OldOStream.str() == NewQualified) + break; + } for (LookupResult::iterator TRD = TmpRes.begin(), TRDEnd = TmpRes.end(); TRD != TRDEnd; ++TRD) { diff --git a/test/CXX/class.access/class.friend/p1.cpp b/test/CXX/class.access/class.friend/p1.cpp index 1a519dcc3e..4a681629ee 100644 --- a/test/CXX/class.access/class.friend/p1.cpp +++ b/test/CXX/class.access/class.friend/p1.cpp @@ -7,8 +7,8 @@ // special access rights to the friends, but they do not make the nominated // friends members of the befriending class. -struct S { static void f(); }; -S* g() { return 0; } +struct S { static void f(); }; // expected-note 2 {{'S' declared here}} +S* g() { return 0; } // expected-note 2 {{'g' declared here}} struct X { friend struct S; @@ -19,8 +19,8 @@ void test1() { S s; g()->f(); S::f(); - X::g(); // expected-error{{no member named 'g' in 'X'}} - X::S x_s; // expected-error{{no type named 'S' in 'X'}} + X::g(); // expected-error{{no member named 'g' in 'X'; did you mean simply 'g'?}} + X::S x_s; // expected-error{{no type named 'S' in 'X'; did you mean simply 'S'?}} X x; x.g(); // expected-error{{no member named 'g' in 'X'}} } @@ -36,24 +36,24 @@ namespace N { friend struct S2* g2(); }; - struct S2 { static void f2(); }; - S2* g2() { return 0; } + struct S2 { static void f2(); }; // expected-note 2 {{'S2' declared here}} + S2* g2() { return 0; } // expected-note 2 {{'g2' declared here}} void test() { g()->f(); S s; S::f(); - X::g(); // expected-error{{no member named 'g' in 'N::X'}} - X::S x_s; // expected-error{{no type named 'S' in 'N::X'}} + X::g(); // expected-error{{no member named 'g' in 'N::X'; did you mean simply 'g'?}} + X::S x_s; // expected-error{{no type named 'S' in 'N::X'; did you mean simply 'S'?}} X x; x.g(); // expected-error{{no member named 'g' in 'N::X'}} g2(); S2 s2; - ::g2(); // expected-error{{no member named 'g2' in the global namespace}} - ::S2 g_s2; // expected-error{{no type named 'S2' in the global namespace}} - X::g2(); // expected-error{{no member named 'g2' in 'N::X'}} - X::S2 x_s2; // expected-error{{no type named 'S2' in 'N::X'}} + ::g2(); // expected-error{{no member named 'g2' in the global namespace; did you mean simply 'g2'?}} + ::S2 g_s2; // expected-error{{no type named 'S2' in the global namespace; did you mean simply 'S2'?}} + X::g2(); // expected-error{{no member named 'g2' in 'N::X'; did you mean simply 'g2'?}} + X::S2 x_s2; // expected-error{{no type named 'S2' in 'N::X'; did you mean simply 'S2'?}} x.g2(); // expected-error{{no member named 'g2' in 'N::X'}} } } diff --git a/test/CXX/class.access/p6.cpp b/test/CXX/class.access/p6.cpp index fbdc87b24e..6a93658fc7 100644 --- a/test/CXX/class.access/p6.cpp +++ b/test/CXX/class.access/p6.cpp @@ -92,7 +92,7 @@ namespace test3 { template class Outer::A { public: - static void foo(); + static void foo(); // expected-note {{'Outer::A::foo' declared here}} }; class B { @@ -102,7 +102,7 @@ namespace test3 { void test() { Outer::A::foo(); - Outer::A::foo(); // expected-error {{no member named 'foo'}} + Outer::A::foo(); // expected-error {{no member named 'foo' in 'test3::Outer::A'; did you mean 'Outer::A::foo'?}} } } diff --git a/test/CXX/temp/temp.decls/temp.class/temp.mem.enum/p1.cpp b/test/CXX/temp/temp.decls/temp.class/temp.mem.enum/p1.cpp index f8cc009474..2884be146c 100644 --- a/test/CXX/temp/temp.decls/temp.class/temp.mem.enum/p1.cpp +++ b/test/CXX/temp/temp.decls/temp.class/temp.mem.enum/p1.cpp @@ -12,7 +12,7 @@ A a; A::E a0 = A().v; int n = A::E::e1; // expected-error {{implicit instantiation of undefined member}} -template enum A::E : T { e1, e2 }; +template enum A::E : T { e1, e2 }; // expected-note 2 {{declared here}} // FIXME: Now that A::E is defined, we are supposed to inject its enumerators // into the already-instantiated class A. This seems like a really bad idea, @@ -20,7 +20,7 @@ template enum A::E : T { e1, e2 }; // // Either do as the standard says, or only include enumerators lexically defined // within the class in its scope. -A::E a1 = A::e1; // expected-error {{no member named 'e1' in 'A'}} +A::E a1 = A::e1; // expected-error {{no member named 'e1' in 'A'; did you mean simply 'e1'?}} A::E a2 = A::e2; @@ -94,7 +94,7 @@ D::E d1 = D::E::e1; // expected-error {{incomplete type 'D::E'}} template<> enum class D::E { e2 }; D::E d2 = D::E::e2; D::E d3 = D::E::e1; // expected-note {{first required here}} -D::E d4 = D::E::e2; // expected-error {{no member named 'e2'}} +D::E d4 = D::E::e2; // expected-error {{no member named 'e2' in 'D::E'; did you mean simply 'e2'?}} template<> enum class D::E { e3 }; // expected-error {{explicit specialization of 'E' after instantiation}} template<> enum class D::E; diff --git a/test/Parser/cxx-using-directive.cpp b/test/Parser/cxx-using-directive.cpp index 76dc22f153..5efd991c8e 100644 --- a/test/Parser/cxx-using-directive.cpp +++ b/test/Parser/cxx-using-directive.cpp @@ -4,7 +4,7 @@ class A {}; namespace B { namespace A {} // expected-note{{namespace '::B::A' defined here}} \ - // expected-note{{namespace 'B::A' defined here}} + // expected-note 2{{namespace 'B::A' defined here}} using namespace A ; } @@ -28,7 +28,7 @@ namespace D { using namespace ! ; // expected-error{{expected namespace name}} using namespace A ; // expected-error{{no namespace named 'A'; did you mean 'B::A'?}} -using namespace ::A // expected-error{{expected namespace name}} \ +using namespace ::A // expected-error{{no namespace named 'A' in the global namespace; did you mean 'B::A'?}} \ // expected-error{{expected ';' after namespace name}} B ; diff --git a/test/Parser/switch-recovery.cpp b/test/Parser/switch-recovery.cpp index 84ac0c899e..63b580202a 100644 --- a/test/Parser/switch-recovery.cpp +++ b/test/Parser/switch-recovery.cpp @@ -95,7 +95,7 @@ int test8( foo x ) { } // Stress test to make sure Clang doesn't crash. -void test9(int x) { +void test9(int x) { // expected-note {{'x' declared here}} switch(x) { case 1: return; 2: case; // expected-error {{expected 'case' keyword before expression}} \ @@ -104,8 +104,8 @@ void test9(int x) { 7: :x; // expected-error {{expected 'case' keyword before expression}} \ expected-error {{expected expression}} 8:: x; // expected-error {{expected ';' after expression}} \ - expected-error {{no member named 'x' in the global namespace}} \ - expected-warning {{expression result unused}} + expected-error {{no member named 'x' in the global namespace; did you mean simply 'x'?}} \ + expected-warning 2 {{expression result unused}} 9:: :y; // expected-error {{expected ';' after expression}} \ expected-error {{expected unqualified-id}} \ expected-warning {{expression result unused}} diff --git a/test/SemaCXX/missing-members.cpp b/test/SemaCXX/missing-members.cpp index 529ba1023d..619bc61f25 100644 --- a/test/SemaCXX/missing-members.cpp +++ b/test/SemaCXX/missing-members.cpp @@ -1,7 +1,7 @@ // RUN: %clang_cc1 -fsyntax-only -verify %s namespace A { namespace B { - class C { }; + class C { }; // expected-note 2 {{'A::B::C' declared here}} struct S { }; union U { }; } @@ -19,8 +19,12 @@ namespace B { void g() { A::B::D::E; // expected-error {{no member named 'D' in namespace 'A::B'}} - B::B::C::D; // expected-error {{no member named 'C' in 'B::B'}} - ::C::D; // expected-error {{no member named 'C' in the global namespace}} + // FIXME: The typo corrections below should be suppressed since A::B::C + // doesn't have a member named D. + B::B::C::D; // expected-error {{no member named 'C' in 'B::B'; did you mean 'A::B::C'?}} \ + // expected-error {{no member named 'D' in 'A::B::C'}} + ::C::D; // expected-error {{no member named 'C' in the global namespace; did you mean 'A::B::C'?}}\ + // expected-error {{no member named 'D' in 'A::B::C'}} } int A::B::i = 10; // expected-error {{no member named 'i' in namespace 'A::B'}} diff --git a/test/SemaCXX/typo-correction-pt2.cpp b/test/SemaCXX/typo-correction-pt2.cpp index d22a8e92e0..2da52b31f5 100644 --- a/test/SemaCXX/typo-correction-pt2.cpp +++ b/test/SemaCXX/typo-correction-pt2.cpp @@ -168,3 +168,16 @@ namespace PR17019 { evil Q(0); // expected-note {{in instantiation of member function}} } } + +namespace fix_class_name_qualifier { +class MessageHeaders {}; +class MessageUtils { + public: + static void ParseMessageHeaders(int, int); // expected-note {{'MessageUtils::ParseMessageHeaders' declared here}} +}; + +void test() { + // No, we didn't mean to call MessageHeaders::MessageHeaders. + MessageHeaders::ParseMessageHeaders(5, 4); // expected-error {{no member named 'ParseMessageHeaders' in 'fix_class_name_qualifier::MessageHeaders'; did you mean 'MessageUtils::ParseMessageHeaders'?}} +} +} diff --git a/test/SemaCXX/typo-correction.cpp b/test/SemaCXX/typo-correction.cpp index d779e2a448..4047e6a18c 100644 --- a/test/SemaCXX/typo-correction.cpp +++ b/test/SemaCXX/typo-correction.cpp @@ -217,10 +217,14 @@ namespace PR13051 { operator bool() const; }; - void f() { - f(&S::tempalte f); // expected-error{{did you mean 'template'?}} - f(&S::opeartor bool); // expected-error{{did you mean 'operator'?}} - f(&S::foo); // expected-error-re{{no member named 'foo' in 'PR13051::S'$}} + void foo(); // expected-note{{'foo' declared here}} + void g(void(*)()); + void g(bool(S::*)() const); + + void test() { + g(&S::tempalte f); // expected-error{{did you mean 'template'?}} + g(&S::opeartor bool); // expected-error{{did you mean 'operator'?}} + g(&S::foo); // expected-error{{no member named 'foo' in 'PR13051::S'; did you mean simply 'foo'?}} } } diff --git a/test/SemaTemplate/temp_arg_nontype.cpp b/test/SemaTemplate/temp_arg_nontype.cpp index 24509524b2..4a75b11c42 100644 --- a/test/SemaTemplate/temp_arg_nontype.cpp +++ b/test/SemaTemplate/temp_arg_nontype.cpp @@ -252,16 +252,16 @@ namespace PR8372 { namespace PR9227 { template struct enable_if_bool { }; - template <> struct enable_if_bool { typedef int type; }; - void test_bool() { enable_if_bool::type i; } // expected-error{{enable_if_bool}} + template <> struct enable_if_bool { typedef int type; }; // expected-note{{'enable_if_bool::type' declared here}} + void test_bool() { enable_if_bool::type i; } // expected-error{{enable_if_bool'; did you mean 'enable_if_bool::type'?}} template struct enable_if_char { }; - template <> struct enable_if_char<'a'> { typedef int type; }; - void test_char_0() { enable_if_char<0>::type i; } // expected-error{{enable_if_char<'\x00'>}} - void test_char_b() { enable_if_char<'b'>::type i; } // expected-error{{enable_if_char<'b'>}} - void test_char_possibly_negative() { enable_if_char<'\x02'>::type i; } // expected-error{{enable_if_char<'\x02'>}} - void test_char_single_quote() { enable_if_char<'\''>::type i; } // expected-error{{enable_if_char<'\''>}} - void test_char_backslash() { enable_if_char<'\\'>::type i; } // expected-error{{enable_if_char<'\\'>}} + template <> struct enable_if_char<'a'> { typedef int type; }; // expected-note 5{{'enable_if_char<'a'>::type' declared here}} + void test_char_0() { enable_if_char<0>::type i; } // expected-error{{enable_if_char<'\x00'>'; did you mean 'enable_if_char<'a'>::type'?}} + void test_char_b() { enable_if_char<'b'>::type i; } // expected-error{{enable_if_char<'b'>'; did you mean 'enable_if_char<'a'>::type'?}} + void test_char_possibly_negative() { enable_if_char<'\x02'>::type i; } // expected-error{{enable_if_char<'\x02'>'; did you mean 'enable_if_char<'a'>::type'?}} + void test_char_single_quote() { enable_if_char<'\''>::type i; } // expected-error{{enable_if_char<'\''>'; did you mean 'enable_if_char<'a'>::type'?}} + void test_char_backslash() { enable_if_char<'\\'>::type i; } // expected-error{{enable_if_char<'\\'>'; did you mean 'enable_if_char<'a'>::type'?}} } namespace PR10579 { -- 2.40.0