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