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