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