• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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)
CorrectionName(Name)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)
CorrectionName(Name)64       : CorrectionName(Name), CorrectionNameSpec(NNS),
65         CharDistance(CharDistance), QualifierDistance(0), CallbackDistance(0),
66         ForceSpecifierReplacement(false), RequiresImport(false) {}
67 
TypoCorrection()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
getCorrection()74   DeclarationName getCorrection() const { return CorrectionName; }
getCorrectionAsIdentifierInfo()75   IdentifierInfo *getCorrectionAsIdentifierInfo() const {
76     return CorrectionName.getAsIdentifierInfo();
77   }
78 
79   /// \brief Gets the NestedNameSpecifier needed to use the typo correction
getCorrectionSpecifier()80   NestedNameSpecifier *getCorrectionSpecifier() const {
81     return CorrectionNameSpec;
82   }
setCorrectionSpecifier(NestedNameSpecifier * NNS)83   void setCorrectionSpecifier(NestedNameSpecifier *NNS) {
84     CorrectionNameSpec = NNS;
85     ForceSpecifierReplacement = (NNS != nullptr);
86   }
87 
WillReplaceSpecifier(bool ForceReplacement)88   void WillReplaceSpecifier(bool ForceReplacement) {
89     ForceSpecifierReplacement = ForceReplacement;
90   }
91 
WillReplaceSpecifier()92   bool WillReplaceSpecifier() const {
93     return ForceSpecifierReplacement;
94   }
95 
setQualifierDistance(unsigned ED)96   void setQualifierDistance(unsigned ED) {
97     QualifierDistance = ED;
98   }
99 
setCallbackDistance(unsigned ED)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).
NormalizeEditDistance(unsigned ED)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 Get the correction declaration found by name lookup (before we
133   /// looked through using shadow declarations and the like).
getFoundDecl()134   NamedDecl *getFoundDecl() const {
135     return hasCorrectionDecl() ? *(CorrectionDecls.begin()) : nullptr;
136   }
137 
138   /// \brief Gets the pointer to the declaration of the typo correction
getCorrectionDecl()139   NamedDecl *getCorrectionDecl() const {
140     auto *D = getFoundDecl();
141     return D ? D->getUnderlyingDecl() : nullptr;
142   }
143   template <class DeclClass>
getCorrectionDeclAs()144   DeclClass *getCorrectionDeclAs() const {
145     return dyn_cast_or_null<DeclClass>(getCorrectionDecl());
146   }
147 
148   /// \brief Clears the list of NamedDecls.
ClearCorrectionDecls()149   void ClearCorrectionDecls() {
150     CorrectionDecls.clear();
151   }
152 
153   /// \brief Clears the list of NamedDecls before adding the new one.
setCorrectionDecl(NamedDecl * CDecl)154   void setCorrectionDecl(NamedDecl *CDecl) {
155     CorrectionDecls.clear();
156     addCorrectionDecl(CDecl);
157   }
158 
159   /// \brief Clears the list of NamedDecls and adds the given set.
setCorrectionDecls(ArrayRef<NamedDecl * > Decls)160   void setCorrectionDecls(ArrayRef<NamedDecl*> Decls) {
161     CorrectionDecls.clear();
162     CorrectionDecls.insert(CorrectionDecls.begin(), Decls.begin(), Decls.end());
163   }
164 
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);
168 
169   std::string getAsString(const LangOptions &LO) const;
getQuoted(const LangOptions & LO)170   std::string getQuoted(const LangOptions &LO) const {
171     return "'" + getAsString(LO) + "'";
172   }
173 
174   /// \brief Returns whether this TypoCorrection has a non-empty DeclarationName
175   explicit operator bool() const { return bool(CorrectionName); }
176 
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.
makeKeyword()181   void makeKeyword() {
182     CorrectionDecls.clear();
183     CorrectionDecls.push_back(nullptr);
184     ForceSpecifierReplacement = true;
185   }
186 
187   // Check if this TypoCorrection is a keyword by checking if the first
188   // item in CorrectionDecls is NULL.
isKeyword()189   bool isKeyword() const {
190     return !CorrectionDecls.empty() && CorrectionDecls.front() == nullptr;
191   }
192 
193   // Check if this TypoCorrection is the given keyword.
194   template<std::size_t StrLen>
isKeyword(const char (& Str)[StrLen])195   bool isKeyword(const char (&Str)[StrLen]) const {
196     return isKeyword() && getCorrectionAsIdentifierInfo()->isStr(Str);
197   }
198 
199   // Returns true if the correction either is a keyword or has a known decl.
isResolved()200   bool isResolved() const { return !CorrectionDecls.empty(); }
201 
isOverloaded()202   bool isOverloaded() const {
203     return CorrectionDecls.size() > 1;
204   }
205 
setCorrectionRange(CXXScopeSpec * SS,const DeclarationNameInfo & TypoName)206   void setCorrectionRange(CXXScopeSpec *SS,
207                           const DeclarationNameInfo &TypoName) {
208     CorrectionRange = TypoName.getSourceRange();
209     if (ForceSpecifierReplacement && SS && !SS->isEmpty())
210       CorrectionRange.setBegin(SS->getBeginLoc());
211   }
212 
getCorrectionRange()213   SourceRange getCorrectionRange() const {
214     return CorrectionRange;
215   }
216 
217   typedef SmallVectorImpl<NamedDecl *>::iterator decl_iterator;
begin()218   decl_iterator begin() {
219     return isKeyword() ? CorrectionDecls.end() : CorrectionDecls.begin();
220   }
end()221   decl_iterator end() { return CorrectionDecls.end(); }
222   typedef SmallVectorImpl<NamedDecl *>::const_iterator const_decl_iterator;
begin()223   const_decl_iterator begin() const {
224     return isKeyword() ? CorrectionDecls.end() : CorrectionDecls.begin();
225   }
end()226   const_decl_iterator end() const { return CorrectionDecls.end(); }
227 
228   /// \brief Returns whether this typo correction is correcting to a
229   /// declaration that was declared in a module that has not been imported.
requiresImport()230   bool requiresImport() const { return RequiresImport; }
setRequiresImport(bool Req)231   void setRequiresImport(bool Req) { RequiresImport = Req; }
232 
233 private:
hasCorrectionDecl()234   bool hasCorrectionDecl() const {
235     return (!isKeyword() && !CorrectionDecls.empty());
236   }
237 
238   // Results.
239   DeclarationName CorrectionName;
240   NestedNameSpecifier *CorrectionNameSpec;
241   SmallVector<NamedDecl *, 1> CorrectionDecls;
242   unsigned CharDistance;
243   unsigned QualifierDistance;
244   unsigned CallbackDistance;
245   SourceRange CorrectionRange;
246   bool ForceSpecifierReplacement;
247   bool RequiresImport;
248 };
249 
250 /// @brief Base class for callback objects used by Sema::CorrectTypo to check
251 /// the validity of a potential typo correction.
252 class CorrectionCandidateCallback {
253 public:
254   static const unsigned InvalidDistance = TypoCorrection::InvalidDistance;
255 
256   explicit CorrectionCandidateCallback(IdentifierInfo *Typo = nullptr,
257                                        NestedNameSpecifier *TypoNNS = nullptr)
WantTypeSpecifiers(true)258       : WantTypeSpecifiers(true), WantExpressionKeywords(true),
259         WantCXXNamedCasts(true), WantFunctionLikeCasts(true),
260         WantRemainingKeywords(true), WantObjCSuper(false),
261         IsObjCIvarLookup(false), IsAddressOfOperand(false), Typo(Typo),
262         TypoNNS(TypoNNS) {}
263 
~CorrectionCandidateCallback()264   virtual ~CorrectionCandidateCallback() {}
265 
266   /// \brief Simple predicate used by the default RankCandidate to
267   /// determine whether to return an edit distance of 0 or InvalidDistance.
268   /// This can be overrided by validators that only need to determine if a
269   /// candidate is viable, without ranking potentially viable candidates.
270   /// Only ValidateCandidate or RankCandidate need to be overriden by a
271   /// callback wishing to check the viability of correction candidates.
272   /// The default predicate always returns true if the candidate is not a type
273   /// name or keyword, true for types if WantTypeSpecifiers is true, and true
274   /// for keywords if WantTypeSpecifiers, WantExpressionKeywords,
275   /// WantCXXNamedCasts, WantRemainingKeywords, or WantObjCSuper is true.
276   virtual bool ValidateCandidate(const TypoCorrection &candidate);
277 
278   /// \brief Method used by Sema::CorrectTypo to assign an "edit distance" rank
279   /// to a candidate (where a lower value represents a better candidate), or
280   /// returning InvalidDistance if the candidate is not at all viable. For
281   /// validation callbacks that only need to determine if a candidate is viable,
282   /// the default RankCandidate returns either 0 or InvalidDistance depending
283   /// whether ValidateCandidate returns true or false.
RankCandidate(const TypoCorrection & candidate)284   virtual unsigned RankCandidate(const TypoCorrection &candidate) {
285     return (!MatchesTypo(candidate) && ValidateCandidate(candidate))
286                ? 0
287                : InvalidDistance;
288   }
289 
setTypoName(IdentifierInfo * II)290   void setTypoName(IdentifierInfo *II) { Typo = II; }
setTypoNNS(NestedNameSpecifier * NNS)291   void setTypoNNS(NestedNameSpecifier *NNS) { TypoNNS = NNS; }
292 
293   // Flags for context-dependent keywords. WantFunctionLikeCasts is only
294   // used/meaningful when WantCXXNamedCasts is false.
295   // TODO: Expand these to apply to non-keywords or possibly remove them.
296   bool WantTypeSpecifiers;
297   bool WantExpressionKeywords;
298   bool WantCXXNamedCasts;
299   bool WantFunctionLikeCasts;
300   bool WantRemainingKeywords;
301   bool WantObjCSuper;
302   // Temporary hack for the one case where a CorrectTypoContext enum is used
303   // when looking up results.
304   bool IsObjCIvarLookup;
305   bool IsAddressOfOperand;
306 
307 protected:
MatchesTypo(const TypoCorrection & candidate)308   bool MatchesTypo(const TypoCorrection &candidate) {
309     return Typo && candidate.isResolved() && !candidate.requiresImport() &&
310            candidate.getCorrectionAsIdentifierInfo() == Typo &&
311            // FIXME: This probably does not return true when both
312            // NestedNameSpecifiers have the same textual representation.
313            candidate.getCorrectionSpecifier() == TypoNNS;
314   }
315 
316   IdentifierInfo *Typo;
317   NestedNameSpecifier *TypoNNS;
318 };
319 
320 /// @brief Simple template class for restricting typo correction candidates
321 /// to ones having a single Decl* of the given type.
322 template <class C>
323 class DeclFilterCCC : public CorrectionCandidateCallback {
324 public:
ValidateCandidate(const TypoCorrection & candidate)325   bool ValidateCandidate(const TypoCorrection &candidate) override {
326     return candidate.getCorrectionDeclAs<C>();
327   }
328 };
329 
330 // @brief Callback class to limit the allowed keywords and to only accept typo
331 // corrections that are keywords or whose decls refer to functions (or template
332 // functions) that accept the given number of arguments.
333 class FunctionCallFilterCCC : public CorrectionCandidateCallback {
334 public:
335   FunctionCallFilterCCC(Sema &SemaRef, unsigned NumArgs,
336                         bool HasExplicitTemplateArgs,
337                         MemberExpr *ME = nullptr);
338 
339   bool ValidateCandidate(const TypoCorrection &candidate) override;
340 
341  private:
342   unsigned NumArgs;
343   bool HasExplicitTemplateArgs;
344   DeclContext *CurContext;
345   MemberExpr *MemberFn;
346 };
347 
348 // @brief Callback class that effectively disabled typo correction
349 class NoTypoCorrectionCCC : public CorrectionCandidateCallback {
350 public:
NoTypoCorrectionCCC()351   NoTypoCorrectionCCC() {
352     WantTypeSpecifiers = false;
353     WantExpressionKeywords = false;
354     WantCXXNamedCasts = false;
355     WantFunctionLikeCasts = false;
356     WantRemainingKeywords = false;
357   }
358 
ValidateCandidate(const TypoCorrection & candidate)359   bool ValidateCandidate(const TypoCorrection &candidate) override {
360     return false;
361   }
362 };
363 
364 }
365 
366 #endif
367