]> granicus.if.org Git - clang/commitdiff
Teach typo correction about various language keywords. We can't
authorDouglas Gregor <dgregor@apple.com>
Wed, 14 Apr 2010 20:04:41 +0000 (20:04 +0000)
committerDouglas Gregor <dgregor@apple.com>
Wed, 14 Apr 2010 20:04:41 +0000 (20:04 +0000)
generally recover from typos in keywords (since we would effectively
have to mangle the token stream). However, there are still benefits to
typo-correcting with keywords:
  - We don't make stupid suggestions when the user typed something
  that is similar to a keyword.
  - We can suggest the keyword in a diagnostic (did you mean
  "static_cast"?), even if we can't recover and therefore don't have
  a fix-it.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@101274 91177308-0d34-0410-b5e6-96231b3b80d8

13 files changed:
lib/Sema/Sema.h
lib/Sema/SemaCXXScopeSpec.cpp
lib/Sema/SemaDecl.cpp
lib/Sema/SemaDeclCXX.cpp
lib/Sema/SemaDeclObjC.cpp
lib/Sema/SemaExpr.cpp
lib/Sema/SemaExprObjC.cpp
lib/Sema/SemaInit.cpp
lib/Sema/SemaLookup.cpp
lib/Sema/SemaTemplate.cpp
test/FixIt/fixit-unrecoverable.c [new file with mode: 0644]
test/FixIt/fixit-unrecoverable.cpp [new file with mode: 0644]
test/FixIt/typo.m

index d198cf9d2b8da85243d453c7e1f7650ece0f1c3e..eea0a2bf9e67db05e22b5419c00ec02bf846b24a 100644 (file)
@@ -1431,9 +1431,33 @@ public:
   void LookupVisibleDecls(DeclContext *Ctx, LookupNameKind Kind,
                           VisibleDeclConsumer &Consumer);
 
