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) CorrectionName(Name)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) CorrectionName(Name)63 : CorrectionName(Name), CorrectionNameSpec(NNS), 64 CharDistance(CharDistance), QualifierDistance(0), CallbackDistance(0), 65 ForceSpecifierReplacement(false), RequiresImport(false) {} 66 TypoCorrection()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 getCorrection()73 DeclarationName getCorrection() const { return CorrectionName; } getCorrectionAsIdentifierInfo()74 IdentifierInfo* getCorrectionAsIdentifierInfo() const { 75 return CorrectionName.getAsIdentifierInfo(); 76 } 77 78 /// \brief Gets the NestedNameSpecifier needed to use the typo correction getCorrectionSpecifier()79 NestedNameSpecifier* getCorrectionSpecifier() const { 80 return CorrectionNameSpec; 81 } setCorrectionSpecifier(NestedNameSpecifier * NNS)82 void setCorrectionSpecifier(NestedNameSpecifier* NNS) { 83 CorrectionNameSpec = NNS; 84 ForceSpecifierReplacement = (NNS != nullptr); 85 } 86 WillReplaceSpecifier(bool ForceReplacement)87 void WillReplaceSpecifier(bool ForceReplacement) { 88 ForceSpecifierReplacement = ForceReplacement; 89 } 90 WillReplaceSpecifier()91 bool WillReplaceSpecifier() const { 92 return ForceSpecifierReplacement; 93 } 94 setQualifierDistance(unsigned ED)95 void setQualifierDistance(unsigned ED) { 96 QualifierDistance = ED; 97 } 98 setCallbackDistance(unsigned ED)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). NormalizeEditDistance(unsigned ED)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 getCorrectionDecl()132 NamedDecl *getCorrectionDecl() const { 133 return hasCorrectionDecl() ? *(CorrectionDecls.begin()) : nullptr; 134 } 135 template <class DeclClass> getCorrectionDeclAs()136 DeclClass *getCorrectionDeclAs() const { 137 return dyn_cast_or_null<DeclClass>(getCorrectionDecl()); 138 } 139 140 /// \brief Clears the list of NamedDecls. ClearCorrectionDecls()141 void ClearCorrectionDecls() { 142 CorrectionDecls.clear(); 143 } 144 145 /// \brief Clears the list of NamedDecls before adding the new one. setCorrectionDecl(NamedDecl * CDecl)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. setCorrectionDecls(ArrayRef<NamedDecl * > Decls)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; getQuoted(const LangOptions & LO)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. makeKeyword()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. isKeyword()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> isKeyword(const char (& Str)[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. isResolved()193 bool isResolved() const { return !CorrectionDecls.empty(); } 194 isOverloaded()195 bool isOverloaded() const { 196 return CorrectionDecls.size() > 1; 197 } 198 setCorrectionRange(CXXScopeSpec * SS,const DeclarationNameInfo & TypoName)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 getCorrectionRange()207 SourceRange getCorrectionRange() const { 208 return CorrectionRange; 209 } 210 211 typedef SmallVectorImpl<NamedDecl *>::iterator decl_iterator; begin()212 decl_iterator begin() { 213 return isKeyword() ? CorrectionDecls.end() : CorrectionDecls.begin(); 214 } end()215 decl_iterator end() { return CorrectionDecls.end(); } 216 typedef SmallVectorImpl<NamedDecl *>::const_iterator const_decl_iterator; begin()217 const_decl_iterator begin() const { 218 return isKeyword() ? CorrectionDecls.end() : CorrectionDecls.begin(); 219 } end()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. requiresImport()224 bool requiresImport() const { return RequiresImport; } setRequiresImport(bool Req)225 void setRequiresImport(bool Req) { RequiresImport = Req; } 226 227 private: hasCorrectionDecl()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 CorrectionCandidateCallback()250 CorrectionCandidateCallback() 251 : WantTypeSpecifiers(true), WantExpressionKeywords(true), 252 WantCXXNamedCasts(true), WantRemainingKeywords(true), 253 WantObjCSuper(false), IsObjCIvarLookup(false), 254 IsAddressOfOperand(false) {} 255 ~CorrectionCandidateCallback()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. RankCandidate(const TypoCorrection & candidate)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: ValidateCandidate(const TypoCorrection & candidate)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: NoTypoCorrectionCCC()324 NoTypoCorrectionCCC() { 325 WantTypeSpecifiers = false; 326 WantExpressionKeywords = false; 327 WantCXXNamedCasts = false; 328 WantRemainingKeywords = false; 329 } 330 ValidateCandidate(const TypoCorrection & candidate)331 bool ValidateCandidate(const TypoCorrection &candidate) override { 332 return false; 333 } 334 }; 335 336 } 337 338 #endif 339