1 //===--- TypoCorrection.h - Class for typo correction results ---*- C++ -*-===//
3 // The LLVM Compiler Infrastructure
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
8 //===----------------------------------------------------------------------===//
10 // This file defines the TypoCorrection class, which stores the results of
11 // Sema's typo correction (Sema::CorrectTypo).
13 //===----------------------------------------------------------------------===//
15 #ifndef LLVM_CLANG_SEMA_TYPOCORRECTION_H
16 #define LLVM_CLANG_SEMA_TYPOCORRECTION_H
18 #include "clang/AST/DeclCXX.h"
19 #include "clang/Sema/DeclSpec.h"
20 #include "clang/Sema/Ownership.h"
21 #include "llvm/ADT/SmallVector.h"
25 /// @brief Simple class containing the result of Sema::CorrectTypo
26 class TypoCorrection {
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;
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;
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) {
50 CorrectionDecls.push_back(NameDecl);
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) {
59 CorrectionDecls.push_back(Name);
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) {}
69 : CorrectionNameSpec(nullptr), CharDistance(0), QualifierDistance(0),
70 CallbackDistance(0), ForceSpecifierReplacement(false),
71 RequiresImport(false) {}
73 /// \brief Gets the DeclarationName of the typo correction
74 DeclarationName getCorrection() const { return CorrectionName; }
75 IdentifierInfo *getCorrectionAsIdentifierInfo() const {
76 return CorrectionName.getAsIdentifierInfo();
79 /// \brief Gets the NestedNameSpecifier needed to use the typo correction
80 NestedNameSpecifier *getCorrectionSpecifier() const {
81 return CorrectionNameSpec;
83 void setCorrectionSpecifier(NestedNameSpecifier *NNS) {
84 CorrectionNameSpec = NNS;
85 ForceSpecifierReplacement = (NNS != nullptr);
88 void WillReplaceSpecifier(bool ForceReplacement) {
89 ForceSpecifierReplacement = ForceReplacement;
92 bool WillReplaceSpecifier() const {
93 return ForceSpecifierReplacement;
96 void setQualifierDistance(unsigned ED) {
97 QualifierDistance = ED;
100 void setCallbackDistance(unsigned ED) {
101 CallbackDistance = ED;
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;
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;
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;
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;
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;
143 template <class DeclClass>
144 DeclClass *getCorrectionDeclAs() const {
145 return dyn_cast_or_null<DeclClass>(getCorrectionDecl());
148 /// \brief Clears the list of NamedDecls.
149 void ClearCorrectionDecls() {
150 CorrectionDecls.clear();
153 /// \brief Clears the list of NamedDecls before adding the new one.
154 void setCorrectionDecl(NamedDecl *CDecl) {
155 CorrectionDecls.clear();
156 addCorrectionDecl(CDecl);
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());
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);
169 std::string getAsString(const LangOptions &LO) const;
170 std::string getQuoted(const LangOptions &LO) const {
171 return "'" + getAsString(LO) + "'";
174 /// \brief Returns whether this TypoCorrection has a non-empty DeclarationName
175 explicit operator bool() const { return bool(CorrectionName); }
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.
182 CorrectionDecls.clear();
183 CorrectionDecls.push_back(nullptr);
184 ForceSpecifierReplacement = true;
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;
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);
199 // Returns true if the correction either is a keyword or has a known decl.
200 bool isResolved() const { return !CorrectionDecls.empty(); }
202 bool isOverloaded() const {
203 return CorrectionDecls.size() > 1;
206 void setCorrectionRange(CXXScopeSpec *SS,
207 const DeclarationNameInfo &TypoName) {
208 CorrectionRange = TypoName.getSourceRange();
209 if (ForceSpecifierReplacement && SS && !SS->isEmpty())
210 CorrectionRange.setBegin(SS->getBeginLoc());
213 SourceRange getCorrectionRange() const {
214 return CorrectionRange;
217 typedef SmallVectorImpl<NamedDecl *>::iterator decl_iterator;
218 decl_iterator begin() {
219 return isKeyword() ? CorrectionDecls.end() : CorrectionDecls.begin();
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();
226 const_decl_iterator end() const { return CorrectionDecls.end(); }
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; }
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));
238 ArrayRef<PartialDiagnostic> getExtraDiagnostics() const {
239 return ExtraDiagnostics;
243 bool hasCorrectionDecl() const {
244 return (!isKeyword() && !CorrectionDecls.empty());
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;
258 std::vector<PartialDiagnostic> ExtraDiagnostics;
261 /// @brief Base class for callback objects used by Sema::CorrectTypo to check
262 /// the validity of a potential typo correction.
263 class CorrectionCandidateCallback {
265 static const unsigned InvalidDistance = TypoCorrection::InvalidDistance;
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),
275 virtual ~CorrectionCandidateCallback() {}
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);
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))
301 void setTypoName(IdentifierInfo *II) { Typo = II; }
302 void setTypoNNS(NestedNameSpecifier *NNS) { TypoNNS = NNS; }
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;
313 // Temporary hack for the one case where a CorrectTypoContext enum is used
314 // when looking up results.
315 bool IsObjCIvarLookup;
316 bool IsAddressOfOperand;
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;
327 IdentifierInfo *Typo;
328 NestedNameSpecifier *TypoNNS;
331 /// @brief Simple template class for restricting typo correction candidates
332 /// to ones having a single Decl* of the given type.
334 class DeclFilterCCC : public CorrectionCandidateCallback {
336 bool ValidateCandidate(const TypoCorrection &candidate) override {
337 return candidate.getCorrectionDeclAs<C>();
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 {
346 FunctionCallFilterCCC(Sema &SemaRef, unsigned NumArgs,
347 bool HasExplicitTemplateArgs,
348 MemberExpr *ME = nullptr);
350 bool ValidateCandidate(const TypoCorrection &candidate) override;
354 bool HasExplicitTemplateArgs;
355 DeclContext *CurContext;
356 MemberExpr *MemberFn;
359 // @brief Callback class that effectively disabled typo correction
360 class NoTypoCorrectionCCC : public CorrectionCandidateCallback {
362 NoTypoCorrectionCCC() {
363 WantTypeSpecifiers = false;
364 WantExpressionKeywords = false;
365 WantCXXNamedCasts = false;
366 WantFunctionLikeCasts = false;
367 WantRemainingKeywords = false;
370 bool ValidateCandidate(const TypoCorrection &candidate) override {