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 Get the correction declaration found by name lookup (before we
    133   /// looked through using shadow declarations and the like).
    134   NamedDecl *getFoundDecl() const {
    135     return hasCorrectionDecl() ? *(CorrectionDecls.begin()) : nullptr;
    136   }
    137 
    138   /// \brief Gets the pointer to the declaration of the typo correction
    139   NamedDecl *getCorrectionDecl() const {
    140     auto *D = getFoundDecl();
    141     return D ? D->getUnderlyingDecl() : nullptr;
    142   }
    143   template <class DeclClass>
    144   DeclClass *getCorrectionDeclAs() const {
    145     return dyn_cast_or_null<DeclClass>(getCorrectionDecl());
    146   }
    147 
    148   /// \brief Clears the list of NamedDecls.
    149   void ClearCorrectionDecls() {
    150     CorrectionDecls.clear();
    151   }
    152 
    153   /// \brief Clears the list of NamedDecls before adding the new one.
    154   void setCorrectionDecl(NamedDecl *CDecl) {
    155     CorrectionDecls.clear();
    156     addCorrectionDecl(CDecl);
    157   }
    158 
    159   /// \brief Clears the list of NamedDecls and adds the given set.
    160   void setCorrectionDecls(ArrayRef<NamedDecl*> Decls) {
    161     CorrectionDecls.clear();
    162     CorrectionDecls.insert(CorrectionDecls.begin(), Decls.begin(), Decls.end());
    163   }
    164 
    165   /// \brief Add the given NamedDecl to the list of NamedDecls that are the
    166   /// declarations associated with the DeclarationName of this TypoCorrection
    167   void addCorrectionDecl(NamedDecl *CDecl);
    168 
    169   std::string getAsString(const LangOptions &LO) const;
    170   std::string getQuoted(const LangOptions &LO) const {
    171     return "'" + getAsString(LO) + "'";
    172   }
    173 
    174   /// \brief Returns whether this TypoCorrection has a non-empty DeclarationName
    175   explicit operator bool() const { return bool(CorrectionName); }
    176 
    177   /// \brief Mark this TypoCorrection as being a keyword.
    178   /// Since addCorrectionDeclsand setCorrectionDecl don't allow NULL to be
    179   /// added to the list of the correction's NamedDecl pointers, NULL is added
    180   /// as the only element in the list to mark this TypoCorrection as a keyword.
    181   void makeKeyword() {
    182     CorrectionDecls.clear();
    183     CorrectionDecls.push_back(nullptr);
    184     ForceSpecifierReplacement = true;
    185   }
    186 
    187   // Check if this TypoCorrection is a keyword by checking if the first
    188   // item in CorrectionDecls is NULL.
    189   bool isKeyword() const {
    190     return !CorrectionDecls.empty() && CorrectionDecls.front() == nullptr;
    191   }
    192 
    193   // Check if this TypoCorrection is the given keyword.
    194   template<std::size_t StrLen>
    195   bool isKeyword(const char (&Str)[StrLen]) const {
    196     return isKeyword() && getCorrectionAsIdentifierInfo()->isStr(Str);
    197   }
    198 
    199   // Returns true if the correction either is a keyword or has a known decl.
    200   bool isResolved() const { return !CorrectionDecls.empty(); }
    201 
    202   bool isOverloaded() const {
    203     return CorrectionDecls.size() > 1;
    204   }
    205 
    206   void setCorrectionRange(CXXScopeSpec *SS,
    207                           const DeclarationNameInfo &TypoName) {
    208     CorrectionRange = TypoName.getSourceRange();
    209     if (ForceSpecifierReplacement && SS && !SS->isEmpty())
    210       CorrectionRange.setBegin(SS->getBeginLoc());
    211   }
    212 
    213   SourceRange getCorrectionRange() const {
    214     return CorrectionRange;
    215   }
    216 
    217   typedef SmallVectorImpl<NamedDecl *>::iterator decl_iterator;
    218   decl_iterator begin() {
    219     return isKeyword() ? CorrectionDecls.end() : CorrectionDecls.begin();
    220   }
    221   decl_iterator end() { return CorrectionDecls.end(); }
    222   typedef SmallVectorImpl<NamedDecl *>::const_iterator const_decl_iterator;
    223   const_decl_iterator begin() const {
    224     return isKeyword() ? CorrectionDecls.end() : CorrectionDecls.begin();
    225   }
    226   const_decl_iterator end() const { return CorrectionDecls.end(); }
    227 
    228   /// \brief Returns whether this typo correction is correcting to a
    229   /// declaration that was declared in a module that has not been imported.
    230   bool requiresImport() const { return RequiresImport; }
    231   void setRequiresImport(bool Req) { RequiresImport = Req; }
    232 
    233   /// Extra diagnostics are printed after the first diagnostic for the typo.
    234   /// This can be used to attach external notes to the diag.
    235   void addExtraDiagnostic(PartialDiagnostic PD) {
    236     ExtraDiagnostics.push_back(std::move(PD));
    237   }
    238   ArrayRef<PartialDiagnostic> getExtraDiagnostics() const {
    239     return ExtraDiagnostics;
    240   }
    241 
    242 private:
    243   bool hasCorrectionDecl() const {
    244     return (!isKeyword() && !CorrectionDecls.empty());
    245   }
    246 
    247   // Results.
    248   DeclarationName CorrectionName;
    249   NestedNameSpecifier *CorrectionNameSpec;
    250   SmallVector<NamedDecl *, 1> CorrectionDecls;
    251   unsigned CharDistance;
    252   unsigned QualifierDistance;
    253   unsigned CallbackDistance;
    254   SourceRange CorrectionRange;
    255   bool ForceSpecifierReplacement;
    256   bool RequiresImport;
    257 
    258   std::vector<PartialDiagnostic> ExtraDiagnostics;
    259 };
    260 
    261 /// @brief Base class for callback objects used by Sema::CorrectTypo to check
    262 /// the validity of a potential typo correction.
    263 class CorrectionCandidateCallback {
    264 public:
    265   static const unsigned InvalidDistance = TypoCorrection::InvalidDistance;
    266 
    267   explicit CorrectionCandidateCallback(IdentifierInfo *Typo = nullptr,
    268                                        NestedNameSpecifier *TypoNNS = nullptr)
    269       : WantTypeSpecifiers(true), WantExpressionKeywords(true),
    270         WantCXXNamedCasts(true), WantFunctionLikeCasts(true),
    271         WantRemainingKeywords(true), WantObjCSuper(false),
    272         IsObjCIvarLookup(false), IsAddressOfOperand(false), Typo(Typo),
    273         TypoNNS(TypoNNS) {}
    274 
    275   virtual ~CorrectionCandidateCallback() {}
    276 
    277   /// \brief Simple predicate used by the default RankCandidate to
    278   /// determine whether to return an edit distance of 0 or InvalidDistance.
    279   /// This can be overrided by validators that only need to determine if a
    280   /// candidate is viable, without ranking potentially viable candidates.
    281   /// Only ValidateCandidate or RankCandidate need to be overriden by a
    282   /// callback wishing to check the viability of correction candidates.
    283   /// The default predicate always returns true if the candidate is not a type
    284   /// name or keyword, true for types if WantTypeSpecifiers is true, and true
    285   /// for keywords if WantTypeSpecifiers, WantExpressionKeywords,
    286   /// WantCXXNamedCasts, WantRemainingKeywords, or WantObjCSuper is true.
    287   virtual bool ValidateCandidate(const TypoCorrection &candidate);
    288 
    289   /// \brief Method used by Sema::CorrectTypo to assign an "edit distance" rank
    290   /// to a candidate (where a lower value represents a better candidate), or
    291   /// returning InvalidDistance if the candidate is not at all viable. For
    292   /// validation callbacks that only need to determine if a candidate is viable,
    293   /// the default RankCandidate returns either 0 or InvalidDistance depending
    294   /// whether ValidateCandidate returns true or false.
    295   virtual unsigned RankCandidate(const TypoCorrection &candidate) {
    296     return (!MatchesTypo(candidate) && ValidateCandidate(candidate))
    297                ? 0
    298                : InvalidDistance;
    299   }
    300 
    301   void setTypoName(IdentifierInfo *II) { Typo = II; }
    302   void setTypoNNS(NestedNameSpecifier *NNS) { TypoNNS = NNS; }
    303 
    304   // Flags for context-dependent keywords. WantFunctionLikeCasts is only
    305   // used/meaningful when WantCXXNamedCasts is false.
    306   // TODO: Expand these to apply to non-keywords or possibly remove them.
    307   bool WantTypeSpecifiers;
    308   bool WantExpressionKeywords;
    309   bool WantCXXNamedCasts;
    310   bool WantFunctionLikeCasts;
    311   bool WantRemainingKeywords;
    312   bool WantObjCSuper;
    313   // Temporary hack for the one case where a CorrectTypoContext enum is used
    314   // when looking up results.
    315   bool IsObjCIvarLookup;
    316   bool IsAddressOfOperand;
    317 
    318 protected:
    319   bool MatchesTypo(const TypoCorrection &candidate) {
    320     return Typo && candidate.isResolved() && !candidate.requiresImport() &&
    321            candidate.getCorrectionAsIdentifierInfo() == Typo &&
    322            // FIXME: This probably does not return true when both
    323            // NestedNameSpecifiers have the same textual representation.
    324            candidate.getCorrectionSpecifier() == TypoNNS;
    325   }
    326 
    327   IdentifierInfo *Typo;
    328   NestedNameSpecifier *TypoNNS;
    329 };
    330 
    331 /// @brief Simple template class for restricting typo correction candidates
    332 /// to ones having a single Decl* of the given type.
    333 template <class C>
    334 class DeclFilterCCC : public CorrectionCandidateCallback {
    335 public:
    336   bool ValidateCandidate(const TypoCorrection &candidate) override {
    337     return candidate.getCorrectionDeclAs<C>();
    338   }
    339 };
    340 
    341 // @brief Callback class to limit the allowed keywords and to only accept typo
    342 // corrections that are keywords or whose decls refer to functions (or template
    343 // functions) that accept the given number of arguments.
    344 class FunctionCallFilterCCC : public CorrectionCandidateCallback {
    345 public:
    346   FunctionCallFilterCCC(Sema &SemaRef, unsigned NumArgs,
    347                         bool HasExplicitTemplateArgs,
    348                         MemberExpr *ME = nullptr);
    349 
    350   bool ValidateCandidate(const TypoCorrection &candidate) override;
    351 
    352  private:
    353   unsigned NumArgs;
    354   bool HasExplicitTemplateArgs;
    355   DeclContext *CurContext;
    356   MemberExpr *MemberFn;
    357 };
    358 
    359 // @brief Callback class that effectively disabled typo correction
    360 class NoTypoCorrectionCCC : public CorrectionCandidateCallback {
    361 public:
    362   NoTypoCorrectionCCC() {
    363     WantTypeSpecifiers = false;
    364     WantExpressionKeywords = false;
    365     WantCXXNamedCasts = false;
    366     WantFunctionLikeCasts = false;
    367     WantRemainingKeywords = false;
    368   }
    369 
    370   bool ValidateCandidate(const TypoCorrection &candidate) override {
    371     return false;
    372   }
    373 };
    374 
    375 }
    376 
    377 #endif
    378