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