Home | History | Annotate | Download | only in Sema
      1 //===--- TypoCorrection.h - Class for typo correction results ---*- C++ -*-===//
      2 //
      3 //                     The LLVM Compiler Infrastructure
      4 //
      5 // This file is distributed under the University of Illinois Open Source
      6 // License. See LICENSE.TXT for details.
      7 //
      8 //===----------------------------------------------------------------------===//
      9 //
     10 // This file defines the TypoCorrection class, which stores the results of
     11 // Sema's typo correction (Sema::CorrectTypo).
     12 //
     13 //===----------------------------------------------------------------------===//
     14 
     15 #ifndef LLVM_CLANG_SEMA_TYPOCORRECTION_H
     16 #define LLVM_CLANG_SEMA_TYPOCORRECTION_H
     17 
     18 #include "clang/AST/DeclCXX.h"
     19 #include "clang/Sema/DeclSpec.h"
     20 #include "llvm/ADT/SmallVector.h"
     21 
     22 namespace clang {
     23 
     24 /// @brief Simple class containing the result of Sema::CorrectTypo
     25 class TypoCorrection {
     26 public:
     27   // "Distance" for unusable corrections
     28   static const unsigned InvalidDistance = ~0U;
     29   // The largest distance still considered valid (larger edit distances are
     30   // mapped to InvalidDistance by getEditDistance).
     31   static const unsigned MaximumDistance = 10000U;
     32 
     33   // Relative weightings of the "edit distance" components. The higher the
     34   // weight, the more of a penalty to fitness the component will give (higher
     35   // weights mean greater contribution to the total edit distance, with the
     36   // best correction candidates having the lowest edit distance).
     37   static const unsigned CharDistanceWeight = 100U;
     38   static const unsigned QualifierDistanceWeight = 110U;
     39   static const unsigned CallbackDistanceWeight = 150U;
     40 
     41   TypoCorrection(const DeclarationName &Name, NamedDecl *NameDecl,
     42                  NestedNameSpecifier *NNS = nullptr, unsigned CharDistance = 0,
     43                  unsigned QualifierDistance = 0)
     44       : CorrectionName(Name), CorrectionNameSpec(NNS),
     45         CharDistance(CharDistance), QualifierDistance(QualifierDistance),
     46         CallbackDistance(0), ForceSpecifierReplacement(false),
     47         RequiresImport(false) {
     48     if (NameDecl)
     49       CorrectionDecls.push_back(NameDecl);
     50   }
     51 
     52   TypoCorrection(NamedDecl *Name, NestedNameSpecifier *NNS = nullptr,
     53                  unsigned CharDistance = 0)
     54       : CorrectionName(Name->getDeclName()), CorrectionNameSpec(NNS),
     55         CharDistance(CharDistance), QualifierDistance(0), CallbackDistance(0),
     56         ForceSpecifierReplacement(false), RequiresImport(false) {
     57     if (Name)
     58       CorrectionDecls.push_back(Name);
     59   }
     60 
     61   TypoCorrection(DeclarationName Name, NestedNameSpecifier *NNS = nullptr,
     62                  unsigned CharDistance = 0)
     63       : CorrectionName(Name), CorrectionNameSpec(NNS),
     64         CharDistance(CharDistance), QualifierDistance(0), CallbackDistance(0),
     65         ForceSpecifierReplacement(false), RequiresImport(false) {}
     66 
     67   TypoCorrection()
     68       : CorrectionNameSpec(nullptr), CharDistance(0), QualifierDistance(0),
     69         CallbackDistance(0), ForceSpecifierReplacement(false),
     70         RequiresImport(false) {}
     71 
     72   /// \brief Gets the DeclarationName of the typo correction
     73   DeclarationName getCorrection() const { return CorrectionName; }
     74   IdentifierInfo* getCorrectionAsIdentifierInfo() const {
     75     return CorrectionName.getAsIdentifierInfo();
     76   }
     77 
     78   /// \brief Gets the NestedNameSpecifier needed to use the typo correction
     79   NestedNameSpecifier* getCorrectionSpecifier() const {
     80     return CorrectionNameSpec;
     81   }
     82   void setCorrectionSpecifier(NestedNameSpecifier* NNS) {
     83     CorrectionNameSpec = NNS;
     84     ForceSpecifierReplacement = (NNS != nullptr);
     85   }
     86 
     87   void WillReplaceSpecifier(bool ForceReplacement) {
     88     ForceSpecifierReplacement = ForceReplacement;
     89   }
     90 
     91   bool WillReplaceSpecifier() const {
     92     return ForceSpecifierReplacement;
     93   }
     94 
     95   void setQualifierDistance(unsigned ED) {
     96     QualifierDistance = ED;
     97   }
     98 
     99   void setCallbackDistance(unsigned ED) {
    100     CallbackDistance = ED;
    101   }
    102 
    103   // Convert the given weighted edit distance to a roughly equivalent number of
    104   // single-character edits (typically for comparison to the length of the
    105   // string being edited).
    106   static unsigned NormalizeEditDistance(unsigned ED) {
    107     if (ED > MaximumDistance)
    108       return InvalidDistance;
    109     return (ED + CharDistanceWeight / 2) / CharDistanceWeight;
    110   }
    111 
    112   /// \brief Gets the "edit distance" of the typo correction from the typo.
    113   /// If Normalized is true, scale the distance down by the CharDistanceWeight
    114   /// to return the edit distance in terms of single-character edits.
    115   unsigned getEditDistance(bool Normalized = true) const {
    116     if (CharDistance > MaximumDistance || QualifierDistance > MaximumDistance ||
    117         CallbackDistance > MaximumDistance)
    118       return InvalidDistance;
    119     unsigned ED =
    120         CharDistance * CharDistanceWeight +
    121         QualifierDistance * QualifierDistanceWeight +
    122         CallbackDistance * CallbackDistanceWeight;
    123     if (ED > MaximumDistance)
    124       return InvalidDistance;
    125     // Half the CharDistanceWeight is added to ED to simulate rounding since
    126     // integer division truncates the value (i.e. round-to-nearest-int instead
    127     // of round-to-zero).
    128     return Normalized ? NormalizeEditDistance(ED) : ED;
    129   }
    130 
    131   /// \brief Gets the pointer to the declaration of the typo correction
    132   NamedDecl *getCorrectionDecl() const {
    133     return hasCorrectionDecl() ? *(CorrectionDecls.begin()) : nullptr;
    134   }
    135   template <class DeclClass>
    136   DeclClass *getCorrectionDeclAs() const {
    137     return dyn_cast_or_null<DeclClass>(getCorrectionDecl());
    138   }
    139 
    140   /// \brief Clears the list of NamedDecls.
    141   void ClearCorrectionDecls() {
    142     CorrectionDecls.clear();
    143   }
    144 
    145   /// \brief Clears the list of NamedDecls before adding the new one.
    146   void setCorrectionDecl(NamedDecl *CDecl) {
    147     CorrectionDecls.clear();
    148     addCorrectionDecl(CDecl);
    149   }
    150 
    151   /// \brief Clears the list of NamedDecls and adds the given set.
    152   void setCorrectionDecls(ArrayRef<NamedDecl*> Decls) {
    153     CorrectionDecls.clear();
    154     CorrectionDecls.insert(CorrectionDecls.begin(), Decls.begin(), Decls.end());
    155   }
    156 
    157   /// \brief Add the given NamedDecl to the list of NamedDecls that are the
    158   /// declarations associated with the DeclarationName of this TypoCorrection
    159   void addCorrectionDecl(NamedDecl *CDecl);
    160 
    161   std::string getAsString(const LangOptions &LO) const;
    162   std::string getQuoted(const LangOptions &LO) const {
    163     return "'" + getAsString(LO) + "'";
    164   }
    165 
    166   /// \brief Returns whether this TypoCorrection has a non-empty DeclarationName
    167   LLVM_EXPLICIT operator bool() const { return bool(CorrectionName); }
    168 
    169   /// \brief Mark this TypoCorrection as being a keyword.
    170   /// Since addCorrectionDeclsand setCorrectionDecl don't allow NULL to be
    171   /// added to the list of the correction's NamedDecl pointers, NULL is added
    172   /// as the only element in the list to mark this TypoCorrection as a keyword.
    173   void makeKeyword() {
    174     CorrectionDecls.clear();
    175     CorrectionDecls.push_back(nullptr);
    176     ForceSpecifierReplacement = true;
    177   }
    178 
    179   // Check if this TypoCorrection is a keyword by checking if the first
    180   // item in CorrectionDecls is NULL.
    181   bool isKeyword() const {
    182     return !CorrectionDecls.empty() &&
    183         CorrectionDecls.front() == nullptr;
    184   }
    185 
    186   // Check if this TypoCorrection is the given keyword.
    187   template<std::size_t StrLen>
    188   bool isKeyword(const char (&Str)[StrLen]) const {
    189     return isKeyword() && getCorrectionAsIdentifierInfo()->isStr(Str);
    190   }
    191 
    192   // Returns true if the correction either is a keyword or has a known decl.
    193   bool isResolved() const { return !CorrectionDecls.empty(); }
    194 
    195   bool isOverloaded() const {
    196     return CorrectionDecls.size() > 1;
    197   }
    198 
    199   void setCorrectionRange(CXXScopeSpec *SS,
    200                           const DeclarationNameInfo &TypoName) {
    201     CorrectionRange.setBegin(ForceSpecifierReplacement && SS && !SS->isEmpty()
    202                                  ? SS->getBeginLoc()
    203                                  : TypoName.getLoc());
    204     CorrectionRange.setEnd(TypoName.getLoc());
    205   }
    206 
    207   SourceRange getCorrectionRange() const {
    208     return CorrectionRange;
    209   }
    210 
    211   typedef SmallVectorImpl<NamedDecl *>::iterator decl_iterator;
    212   decl_iterator begin() {
    213     return isKeyword() ? CorrectionDecls.end() : CorrectionDecls.begin();
    214   }
    215   decl_iterator end() { return CorrectionDecls.end(); }
    216   typedef SmallVectorImpl<NamedDecl *>::const_iterator const_decl_iterator;
    217   const_decl_iterator begin() const {
    218     return isKeyword() ? CorrectionDecls.end() : CorrectionDecls.begin();
    219   }
    220   const_decl_iterator end() const { return CorrectionDecls.end(); }
    221 
    222   /// \brief Returns whether this typo correction is correcting to a
    223   /// declaration that was declared in a module that has not been imported.
    224   bool requiresImport() const { return RequiresImport; }
    225   void setRequiresImport(bool Req) { RequiresImport = Req; }
    226 
    227 private:
    228   bool hasCorrectionDecl() const {
    229     return (!isKeyword() && !CorrectionDecls.empty());
    230   }
    231 
    232   // Results.
    233   DeclarationName CorrectionName;
    234   NestedNameSpecifier *CorrectionNameSpec;
    235   SmallVector<NamedDecl *, 1> CorrectionDecls;
    236   unsigned CharDistance;
    237   unsigned QualifierDistance;
    238   unsigned CallbackDistance;
    239   SourceRange CorrectionRange;
    240   bool ForceSpecifierReplacement;
    241   bool RequiresImport;
    242 };
    243 
    244 /// @brief Base class for callback objects used by Sema::CorrectTypo to check
    245 /// the validity of a potential typo correction.
    246 class CorrectionCandidateCallback {
    247 public:
    248   static const unsigned InvalidDistance = TypoCorrection::InvalidDistance;
    249 
    250   CorrectionCandidateCallback()
    251       : WantTypeSpecifiers(true), WantExpressionKeywords(true),
    252         WantCXXNamedCasts(true), WantRemainingKeywords(true),
    253         WantObjCSuper(false), IsObjCIvarLookup(false),
    254         IsAddressOfOperand(false) {}
    255 
    256   virtual ~CorrectionCandidateCallback() {}
    257 
    258   /// \brief Simple predicate used by the default RankCandidate to
    259   /// determine whether to return an edit distance of 0 or InvalidDistance.
    260   /// This can be overrided by validators that only need to determine if a
    261   /// candidate is viable, without ranking potentially viable candidates.
    262   /// Only ValidateCandidate or RankCandidate need to be overriden by a
    263   /// callback wishing to check the viability of correction candidates.
    264   /// The default predicate always returns true if the candidate is not a type
    265   /// name or keyword, true for types if WantTypeSpecifiers is true, and true
    266   /// for keywords if WantTypeSpecifiers, WantExpressionKeywords,
    267   /// WantCXXNamedCasts, WantRemainingKeywords, or WantObjCSuper is true.
    268   virtual bool ValidateCandidate(const TypoCorrection &candidate);
    269 
    270   /// \brief Method used by Sema::CorrectTypo to assign an "edit distance" rank
    271   /// to a candidate (where a lower value represents a better candidate), or
    272   /// returning InvalidDistance if the candidate is not at all viable. For
    273   /// validation callbacks that only need to determine if a candidate is viable,
    274   /// the default RankCandidate returns either 0 or InvalidDistance depending
    275   /// whether ValidateCandidate returns true or false.
    276   virtual unsigned RankCandidate(const TypoCorrection &candidate) {
    277     return ValidateCandidate(candidate) ? 0 : InvalidDistance;
    278   }
    279 
    280   // Flags for context-dependent keywords.
    281   // TODO: Expand these to apply to non-keywords or possibly remove them.
    282   bool WantTypeSpecifiers;
    283   bool WantExpressionKeywords;
    284   bool WantCXXNamedCasts;
    285   bool WantRemainingKeywords;
    286   bool WantObjCSuper;
    287   // Temporary hack for the one case where a CorrectTypoContext enum is used
    288   // when looking up results.
    289   bool IsObjCIvarLookup;
    290   bool IsAddressOfOperand;
    291 };
    292 
    293 /// @brief Simple template class for restricting typo correction candidates
    294 /// to ones having a single Decl* of the given type.
    295 template <class C>
    296 class DeclFilterCCC : public CorrectionCandidateCallback {
    297 public:
    298   bool ValidateCandidate(const TypoCorrection &candidate) override {
    299     return candidate.getCorrectionDeclAs<C>();
    300   }
    301 };
    302 
    303 // @brief Callback class to limit the allowed keywords and to only accept typo
    304 // corrections that are keywords or whose decls refer to functions (or template
    305 // functions) that accept the given number of arguments.
    306 class FunctionCallFilterCCC : public CorrectionCandidateCallback {
    307 public:
    308   FunctionCallFilterCCC(Sema &SemaRef, unsigned NumArgs,
    309                         bool HasExplicitTemplateArgs,
    310                         MemberExpr *ME = nullptr);
    311 
    312   bool ValidateCandidate(const TypoCorrection &candidate) override;
    313 
    314  private:
    315   unsigned NumArgs;
    316   bool HasExplicitTemplateArgs;
    317   DeclContext *CurContext;
    318   MemberExpr *MemberFn;
    319 };
    320 
    321 // @brief Callback class that effectively disabled typo correction
    322 class NoTypoCorrectionCCC : public CorrectionCandidateCallback {
    323 public:
    324   NoTypoCorrectionCCC() {
    325     WantTypeSpecifiers = false;
    326     WantExpressionKeywords = false;
    327     WantCXXNamedCasts = false;
    328     WantRemainingKeywords = false;
    329   }
    330 
    331   bool ValidateCandidate(const TypoCorrection &candidate) override {
    332     return false;
    333   }
    334 };
    335 
    336 }
    337 
    338 #endif
    339