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 |