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 "clang/Sema/Ownership.h"
     21 #include "llvm/ADT/SmallVector.h"
     22 
     23 namespace clang {
     24 
     25 /// @brief Simple class containing the result of Sema::CorrectTypo
     26 class TypoCorrection {
     27 public:
     28   // "Distance" for unusable corrections
     29   static const unsigned InvalidDistance = ~0U;
     30   // The largest distance still considered valid (larger edit distances are
     31   // mapped to InvalidDistance by getEditDistance).
     32   static const unsigned MaximumDistance = 10000U;
     33 
     34   // Relative weightings of the "edit distance" components. The higher the
     35   // weight, the more of a penalty to fitness the component will give (higher
     36   // weights mean greater contribution to the total edit distance, with the
     37   // best correction candidates having the lowest edit distance).
     38   static const unsigned CharDistanceWeight = 100U;
     39   static const unsigned QualifierDistanceWeight = 110U;
     40   static const unsigned CallbackDistanceWeight = 150U;
     41 
     42   TypoCorrection(const DeclarationName &Name, NamedDecl *NameDecl,
     43                  NestedNameSpecifier *NNS = nullptr, unsigned CharDistance = 0,
     44                  unsigned QualifierDistance = 0)
     45       : CorrectionName(Name), CorrectionNameSpec(NNS),
     46         CharDistance(CharDistance), QualifierDistance(QualifierDistance),
     47         CallbackDistance(0), ForceSpecifierReplacement(false),
     48         RequiresImport(false) {
     49     if (NameDecl)
     50       CorrectionDecls.push_back(NameDecl);
     51   }
     52 
     53   TypoCorrection(NamedDecl *Name, NestedNameSpecifier *NNS = nullptr,
     54                  unsigned CharDistance = 0)
     55       : CorrectionName(Name->getDeclName()), CorrectionNameSpec(NNS),
     56         CharDistance(CharDistance), QualifierDistance(0), CallbackDistance(0),
     57         ForceSpecifierReplacement(false), RequiresImport(false) {
     58     if (Name)
     59       CorrectionDecls.push_back(Name);
     60   }
     61 
     62   TypoCorrection(DeclarationName Name, NestedNameSpecifier *NNS = nullptr,
     63                  unsigned CharDistance = 0)
     64       : CorrectionName(Name), CorrectionNameSpec(NNS),
     65         CharDistance(CharDistance), QualifierDistance(0), CallbackDistance(0),
     66         ForceSpecifierReplacement(false), RequiresImport(false) {}
     67 
     68   TypoCorrection()
     69       : CorrectionNameSpec(nullptr), CharDistance(0), QualifierDistance(0),
     70         CallbackDistance(0), ForceSpecifierReplacement(false),
     71         RequiresImport(false) {}
     72 
     73   /// \brief Gets the DeclarationName of the typo correction
     74   DeclarationName getCorrection() const { return CorrectionName; }
     75   IdentifierInfo* getCorrectionAsIdentifierInfo() const {
     76     return CorrectionName.getAsIdentifierInfo();
     77   }
     78 
     79   /// \brief Gets the NestedNameSpecifier needed to use the typo correction
     80   NestedNameSpecifier* getCorrectionSpecifier() const {
     81     return CorrectionNameSpec;
     82   }
     83   void setCorrectionSpecifier(NestedNameSpecifier* NNS) {
     84     CorrectionNameSpec = NNS;
     85     ForceSpecifierReplacement = (NNS != nullptr);
     86   }
     87 
     88   void WillReplaceSpecifier(bool ForceReplacement) {
     89     ForceSpecifierReplacement = ForceReplacement;
     90   }
     91 
     92   bool WillReplaceSpecifier() const {
     93     return ForceSpecifierReplacement;
     94   }
     95 
     96   void setQualifierDistance(unsigned ED) {
     97     QualifierDistance = ED;
     98   }
     99 
    100   void setCallbackDistance(unsigned ED) {
    101     CallbackDistance = ED;
    102   }
    103 
    104   // Convert the given weighted edit distance to a roughly equivalent number of
    105   // single-character edits (typically for comparison to the length of the
    106   // string being edited).
    107   static unsigned NormalizeEditDistance(unsigned ED) {
    108     if (ED > MaximumDistance)
    109       return InvalidDistance;
    110     return (ED + CharDistanceWeight / 2) / CharDistanceWeight;
    111   }
    112 
    113   /// \brief Gets the "edit distance" of the typo correction from the typo.
    114   /// If Normalized is true, scale the distance down by the CharDistanceWeight
    115   /// to return the edit distance in terms of single-character edits.
    116   unsigned getEditDistance(bool Normalized = true) const {
    117     if (CharDistance > MaximumDistance || QualifierDistance > MaximumDistance ||
    118         CallbackDistance > MaximumDistance)
    119       return InvalidDistance;
    120     unsigned ED =
    121         CharDistance * CharDistanceWeight +
    122         QualifierDistance * QualifierDistanceWeight +
    123         CallbackDistance * CallbackDistanceWeight;
    124     if (ED > MaximumDistance)
    125       return InvalidDistance;
    126     // Half the CharDistanceWeight is added to ED to simulate rounding since
    127     // integer division truncates the value (i.e. round-to-nearest-int instead
    128     // of round-to-zero).
    129     return Normalized ? NormalizeEditDistance(ED) : ED;
    130   }
    131 
    132   /// \brief Gets the pointer to the declaration of the typo correction
    133   NamedDecl *getCorrectionDecl() const {
    134     return hasCorrectionDecl() ? *(CorrectionDecls.begin()) : nullptr;
    135   }
    136   template <class DeclClass>
    137   DeclClass *getCorrectionDeclAs() const {
    138     return dyn_cast_or_null<DeclClass>(getCorrectionDecl());
    139   }
    140 
    141   /// \brief Clears the list of NamedDecls.
    142   void ClearCorrectionDecls() {
    143     CorrectionDecls.clear();
    144   }
    145 
    146   /// \brief Clears the list of NamedDecls before adding the new one.
    147   void setCorrectionDecl(NamedDecl *CDecl) {
    148     CorrectionDecls.clear();
    149     addCorrectionDecl(CDecl);
    150   }
    151 
    152   /// \brief Clears the list of NamedDecls and adds the given set.
    153   void setCorrectionDecls(ArrayRef<NamedDecl*> Decls) {
    154     CorrectionDecls.clear();
    155     CorrectionDecls.insert(CorrectionDecls.begin(), Decls.begin(), Decls.end());
    156   }
    157 
    158   /// \brief Add the given NamedDecl to the list of NamedDecls that are the
    159   /// declarations associated with the DeclarationName of this TypoCorrection
    160   void addCorrectionDecl(NamedDecl *CDecl);
    161 
    162   std::string getAsString(const LangOptions &LO) const;
    163   std::string getQuoted(const LangOptions &LO) const {
    164     return "'" + getAsString(LO) + "'";
    165   }
    166 
    167   /// \brief Returns whether this TypoCorrection has a non-empty DeclarationName
    168   explicit operator bool() const { return bool(CorrectionName); }
    169 
    170   /// \brief Mark this TypoCorrection as being a keyword.
    171   /// Since addCorrectionDeclsand setCorrectionDecl don't allow NULL to be
    172   /// added to the list of the correction's NamedDecl pointers, NULL is added
    173   /// as the only element in the list to mark this TypoCorrection as a keyword.
    174   void makeKeyword() {
    175     CorrectionDecls.clear();
    176     CorrectionDecls.push_back(nullptr);
    177     ForceSpecifierReplacement = true;
    178   }
    179 
    180   // Check if this TypoCorrection is a keyword by checking if the first
    181   // item in CorrectionDecls is NULL.
    182   bool isKeyword() const {
    183     return !CorrectionDecls.empty() &&
    184         CorrectionDecls.front() == nullptr;
    185   }
    186 
    187   // Check if this TypoCorrection is the given keyword.
    188   template<std::size_t StrLen>
    189   bool isKeyword(const char (&Str)[StrLen]) const {
    190     return isKeyword() && getCorrectionAsIdentifierInfo()->isStr(Str);
    191   }
    192 
    193   // Returns true if the correction either is a keyword or has a known decl.
    194   bool isResolved() const { return !CorrectionDecls.empty(); }
    195 
    196   bool isOverloaded() const {
    197     return CorrectionDecls.size() > 1;
    198   }
    199 
    200   void setCorrectionRange(CXXScopeSpec *SS,
    201                           const DeclarationNameInfo &TypoName) {
    202     CorrectionRange = TypoName.getSourceRange();
    203     if (ForceSpecifierReplacement && SS && !SS->isEmpty())
    204       CorrectionRange.setBegin(SS->getBeginLoc());
    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   explicit CorrectionCandidateCallback(IdentifierInfo *Typo = nullptr,
    251                                        NestedNameSpecifier *TypoNNS = nullptr)
    252       : WantTypeSpecifiers(true), WantExpressionKeywords(true),
    253         WantCXXNamedCasts(true), WantFunctionLikeCasts(true),
    254         WantRemainingKeywords(true), WantObjCSuper(false),
    255         IsObjCIvarLookup(false), IsAddressOfOperand(false), Typo(Typo),
    256         TypoNNS(TypoNNS) {}
    257 
    258   virtual ~CorrectionCandidateCallback() {}
    259 
    260   /// \brief Simple predicate used by the default RankCandidate to
    261   /// determine whether to return an edit distance of 0 or InvalidDistance.
    262   /// This can be overrided by validators that only need to determine if a
    263   /// candidate is viable, without ranking potentially viable candidates.
    264   /// Only ValidateCandidate or RankCandidate need to be overriden by a
    265   /// callback wishing to check the viability of correction candidates.
    266   /// The default predicate always returns true if the candidate is not a type
    267   /// name or keyword, true for types if WantTypeSpecifiers is true, and true
    268   /// for keywords if WantTypeSpecifiers, WantExpressionKeywords,
    269   /// WantCXXNamedCasts, WantRemainingKeywords, or WantObjCSuper is true.
    270   virtual bool ValidateCandidate(const TypoCorrection &candidate);
    271 
    272   /// \brief Method used by Sema::CorrectTypo to assign an "edit distance" rank
    273   /// to a candidate (where a lower value represents a better candidate), or
    274   /// returning InvalidDistance if the candidate is not at all viable. For
    275   /// validation callbacks that only need to determine if a candidate is viable,
    276   /// the default RankCandidate returns either 0 or InvalidDistance depending
    277   /// whether ValidateCandidate returns true or false.
    278   virtual unsigned RankCandidate(const TypoCorrection &candidate) {
    279     return (!MatchesTypo(candidate) && ValidateCandidate(candidate))
    280                ? 0
    281                : InvalidDistance;
    282   }
    283 
    284   void setTypoName(IdentifierInfo *II) { Typo = II; }
    285   void setTypoNNS(NestedNameSpecifier *NNS) { TypoNNS = NNS; }
    286 
    287   // Flags for context-dependent keywords. WantFunctionLikeCasts is only
    288   // used/meaningful when WantCXXNamedCasts is false.
    289   // TODO: Expand these to apply to non-keywords or possibly remove them.
    290   bool WantTypeSpecifiers;
    291   bool WantExpressionKeywords;
    292   bool WantCXXNamedCasts;
    293   bool WantFunctionLikeCasts;
    294   bool WantRemainingKeywords;
    295   bool WantObjCSuper;
    296   // Temporary hack for the one case where a CorrectTypoContext enum is used
    297   // when looking up results.
    298   bool IsObjCIvarLookup;
    299   bool IsAddressOfOperand;
    300 
    301 protected:
    302   bool MatchesTypo(const TypoCorrection &candidate) {
    303     return Typo && candidate.isResolved() && !candidate.requiresImport() &&
    304            candidate.getCorrectionAsIdentifierInfo() == Typo &&
    305            // FIXME: This probably does not return true when both
    306            // NestedNameSpecifiers have the same textual representation.
    307            candidate.getCorrectionSpecifier() == TypoNNS;
    308   }
    309 
    310   IdentifierInfo *Typo;
    311   NestedNameSpecifier *TypoNNS;
    312 };
    313 
    314 /// @brief Simple template class for restricting typo correction candidates
    315 /// to ones having a single Decl* of the given type.
    316 template <class C>
    317 class DeclFilterCCC : public CorrectionCandidateCallback {
    318 public:
    319   bool ValidateCandidate(const TypoCorrection &candidate) override {
    320     return candidate.getCorrectionDeclAs<C>();
    321   }
    322 };
    323 
    324 // @brief Callback class to limit the allowed keywords and to only accept typo
    325 // corrections that are keywords or whose decls refer to functions (or template
    326 // functions) that accept the given number of arguments.
    327 class FunctionCallFilterCCC : public CorrectionCandidateCallback {
    328 public:
    329   FunctionCallFilterCCC(Sema &SemaRef, unsigned NumArgs,
    330                         bool HasExplicitTemplateArgs,
    331                         MemberExpr *ME = nullptr);
    332 
    333   bool ValidateCandidate(const TypoCorrection &candidate) override;
    334 
    335  private:
    336   unsigned NumArgs;
    337   bool HasExplicitTemplateArgs;
    338   DeclContext *CurContext;
    339   MemberExpr *MemberFn;
    340 };
    341 
    342 // @brief Callback class that effectively disabled typo correction
    343 class NoTypoCorrectionCCC : public CorrectionCandidateCallback {
    344 public:
    345   NoTypoCorrectionCCC() {
    346     WantTypeSpecifiers = false;
    347     WantExpressionKeywords = false;
    348     WantCXXNamedCasts = false;
    349     WantFunctionLikeCasts = false;
    350     WantRemainingKeywords = false;
    351   }
    352 
    353   bool ValidateCandidate(const TypoCorrection &candidate) override {
    354     return false;
    355   }
    356 };
    357 
    358 }
    359 
    360 #endif
    361