+  /// \brief The context in which typo-correction occurs.
+  ///
+  /// The typo-correction context affects which keywords (if any) are 
+  /// considered when trying to correct for typos.
+  enum CorrectTypoContext {
+    /// \brief An unknown context, where any keyword might be valid.
+    CTC_Unknown,
+    /// \brief A context where no keywords are used (e.g. we expect an actual
+    /// name).
+    CTC_NoKeywords,
+    /// \brief A context where we're correcting a type name.
+    CTC_Type,
+    /// \brief An expression context.
+    CTC_Expression,
+    /// \brief A type cast, or anything else that can be followed by a '<'. 
+    CTC_CXXCasts,
+    /// \brief A member lookup context.
+    CTC_MemberLookup,
+    /// \brief The receiver of an Objective-C message send within an 
+    /// Objective-C method where 'super' is a valid keyword.
+    CTC_ObjCMessageReceiver
+  };
+  
   DeclarationName CorrectTypo(LookupResult &R, Scope *S, CXXScopeSpec *SS,
                               DeclContext *MemberContext = 0,
                               bool EnteringContext = false,
+                              CorrectTypoContext CTC = CTC_Unknown,
                               const ObjCObjectPointerType *OPT = 0);
 
   void FindAssociatedClassesAndNamespaces(Expr **Args, unsigned NumArgs,
index 81b3e3116ae8b3d64479b57f12c542ac591a91ea..e9d206e53be0260bf4a88c060d165cc1fe9f0a8f 100644 (file)
@@ -483,7 +483,8 @@ Sema::CXXScopeTy *Sema::BuildCXXNestedNameSpecifier(Scope *S,
     // We haven't found anything, and we're not recovering from a
     // different kind of error, so look for typos.
     DeclarationName Name = Found.getLookupName();
-    if (CorrectTypo(Found, S, &SS, LookupCtx, EnteringContext) &&
+    if (CorrectTypo(Found, S, &SS, LookupCtx, EnteringContext,  
+                    CTC_NoKeywords) &&
         Found.isSingleResult() &&
         isAcceptableNestedNameSpecifier(Found.getAsSingle<NamedDecl>())) {
       if (LookupCtx)
index a2d0e187285635eaec1fd802a156c53ee96e976e..8e46b9b657624bffaef70ee52105612b6ba2e4c1 100644 (file)
@@ -246,32 +246,36 @@ bool Sema::DiagnoseUnknownTypeName(const IdentifierInfo &II,
   LookupResult Lookup(*this, &II, IILoc, LookupOrdinaryName, 
                       NotForRedeclaration);
 
-  // FIXME: It would be nice if we could correct for typos in built-in
-  // names, such as "itn" for "int".
-
-  if (CorrectTypo(Lookup, S, SS) && Lookup.isSingleResult()) {
-    NamedDecl *Result = Lookup.getAsSingle<NamedDecl>();
-    if ((isa<TypeDecl>(Result) || isa<ObjCInterfaceDecl>(Result)) &&
-        !Result->isInvalidDecl()) {
-      // We found a similarly-named type or interface; suggest that.
-      if (!SS || !SS->isSet())
-        Diag(IILoc, diag::err_unknown_typename_suggest)
-          << &II << Lookup.getLookupName()
-          << FixItHint::CreateReplacement(SourceRange(IILoc),
-                                          Result->getNameAsString());
-      else if (DeclContext *DC = computeDeclContext(*SS, false))
-        Diag(IILoc, diag::err_unknown_nested_typename_suggest) 
-          << &II << DC << Lookup.getLookupName() << SS->getRange()
-          << FixItHint::CreateReplacement(SourceRange(IILoc),
-                                          Result->getNameAsString());
-      else
-        llvm_unreachable("could not have corrected a typo here");
+  if (DeclarationName Corrected = CorrectTypo(Lookup, S, SS, 0, 0, CTC_Type)) {
+    if (NamedDecl *Result = Lookup.getAsSingle<NamedDecl>()) {
+      if ((isa<TypeDecl>(Result) || isa<ObjCInterfaceDecl>(Result)) &&
+          !Result->isInvalidDecl()) {
+        // We found a similarly-named type or interface; suggest that.
+        if (!SS || !SS->isSet())
+          Diag(IILoc, diag::err_unknown_typename_suggest)
+            << &II << Lookup.getLookupName()
+            << FixItHint::CreateReplacement(SourceRange(IILoc),
+                                            Result->getNameAsString());
+        else if (DeclContext *DC = computeDeclContext(*SS, false))
+          Diag(IILoc, diag::err_unknown_nested_typename_suggest) 
+            << &II << DC << Lookup.getLookupName() << SS->getRange()
+            << FixItHint::CreateReplacement(SourceRange(IILoc),
+                                            Result->getNameAsString());
+        else
+          llvm_unreachable("could not have corrected a typo here");
 
-      Diag(Result->getLocation(), diag::note_previous_decl)
-        << Result->getDeclName();
-      
-      SuggestedType = getTypeName(*Result->getIdentifier(), IILoc, S, SS);
-      return true;
+        Diag(Result->getLocation(), diag::note_previous_decl)
+          << Result->getDeclName();
+        
+        SuggestedType = getTypeName(*Result->getIdentifier(), IILoc, S, SS);
+        return true;
+      }
+    } else if (Lookup.empty()) {
+      // We corrected to a keyword.
+      // FIXME: Actually recover with the keyword we suggest, and emit a fix-it.
+      Diag(IILoc, diag::err_unknown_typename_suggest)
+        << &II << Corrected;
+      return true;      
     }
   }
 
@@ -605,7 +609,7 @@ ObjCInterfaceDecl *Sema::getObjCInterfaceDecl(IdentifierInfo *&Id,
     // Perform typo correction at the given location, but only if we
     // find an Objective-C class name.
     LookupResult R(*this, Id, RecoverLoc, LookupOrdinaryName);
-    if (CorrectTypo(R, TUScope, 0) &&
+    if (CorrectTypo(R, TUScope, 0, 0, false, CTC_NoKeywords) &&
         (IDecl = R.getAsSingle<ObjCInterfaceDecl>())) {
       Diag(RecoverLoc, diag::err_undef_interface_suggest)
         << Id << IDecl->getDeclName() 
index 214d57c7dce338f39330cf642d9b11ad2d011e28..24695f35cc232539f210b2cebed554e3e0c68d8d 100644 (file)
@@ -1099,7 +1099,8 @@ Sema::ActOnMemInitializer(DeclPtrTy ConstructorD,
 
       // If no results were found, try to correct typos.
       if (R.empty() && BaseType.isNull() &&
-          CorrectTypo(R, S, &SS, ClassDecl) && R.isSingleResult()) {
+          CorrectTypo(R, S, &SS, ClassDecl, 0, CTC_NoKeywords) && 
+          R.isSingleResult()) {
         if (FieldDecl *Member = R.getAsSingle<FieldDecl>()) {
           if (Member->getDeclContext()->getLookupContext()->Equals(ClassDecl)) {
             // We have found a non-static data member with a similar
index eb3f4222b695120da7243b11fe287be4b9b98a50..f56a0b196eff0ad6b6b15a060897d494e99f5799 100644 (file)
@@ -120,7 +120,7 @@ ActOnStartClassInterface(SourceLocation AtInterfaceLoc,
     if (!PrevDecl) {
       // Try to correct for a typo in the superclass name.
       LookupResult R(*this, SuperName, SuperLoc, LookupOrdinaryName);
-      if (CorrectTypo(R, TUScope, 0) &&
+      if (CorrectTypo(R, TUScope, 0, 0, false, CTC_NoKeywords) &&
           (PrevDecl = R.getAsSingle<ObjCInterfaceDecl>())) {
         Diag(SuperLoc, diag::err_undef_superclass_suggest)
           << SuperName << ClassName << PrevDecl->getDeclName();
@@ -317,7 +317,7 @@ Sema::FindProtocolDeclaration(bool WarnOnDeclarations,
     if (!PDecl) {
       LookupResult R(*this, ProtocolId[i].first, ProtocolId[i].second,
                      LookupObjCProtocolName);
-      if (CorrectTypo(R, TUScope, 0) &&
+      if (CorrectTypo(R, TUScope, 0, 0, false, CTC_NoKeywords) &&
           (PDecl = R.getAsSingle<ObjCProtocolDecl>())) {
         Diag(ProtocolId[i].second, diag::err_undeclared_protocol_suggest)
           << ProtocolId[i].first << R.getLookupName();
@@ -554,7 +554,7 @@ Sema::DeclPtrTy Sema::ActOnStartClassImplementation(
     // We did not find anything with the name ClassName; try to correct for 
     // typos in the class name.
     LookupResult R(*this, ClassName, ClassLoc, LookupOrdinaryName);
-    if (CorrectTypo(R, TUScope, 0) &&
+    if (CorrectTypo(R, TUScope, 0, 0, false, CTC_NoKeywords) &&
         (IDecl = R.getAsSingle<ObjCInterfaceDecl>())) {
       // Suggest the (potentially) correct interface name. However, put the
       // fix-it hint itself in a separate note, since changing the name in 
index d10ea6d4ab9559be14967fe9ec0085547bd436f8..2a9af99493354df0ce882ed0020da3df62e4e0c1 100644 (file)
@@ -946,43 +946,55 @@ bool Sema::DiagnoseEmptyLookup(Scope *S, CXXScopeSpec &SS,
   }
 
   // We didn't find anything, so try to correct for a typo.
-  if (S && CorrectTypo(R, S, &SS)) {
-    if (isa<ValueDecl>(*R.begin()) || isa<FunctionTemplateDecl>(*R.begin())) {
-      if (SS.isEmpty())
-        Diag(R.getNameLoc(), diagnostic_suggest) << Name << R.getLookupName()
-          << FixItHint::CreateReplacement(R.getNameLoc(),
-                                          R.getLookupName().getAsString());
-      else
-        Diag(R.getNameLoc(), diag::err_no_member_suggest)
-          << Name << computeDeclContext(SS, false) << R.getLookupName()
-          << SS.getRange()
-          << FixItHint::CreateReplacement(R.getNameLoc(),
-                                          R.getLookupName().getAsString());
-      if (NamedDecl *ND = R.getAsSingle<NamedDecl>())
-        Diag(ND->getLocation(), diag::note_previous_decl)
-          << ND->getDeclName();
-
-      // Tell the callee to try to recover.
-      return false;
-    }
+  DeclarationName Corrected;
+  if (S && (Corrected = CorrectTypo(R, S, &SS))) {
+    if (!R.empty()) {
+      if (isa<ValueDecl>(*R.begin()) || isa<FunctionTemplateDecl>(*R.begin())) {
+        if (SS.isEmpty())
+          Diag(R.getNameLoc(), diagnostic_suggest) << Name << R.getLookupName()
+            << FixItHint::CreateReplacement(R.getNameLoc(),
+                                            R.getLookupName().getAsString());
+        else
+          Diag(R.getNameLoc(), diag::err_no_member_suggest)
+            << Name << computeDeclContext(SS, false) << R.getLookupName()
+            << SS.getRange()
+            << FixItHint::CreateReplacement(R.getNameLoc(),
+                                            R.getLookupName().getAsString());
+        if (NamedDecl *ND = R.getAsSingle<NamedDecl>())
+          Diag(ND->getLocation(), diag::note_previous_decl)
+            << ND->getDeclName();
+
+        // Tell the callee to try to recover.
+        return false;
+      }
+    
+      if (isa<TypeDecl>(*R.begin()) || isa<ObjCInterfaceDecl>(*R.begin())) {
+        // FIXME: If we ended up with a typo for a type name or
+        // Objective-C class name, we're in trouble because the parser
+        // is in the wrong place to recover. Suggest the typo
+        // correction, but don't make it a fix-it since we're not going
+        // to recover well anyway.
+        if (SS.isEmpty())
+          Diag(R.getNameLoc(), diagnostic_suggest) << Name << R.getLookupName();
+        else
+          Diag(R.getNameLoc(), diag::err_no_member_suggest)
+            << Name << computeDeclContext(SS, false) << R.getLookupName()
+            << SS.getRange();
 
-    if (isa<TypeDecl>(*R.begin()) || isa<ObjCInterfaceDecl>(*R.begin())) {
-      // FIXME: If we ended up with a typo for a type name or
-      // Objective-C class name, we're in trouble because the parser
-      // is in the wrong place to recover. Suggest the typo
-      // correction, but don't make it a fix-it since we're not going
-      // to recover well anyway.
+        // Don't try to recover; it won't work.
+        return true;
+      }
+    } else {
+      // FIXME: We found a keyword. Suggest it, but don't provide a fix-it 
+      // because we aren't able to recover.
       if (SS.isEmpty())
-        Diag(R.getNameLoc(), diagnostic_suggest) << Name << R.getLookupName();
+        Diag(R.getNameLoc(), diagnostic_suggest) << Name << Corrected;
       else
         Diag(R.getNameLoc(), diag::err_no_member_suggest)
-          << Name << computeDeclContext(SS, false) << R.getLookupName()
-          << SS.getRange();
-
-      // Don't try to recover; it won't work.
+        << Name << computeDeclContext(SS, false) << Corrected
+        << SS.getRange();
       return true;
     }
-
     R.clear();
   }
 
@@ -2575,7 +2587,8 @@ LookupMemberExprInRecord(Sema &SemaRef, LookupResult &R,
   // We didn't find anything with the given name, so try to correct
   // for typos.
   DeclarationName Name = R.getLookupName();
-  if (SemaRef.CorrectTypo(R, 0, &SS, DC) &&
+  if (SemaRef.CorrectTypo(R, 0, &SS, DC, false, Sema::CTC_MemberLookup) && 
+      !R.empty() &&
       (isa<ValueDecl>(*R.begin()) || isa<FunctionTemplateDecl>(*R.begin()))) {
     SemaRef.Diag(R.getNameLoc(), diag::err_no_member_suggest)
       << Name << DC << R.getLookupName() << SS.getRange()
@@ -3032,7 +3045,7 @@ Sema::LookupMemberExpr(LookupResult &R, Expr *&BaseExpr,
         // Attempt to correct for typos in ivar names.
         LookupResult Res(*this, R.getLookupName(), R.getNameLoc(),
                          LookupMemberName);
-        if (CorrectTypo(Res, 0, 0, IDecl) &&
+        if (CorrectTypo(Res, 0, 0, IDecl, false, CTC_MemberLookup) &&
             (IV = Res.getAsSingle<ObjCIvarDecl>())) {
           Diag(R.getNameLoc(),
                diag::err_typecheck_member_reference_ivar_suggest)
index f7d76b8b9b62b4e7412dd20cda96295aaa31e616..faa1ffc31e2e790d4776211b43109348295d82ba 100644 (file)
@@ -372,7 +372,7 @@ HandleExprPropertyRefExpr(const ObjCObjectPointerType *OPT,
 
   // Attempt to correct for typos in property names.
   LookupResult Res(*this, MemberName, MemberLoc, LookupOrdinaryName);
-  if (CorrectTypo(Res, 0, 0, IFace, false, OPT) &&
+  if (CorrectTypo(Res, 0, 0, IFace, false, CTC_NoKeywords, OPT) &&
       Res.getAsSingle<ObjCPropertyDecl>()) {
     DeclarationName TypoResult = Res.getLookupName();
     Diag(MemberLoc, diag::err_property_not_found_suggest)
@@ -523,18 +523,37 @@ Sema::ObjCMessageKind Sema::getObjCMessageKind(Scope *S,
   }
   }
 
-  if (CorrectTypo(Result, S, 0) && Result.isSingleResult()) {
-    NamedDecl *ND = Result.getFoundDecl();
-    if (isa<ObjCInterfaceDecl>(ND)) {
+  // Determine our typo-correction context.
+  CorrectTypoContext CTC = CTC_Expression;
+  if (ObjCMethodDecl *Method = getCurMethodDecl())
+    if (Method->getClassInterface() &&
+        Method->getClassInterface()->getSuperClass())
+      CTC = CTC_ObjCMessageReceiver;
+      
+  if (DeclarationName Corrected = CorrectTypo(Result, S, 0, 0, false, CTC)) {
+    if (Result.isSingleResult()) {
+      // If we found a declaration, correct when it refers to an Objective-C
+      // class.
+      NamedDecl *ND = Result.getFoundDecl();
+      if (isa<ObjCInterfaceDecl>(ND)) {
+        Diag(NameLoc, diag::err_unknown_receiver_suggest)
+          << Name << Result.getLookupName()
+          << FixItHint::CreateReplacement(SourceRange(NameLoc),
+                                          ND->getNameAsString());
+        Diag(ND->getLocation(), diag::note_previous_decl)
+          << Corrected;
+
+        Name = ND->getIdentifier();
+        return ObjCClassMessage;
+      }
+    } else if (Result.empty() && Corrected.getAsIdentifierInfo() &&
+               Corrected.getAsIdentifierInfo()->isStr("super")) {
+      // If we've found the keyword "super", this is a send to super.
       Diag(NameLoc, diag::err_unknown_receiver_suggest)
-        << Name << Result.getLookupName()
-        << FixItHint::CreateReplacement(SourceRange(NameLoc),
-                                        ND->getNameAsString());
-      Diag(ND->getLocation(), diag::note_previous_decl)
-        << ND->getDeclName();
-
-      Name = ND->getIdentifier();
-      return ObjCClassMessage;
+        << Name << Corrected
+        << FixItHint::CreateReplacement(SourceRange(NameLoc), "super");
+      Name = Corrected.getAsIdentifierInfo();
+      return ObjCSuperMessage;
     }
   }
   
index 52a5cb13724509cee0618d816d290c1f79cc360a..dae3d3ae7e68f75c993bbb1a6e46a0961a5ae2f5 100644 (file)
@@ -1354,7 +1354,8 @@ InitListChecker::CheckDesignatedInitializer(const InitializedEntity &Entity,
         // was a typo for another field name.
         LookupResult R(SemaRef, FieldName, D->getFieldLoc(), 
                        Sema::LookupMemberName);
-        if (SemaRef.CorrectTypo(R, /*Scope=*/0, /*SS=*/0, RT->getDecl()) &&
+        if (SemaRef.CorrectTypo(R, /*Scope=*/0, /*SS=*/0, RT->getDecl(), false,
+                                Sema::CTC_NoKeywords) && 
             (ReplacementField = R.getAsSingle<FieldDecl>()) &&
             ReplacementField->getDeclContext()->getLookupContext()
                                                       ->Equals(RT->getDecl())) {
index 0e2a10aeb0d134953e69310c25c7a50060cd08f2..18b6e7205062a3b2a65ddeceebbfc03985e542ba 100644 (file)
@@ -2421,6 +2421,9 @@ class TypoCorrectionConsumer : public VisibleDeclConsumer {
   /// found (so far) with the typo name.
   llvm::SmallVector<NamedDecl *, 4> BestResults;
 
+  /// \brief The keywords that have the smallest edit distance.
+  llvm::SmallVector<IdentifierInfo *, 4> BestKeywords;
+  
   /// \brief The best edit distance found so far.
   unsigned BestEditDistance;
   
@@ -2429,13 +2432,23 @@ public:
     : Typo(Typo->getName()) { }
 
   virtual void FoundDecl(NamedDecl *ND, NamedDecl *Hiding, bool InBaseClass);
+  void addKeywordResult(ASTContext &Context, llvm::StringRef Keyword);
 
   typedef llvm::SmallVector<NamedDecl *, 4>::const_iterator iterator;
   iterator begin() const { return BestResults.begin(); }
   iterator end() const { return BestResults.end(); }
-  bool empty() const { return BestResults.empty(); }
-
-  unsigned getBestEditDistance() const { return BestEditDistance; }
+  void clear_decls() { BestResults.clear(); }
+  
+  bool empty() const { return BestResults.empty() && BestKeywords.empty(); }
+
+  typedef llvm::SmallVector<IdentifierInfo *, 4>::const_iterator
+    keyword_iterator;
+  keyword_iterator keyword_begin() const { return BestKeywords.begin(); }
+  keyword_iterator keyword_end() const { return BestKeywords.end(); }
+  bool keyword_empty() const { return BestKeywords.empty(); }
+  unsigned keyword_size() const { return BestKeywords.size(); }
+  
+  unsigned getBestEditDistance() const { return BestEditDistance; }  
 };
 
 }
@@ -2457,11 +2470,12 @@ void TypoCorrectionConsumer::FoundDecl(NamedDecl *ND, NamedDecl *Hiding,
   // entity. If this edit distance is not worse than the best edit
   // distance we've seen so far, add it to the list of results.
   unsigned ED = Typo.edit_distance(Name->getName());
-  if (!BestResults.empty()) {
+  if (!BestResults.empty() || !BestKeywords.empty()) {
     if (ED < BestEditDistance) {
       // This result is better than any we've seen before; clear out
       // the previous results.
       BestResults.clear();
+      BestKeywords.clear();
       BestEditDistance = ED;
     } else if (ED > BestEditDistance) {
       // This result is worse than the best results we've seen so far;
@@ -2474,6 +2488,28 @@ void TypoCorrectionConsumer::FoundDecl(NamedDecl *ND, NamedDecl *Hiding,
   BestResults.push_back(ND);
 }
 
+void TypoCorrectionConsumer::addKeywordResult(ASTContext &Context, 
+                                              llvm::StringRef Keyword) {
+  // Compute the edit distance between the typo and this keyword.
+  // If this edit distance is not worse than the best edit
+  // distance we've seen so far, add it to the list of results.
+  unsigned ED = Typo.edit_distance(Keyword);
+  if (!BestResults.empty() || !BestKeywords.empty()) {
+    if (ED < BestEditDistance) {
+      BestResults.clear();
+      BestKeywords.clear();
+      BestEditDistance = ED;
+    } else if (ED > BestEditDistance) {
+      // This result is worse than the best results we've seen so far;
+      // ignore it.
+      return;
+    }
+  } else
+    BestEditDistance = ED;
+  
+  BestKeywords.push_back(&Context.Idents.get(Keyword));
+}
+
 /// \brief Try to "correct" a typo in the source code by finding
 /// visible declarations whose names are similar to the name that was
 /// present in the source code.
@@ -2494,6 +2530,9 @@ void TypoCorrectionConsumer::FoundDecl(NamedDecl *ND, NamedDecl *Hiding,
 /// \param EnteringContext whether we're entering the context described by 
 /// the nested-name-specifier SS.
 ///
+/// \param CTC The context in which typo correction occurs, which impacts the
+/// set of keywords permitted.
+///
 /// \param OPT when non-NULL, the search for visible declarations will
 /// also walk the protocols in the qualified interfaces of \p OPT.
 ///
@@ -2502,8 +2541,10 @@ void TypoCorrectionConsumer::FoundDecl(NamedDecl *ND, NamedDecl *Hiding,
 /// may contain the results of name lookup for the correct name or it may be
 /// empty.
 DeclarationName Sema::CorrectTypo(LookupResult &Res, Scope *S, CXXScopeSpec *SS,
-                       DeclContext *MemberContext, bool EnteringContext,
-                       const ObjCObjectPointerType *OPT) {
+                                  DeclContext *MemberContext, 
+                                  bool EnteringContext,
+                                  CorrectTypoContext CTC,
+                                  const ObjCObjectPointerType *OPT) {
   if (Diags.hasFatalErrorOccurred())
     return DeclarationName();
 
@@ -2529,8 +2570,10 @@ DeclarationName Sema::CorrectTypo(LookupResult &Res, Scope *S, CXXScopeSpec *SS,
   // instantiation.
   if (!ActiveTemplateInstantiations.empty())
     return DeclarationName();
-
+  
   TypoCorrectionConsumer Consumer(Typo);
+  
+  // Perform name lookup to find visible, similarly-named entities.
   if (MemberContext) {
     LookupVisibleDecls(MemberContext, Res.getLookupKind(), Consumer);
 
@@ -2551,37 +2594,231 @@ DeclarationName Sema::CorrectTypo(LookupResult &Res, Scope *S, CXXScopeSpec *SS,
     LookupVisibleDecls(S, Res.getLookupKind(), Consumer);
   }
 
+  // Add context-dependent keywords.
+  bool WantTypeSpecifiers = false;
+  bool WantExpressionKeywords = false;
+  bool WantCXXNamedCasts = false;
+  bool WantRemainingKeywords = false;
+  switch (CTC) {
+    case CTC_Unknown:
+      WantTypeSpecifiers = true;
+      WantExpressionKeywords = true;
+      WantCXXNamedCasts = true;
+      WantRemainingKeywords = true;
+      break;
+  
+    case CTC_NoKeywords:
+      break;
+  
+    case CTC_Type:
+      WantTypeSpecifiers = true;
+      break;
+      
+    case CTC_ObjCMessageReceiver:
+      Consumer.addKeywordResult(Context, "super");
+      // Fall through to handle message receivers like expressions.
+      
+    case CTC_Expression:
+      if (getLangOptions().CPlusPlus)
+        WantTypeSpecifiers = true;
+      WantExpressionKeywords = true;
+      // Fall through to get C++ named casts.
+      
+    case CTC_CXXCasts:
+      WantCXXNamedCasts = true;
+      break;
+      
+    case CTC_MemberLookup:
+      if (getLangOptions().CPlusPlus)
+        Consumer.addKeywordResult(Context, "template");
+      break;
+  }
+
+  if (WantTypeSpecifiers) {
+    // Add type-specifier keywords to the set of results.
+    const char *CTypeSpecs[] = {
+      "char", "const", "double", "enum", "float", "int", "long", "short",
+      "signed", "struct", "union", "unsigned", "void", "volatile", "_Bool",
+      "_Complex", "_Imaginary",
+      // storage-specifiers as well
+      "extern", "inline", "static", "typedef"
+    };
+    
+    const unsigned NumCTypeSpecs = sizeof(CTypeSpecs) / sizeof(CTypeSpecs[0]);
+    for (unsigned I = 0; I != NumCTypeSpecs; ++I)
+      Consumer.addKeywordResult(Context, CTypeSpecs[I]);
+    
+    if (getLangOptions().C99)
+      Consumer.addKeywordResult(Context, "restrict");
+    if (getLangOptions().Bool || getLangOptions().CPlusPlus)
+      Consumer.addKeywordResult(Context, "bool");
+    
+    if (getLangOptions().CPlusPlus) {
+      Consumer.addKeywordResult(Context, "class");
+      Consumer.addKeywordResult(Context, "typename");
+      Consumer.addKeywordResult(Context, "wchar_t");
+      
+      if (getLangOptions().CPlusPlus0x) {
+        Consumer.addKeywordResult(Context, "char16_t");
+        Consumer.addKeywordResult(Context, "char32_t");
+        Consumer.addKeywordResult(Context, "constexpr");
+        Consumer.addKeywordResult(Context, "decltype");
+        Consumer.addKeywordResult(Context, "thread_local");
+      }      
+    }
+        
+    if (getLangOptions().GNUMode)
+      Consumer.addKeywordResult(Context, "typeof");
+  }
+  
+  if (WantCXXNamedCasts) {
+    Consumer.addKeywordResult(Context, "const_cast");
+    Consumer.addKeywordResult(Context, "dynamic_cast");
+    Consumer.addKeywordResult(Context, "reinterpret_cast");
+    Consumer.addKeywordResult(Context, "static_cast");
+  }
+  
+  if (WantExpressionKeywords) {
+    Consumer.addKeywordResult(Context, "sizeof");
+    if (getLangOptions().Bool || getLangOptions().CPlusPlus) {
+      Consumer.addKeywordResult(Context, "false");
+      Consumer.addKeywordResult(Context, "true");
+    }
+    
+    if (getLangOptions().CPlusPlus) {
+      const char *CXXExprs[] = { 
+        "delete", "new", "operator", "throw", "typeid" 
+      };
+      const unsigned NumCXXExprs = sizeof(CXXExprs) / sizeof(CXXExprs[0]);
+      for (unsigned I = 0; I != NumCXXExprs; ++I)
+        Consumer.addKeywordResult(Context, CXXExprs[I]);
+      
+      if (isa<CXXMethodDecl>(CurContext) &&
+          cast<CXXMethodDecl>(CurContext)->isInstance())
+        Consumer.addKeywordResult(Context, "this");
+      
+      if (getLangOptions().CPlusPlus0x) {
+        Consumer.addKeywordResult(Context, "alignof");
+        Consumer.addKeywordResult(Context, "nullptr");
+      }
+    }
+  }
+  
+  if (WantRemainingKeywords) {
+    if (getCurFunctionOrMethodDecl() || getCurBlock()) {
+      // Statements.
+      const char *CStmts[] = {
+        "do", "else", "for", "goto", "if", "return", "switch", "while" };
+      const unsigned NumCStmts = sizeof(CStmts) / sizeof(CStmts[0]);
+      for (unsigned I = 0; I != NumCStmts; ++I)
+        Consumer.addKeywordResult(Context, CStmts[I]);
+      
+      if (getLangOptions().CPlusPlus) {
+        Consumer.addKeywordResult(Context, "catch");
+        Consumer.addKeywordResult(Context, "try");
+      }
+      
+      if (S && S->getBreakParent())
+        Consumer.addKeywordResult(Context, "break");
+      
+      if (S && S->getContinueParent())
+        Consumer.addKeywordResult(Context, "continue");
+      
+      if (!getSwitchStack().empty()) {
+        Consumer.addKeywordResult(Context, "case");
+        Consumer.addKeywordResult(Context, "default");
+      }
+    } else {
+      if (getLangOptions().CPlusPlus) {
+        Consumer.addKeywordResult(Context, "namespace");
+        Consumer.addKeywordResult(Context, "template");
+      }
+
+      if (S && S->isClassScope()) {
+        Consumer.addKeywordResult(Context, "explicit");
+        Consumer.addKeywordResult(Context, "friend");
+        Consumer.addKeywordResult(Context, "mutable");
+        Consumer.addKeywordResult(Context, "private");
+        Consumer.addKeywordResult(Context, "protected");
+        Consumer.addKeywordResult(Context, "public");
+        Consumer.addKeywordResult(Context, "virtual");
+      }
+    }
+        
+    if (getLangOptions().CPlusPlus) {
+      Consumer.addKeywordResult(Context, "using");
+
+      if (getLangOptions().CPlusPlus0x)
+        Consumer.addKeywordResult(Context, "static_assert");
+    }
+  }
+  
+  // If we haven't found anything, we're done.
   if (Consumer.empty())
     return DeclarationName();
 
   // Only allow a single, closest name in the result set (it's okay to
   // have overloads of that name, though).
-  TypoCorrectionConsumer::iterator I = Consumer.begin();
-  DeclarationName BestName = (*I)->getDeclName();
-
-  // If we've found an Objective-C ivar or property, don't perform
-  // name lookup again; we'll just return the result directly.
-  NamedDecl *FoundBest = 0;
-  if (isa<ObjCIvarDecl>(*I) || isa<ObjCPropertyDecl>(*I))
-    FoundBest = *I;
-  ++I;
-  for(TypoCorrectionConsumer::iterator IEnd = Consumer.end(); I != IEnd; ++I) {
-    if (BestName != (*I)->getDeclName())
+  DeclarationName BestName;
+  NamedDecl *BestIvarOrPropertyDecl = 0;
+  bool FoundIvarOrPropertyDecl = false;
+  
+  // Check all of the declaration results to find the best name so far.
+  for (TypoCorrectionConsumer::iterator I = Consumer.begin(), 
+                                     IEnd = Consumer.end();
+       I != IEnd; ++I) {
+    if (!BestName)
+      BestName = (*I)->getDeclName();
+    else if (BestName != (*I)->getDeclName())
       return DeclarationName();
 
-    // FIXME: If there are both ivars and properties of the same name,
-    // don't return both because the callee can't handle two
-    // results. We really need to separate ivar lookup from property
-    // lookup to avoid this problem.
-    FoundBest = 0;
+    // \brief Keep track of either an Objective-C ivar or a property, but not
+    // both.
+    if (isa<ObjCIvarDecl>(*I) || isa<ObjCPropertyDecl>(*I)) {
+      if (FoundIvarOrPropertyDecl)
+        BestIvarOrPropertyDecl = 0;
+      else {
+        BestIvarOrPropertyDecl = *I;
+        FoundIvarOrPropertyDecl = true;
+      }
+    }
   }
 
+  // Now check all of the keyword results to find the best name. 
+  switch (Consumer.keyword_size()) {
+    case 0:
+      // No keywords matched.
+      break;
+      
+    case 1:
+      // If we already have a name
+      if (!BestName) {
+        // We did not have anything previously, 
+        BestName = *Consumer.keyword_begin();
+      } else if (BestName.getAsIdentifierInfo() == *Consumer.keyword_begin()) {
+        // We have a declaration with the same name as a context-sensitive
+        // keyword. The keyword takes precedence.
+        BestIvarOrPropertyDecl = 0;
+        FoundIvarOrPropertyDecl = false;
+        Consumer.clear_decls();
+      } else {
+        // Name collision; we will not correct typos.
+        return DeclarationName();
+      }
+      break;
+      
+    default:
+      // Name collision; we will not correct typos.
+      return DeclarationName();
+  }
+  
   // BestName is the closest viable name to what the user
   // typed. However, to make sure that we don't pick something that's
   // way off, make sure that the user typed at least 3 characters for
   // each correction.
   unsigned ED = Consumer.getBestEditDistance();
-  if (ED == 0 || (BestName.getAsIdentifierInfo()->getName().size() / ED) < 3)
+  if (ED == 0 || !BestName.getAsIdentifierInfo() ||
+      (BestName.getAsIdentifierInfo()->getName().size() / ED) < 3)
     return DeclarationName();
 
   // Perform name lookup again with the name we chose, and declare
@@ -2591,11 +2828,14 @@ DeclarationName Sema::CorrectTypo(LookupResult &Res, Scope *S, CXXScopeSpec *SS,
 
   // If we found an ivar or property, add that result; no further
   // lookup is required.
-  if (FoundBest)
-    Res.addDecl(FoundBest);  
+  if (BestIvarOrPropertyDecl)
+    Res.addDecl(BestIvarOrPropertyDecl);  
   // If we're looking into the context of a member, perform qualified
   // name lookup on the best name.
-  else if (MemberContext)
+  else if (!Consumer.keyword_empty()) {
+    // The best match was a keyword. Return it.
+    return BestName;
+  } else if (MemberContext)
     LookupQualifiedName(Res, MemberContext);
   // Perform lookup as if we had just parsed the best name.
   else
index 9816e76530bc208169453d904a26364afa7b80f5..28d6f0a40aa88d8dd35242f59dad7775fdca2933 100644 (file)
@@ -250,7 +250,8 @@ void Sema::LookupTemplateName(LookupResult &Found,
   if (Found.empty() && !isDependent) {
     // If we did not find any names, attempt to correct any typos.
     DeclarationName Name = Found.getLookupName();
-    if (CorrectTypo(Found, S, &SS, LookupCtx)) {
+    if (DeclarationName Corrected = CorrectTypo(Found, S, &SS, LookupCtx, 
+                                                 false, CTC_CXXCasts)) {
       FilterAcceptableTemplateNames(Context, Found);
       if (!Found.empty() && isa<TemplateDecl>(*Found.begin())) {
         if (LookupCtx)
diff --git a/test/FixIt/fixit-unrecoverable.c b/test/FixIt/fixit-unrecoverable.c
new file mode 100644 (file)
index 0000000..5c825f2
--- /dev/null
@@ -0,0 +1,12 @@
+/* FIXME: This is a file containing various typos for which we can
+   suggest corrections but are unable to actually recover from
+   them. Ideally, we would eliminate all such cases and move these
+   tests elsewhere. */
+
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+// FIXME: Sadly, the following doesn't work within a function.
+
+unsinged x = 17; // expected-error{{unknown type name 'unsinged'; did you mean 'unsigned'?}}
+
+
diff --git a/test/FixIt/fixit-unrecoverable.cpp b/test/FixIt/fixit-unrecoverable.cpp
new file mode 100644 (file)
index 0000000..00ed897
--- /dev/null
@@ -0,0 +1,11 @@
+/* FIXME: This is a file containing various typos for which we can
+   suggest corrections but are unable to actually recover from
+   them. Ideally, we would eliminate all such cases and move these
+   tests elsewhere. */
+
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+float f(int y) {
+  return static_cst<float>(y); // expected-error{{use of undeclared identifier 'static_cst'; did you mean 'static_cast'?}}
+}
+
index 19602fcc8e9071720e35c1c10b40ae115aab5027..ccd94b78c58b784cc1f1d5c9b1c63bc223a7a50c 100644 (file)
@@ -87,3 +87,18 @@ void test2(Collide *a) {
 
 @interface IPv6 <Network_Socket> // expected-error{{cannot find protocol declaration for 'Network_Socket'; did you mean 'NetworkSocket'?}}
 @end
+
+@interface Super
+- (int)method;
+@end
+
+@interface Sub : Super
+- (int)method;
+@end
+
+@implementation Sub
+- (int)method {
+  return [supper method]; // expected-error{{unknown receiver 'supper'; did you mean 'super'?}}
+}
+  
+@end