//===- TypoCorrection.h - Class for typo correction results -----*- C++ -*-===//
 
//
 
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 
// See https://llvm.org/LICENSE.txt for license information.
 
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
//
 
//===----------------------------------------------------------------------===//
 
//
 
// This file defines the TypoCorrection class, which stores the results of
 
// Sema's typo correction (Sema::CorrectTypo).
 
//
 
//===----------------------------------------------------------------------===//
 
 
 
#ifndef LLVM_CLANG_SEMA_TYPOCORRECTION_H
 
#define LLVM_CLANG_SEMA_TYPOCORRECTION_H
 
 
 
#include "clang/AST/Decl.h"
 
#include "clang/AST/DeclarationName.h"
 
#include "clang/Basic/LLVM.h"
 
#include "clang/Basic/PartialDiagnostic.h"
 
#include "clang/Basic/SourceLocation.h"
 
#include "clang/Sema/DeclSpec.h"
 
#include "llvm/ADT/ArrayRef.h"
 
#include "llvm/ADT/SmallVector.h"
 
#include "llvm/Support/Casting.h"
 
#include <cstddef>
 
#include <limits>
 
#include <string>
 
#include <utility>
 
#include <vector>
 
 
 
namespace clang {
 
 
 
class DeclContext;
 
class IdentifierInfo;
 
class LangOptions;
 
class MemberExpr;
 
class NestedNameSpecifier;
 
class Sema;
 
 
 
/// Simple class containing the result of Sema::CorrectTypo
 
class TypoCorrection {
 
public:
 
  // "Distance" for unusable corrections
 
  static const unsigned InvalidDistance = std::numeric_limits<unsigned>::max();
 
 
 
  // The largest distance still considered valid (larger edit distances are
 
  // mapped to InvalidDistance by getEditDistance).
 
  static const unsigned MaximumDistance = 10000U;
 
 
 
  // Relative weightings of the "edit distance" components. The higher the
 
  // weight, the more of a penalty to fitness the component will give (higher
 
  // weights mean greater contribution to the total edit distance, with the
 
  // best correction candidates having the lowest edit distance).
 
  static const unsigned CharDistanceWeight = 100U;
 
  static const unsigned QualifierDistanceWeight = 110U;
 
  static const unsigned CallbackDistanceWeight = 150U;
 
 
 
  TypoCorrection(const DeclarationName &Name, NamedDecl *NameDecl,
 
                 NestedNameSpecifier *NNS = nullptr, unsigned CharDistance = 0,
 
                 unsigned QualifierDistance = 0)
 
      : CorrectionName(Name), CorrectionNameSpec(NNS),
 
        CharDistance(CharDistance), QualifierDistance(QualifierDistance) {
 
    if (NameDecl)
 
      CorrectionDecls.push_back(NameDecl);
 
  }
 
 
 
  TypoCorrection(NamedDecl *Name, NestedNameSpecifier *NNS = nullptr,
 
                 unsigned CharDistance = 0)
 
      : CorrectionName(Name->getDeclName()), CorrectionNameSpec(NNS),
 
        CharDistance(CharDistance) {
 
    if (Name)
 
      CorrectionDecls.push_back(Name);
 
  }
 
 
 
  TypoCorrection(DeclarationName Name, NestedNameSpecifier *NNS = nullptr,
 
                 unsigned CharDistance = 0)
 
      : CorrectionName(Name), CorrectionNameSpec(NNS),
 
        CharDistance(CharDistance) {}
 
 
 
  TypoCorrection() = default;
 
 
 
  /// Gets the DeclarationName of the typo correction
 
  DeclarationName getCorrection() const { return CorrectionName; }
 
 
 
  IdentifierInfo *getCorrectionAsIdentifierInfo() const {
 
    return CorrectionName.getAsIdentifierInfo();
 
  }
 
 
 
  /// Gets the NestedNameSpecifier needed to use the typo correction
 
  NestedNameSpecifier *getCorrectionSpecifier() const {
 
    return CorrectionNameSpec;
 
  }
 
 
 
  void setCorrectionSpecifier(NestedNameSpecifier *NNS) {
 
    CorrectionNameSpec = NNS;
 
    ForceSpecifierReplacement = (NNS != nullptr);
 
  }
 
 
 
  void WillReplaceSpecifier(bool ForceReplacement) {
 
    ForceSpecifierReplacement = ForceReplacement;
 
  }
 
 
 
  bool WillReplaceSpecifier() const {
 
    return ForceSpecifierReplacement;
 
  }
 
 
 
  void setQualifierDistance(unsigned ED) {
 
    QualifierDistance = ED;
 
  }
 
 
 
  void setCallbackDistance(unsigned ED) {
 
    CallbackDistance = ED;
 
  }
 
 
 
  // Convert the given weighted edit distance to a roughly equivalent number of
 
  // single-character edits (typically for comparison to the length of the
 
  // string being edited).
 
  static unsigned NormalizeEditDistance(unsigned ED) {
 
    if (ED > MaximumDistance)
 
      return InvalidDistance;
 
    return (ED + CharDistanceWeight / 2) / CharDistanceWeight;
 
  }
 
 
 
  /// Gets the "edit distance" of the typo correction from the typo.
 
  /// If Normalized is true, scale the distance down by the CharDistanceWeight
 
  /// to return the edit distance in terms of single-character edits.
 
  unsigned getEditDistance(bool Normalized = true) const {
 
    if (CharDistance > MaximumDistance || QualifierDistance > MaximumDistance ||
 
        CallbackDistance > MaximumDistance)
 
      return InvalidDistance;
 
    unsigned ED =
 
        CharDistance * CharDistanceWeight +
 
        QualifierDistance * QualifierDistanceWeight +
 
        CallbackDistance * CallbackDistanceWeight;
 
    if (ED > MaximumDistance)
 
      return InvalidDistance;
 
    // Half the CharDistanceWeight is added to ED to simulate rounding since
 
    // integer division truncates the value (i.e. round-to-nearest-int instead
 
    // of round-to-zero).
 
    return Normalized ? NormalizeEditDistance(ED) : ED;
 
  }
 
 
 
  /// Get the correction declaration found by name lookup (before we
 
  /// looked through using shadow declarations and the like).
 
  NamedDecl *getFoundDecl() const {
 
    return hasCorrectionDecl() ? *(CorrectionDecls.begin()) : nullptr;
 
  }
 
 
 
  /// Gets the pointer to the declaration of the typo correction
 
  NamedDecl *getCorrectionDecl() const {
 
    auto *D = getFoundDecl();
 
    return D ? D->getUnderlyingDecl() : nullptr;
 
  }
 
  template <class DeclClass>
 
  DeclClass *getCorrectionDeclAs() const {
 
    return dyn_cast_or_null<DeclClass>(getCorrectionDecl());
 
  }
 
 
 
  /// Clears the list of NamedDecls.
 
  void ClearCorrectionDecls() {
 
    CorrectionDecls.clear();
 
  }
 
 
 
  /// Clears the list of NamedDecls before adding the new one.
 
  void setCorrectionDecl(NamedDecl *CDecl) {
 
    CorrectionDecls.clear();
 
    addCorrectionDecl(CDecl);
 
  }
 
 
 
  /// Clears the list of NamedDecls and adds the given set.
 
  void setCorrectionDecls(ArrayRef<NamedDecl*> Decls) {
 
    CorrectionDecls.clear();
 
    CorrectionDecls.insert(CorrectionDecls.begin(), Decls.begin(), Decls.end());
 
  }
 
 
 
  /// Add the given NamedDecl to the list of NamedDecls that are the
 
  /// declarations associated with the DeclarationName of this TypoCorrection
 
  void addCorrectionDecl(NamedDecl *CDecl);
 
 
 
  std::string getAsString(const LangOptions &LO) const;
 
 
 
  std::string getQuoted(const LangOptions &LO) const {
 
    return "'" + getAsString(LO) + "'";
 
  }
 
 
 
  /// Returns whether this TypoCorrection has a non-empty DeclarationName
 
  explicit operator bool() const { return bool(CorrectionName); }
 
 
 
  /// Mark this TypoCorrection as being a keyword.
 
  /// Since addCorrectionDeclsand setCorrectionDecl don't allow NULL to be
 
  /// added to the list of the correction's NamedDecl pointers, NULL is added
 
  /// as the only element in the list to mark this TypoCorrection as a keyword.
 
  void makeKeyword() {
 
    CorrectionDecls.clear();
 
    CorrectionDecls.push_back(nullptr);
 
    ForceSpecifierReplacement = true;
 
  }
 
 
 
  // Check if this TypoCorrection is a keyword by checking if the first
 
  // item in CorrectionDecls is NULL.
 
  bool isKeyword() const {
 
    return !CorrectionDecls.empty() && CorrectionDecls.front() == nullptr;
 
  }
 
 
 
  // Check if this TypoCorrection is the given keyword.
 
  template<std::size_t StrLen>
 
  bool isKeyword(const char (&Str)[StrLen]) const {
 
    return isKeyword() && getCorrectionAsIdentifierInfo()->isStr(Str);
 
  }
 
 
 
  // Returns true if the correction either is a keyword or has a known decl.
 
  bool isResolved() const { return !CorrectionDecls.empty(); }
 
 
 
  bool isOverloaded() const {
 
    return CorrectionDecls.size() > 1;
 
  }
 
 
 
  void setCorrectionRange(CXXScopeSpec *SS,
 
                          const DeclarationNameInfo &TypoName) {
 
    CorrectionRange = TypoName.getSourceRange();
 
    if (ForceSpecifierReplacement && SS && !SS->isEmpty())
 
      CorrectionRange.setBegin(SS->getBeginLoc());
 
  }
 
 
 
  SourceRange getCorrectionRange() const {
 
    return CorrectionRange;
 
  }
 
 
 
  using decl_iterator = SmallVectorImpl<NamedDecl *>::iterator;
 
 
 
  decl_iterator begin() {
 
    return isKeyword() ? CorrectionDecls.end() : CorrectionDecls.begin();
 
  }
 
 
 
  decl_iterator end() { return CorrectionDecls.end(); }
 
 
 
  using const_decl_iterator = SmallVectorImpl<NamedDecl *>::const_iterator;
 
 
 
  const_decl_iterator begin() const {
 
    return isKeyword() ? CorrectionDecls.end() : CorrectionDecls.begin();
 
  }
 
 
 
  const_decl_iterator end() const { return CorrectionDecls.end(); }
 
 
 
  /// Returns whether this typo correction is correcting to a
 
  /// declaration that was declared in a module that has not been imported.
 
  bool requiresImport() const { return RequiresImport; }
 
  void setRequiresImport(bool Req) { RequiresImport = Req; }
 
 
 
  /// Extra diagnostics are printed after the first diagnostic for the typo.
 
  /// This can be used to attach external notes to the diag.
 
  void addExtraDiagnostic(PartialDiagnostic PD) {
 
    ExtraDiagnostics.push_back(std::move(PD));
 
  }
 
  ArrayRef<PartialDiagnostic> getExtraDiagnostics() const {
 
    return ExtraDiagnostics;
 
  }
 
 
 
private:
 
  bool hasCorrectionDecl() const {
 
    return (!isKeyword() && !CorrectionDecls.empty());
 
  }
 
 
 
  // Results.
 
  DeclarationName CorrectionName;
 
  NestedNameSpecifier *CorrectionNameSpec = nullptr;
 
  SmallVector<NamedDecl *, 1> CorrectionDecls;
 
  unsigned CharDistance = 0;
 
  unsigned QualifierDistance = 0;
 
  unsigned CallbackDistance = 0;
 
  SourceRange CorrectionRange;
 
  bool ForceSpecifierReplacement = false;
 
  bool RequiresImport = false;
 
 
 
  std::vector<PartialDiagnostic> ExtraDiagnostics;
 
};
 
 
 
/// Base class for callback objects used by Sema::CorrectTypo to check
 
/// the validity of a potential typo correction.
 
class CorrectionCandidateCallback {
 
public:
 
  static const unsigned InvalidDistance = TypoCorrection::InvalidDistance;
 
 
 
  explicit CorrectionCandidateCallback(IdentifierInfo *Typo = nullptr,
 
                                       NestedNameSpecifier *TypoNNS = nullptr)
 
      : Typo(Typo), TypoNNS(TypoNNS) {}
 
 
 
  virtual ~CorrectionCandidateCallback() = default;
 
 
 
  /// Simple predicate used by the default RankCandidate to
 
  /// determine whether to return an edit distance of 0 or InvalidDistance.
 
  /// This can be overridden by validators that only need to determine if a
 
  /// candidate is viable, without ranking potentially viable candidates.
 
  /// Only ValidateCandidate or RankCandidate need to be overridden by a
 
  /// callback wishing to check the viability of correction candidates.
 
  /// The default predicate always returns true if the candidate is not a type
 
  /// name or keyword, true for types if WantTypeSpecifiers is true, and true
 
  /// for keywords if WantTypeSpecifiers, WantExpressionKeywords,
 
  /// WantCXXNamedCasts, WantRemainingKeywords, or WantObjCSuper is true.
 
  virtual bool ValidateCandidate(const TypoCorrection &candidate);
 
 
 
  /// Method used by Sema::CorrectTypo to assign an "edit distance" rank
 
  /// to a candidate (where a lower value represents a better candidate), or
 
  /// returning InvalidDistance if the candidate is not at all viable. For
 
  /// validation callbacks that only need to determine if a candidate is viable,
 
  /// the default RankCandidate returns either 0 or InvalidDistance depending
 
  /// whether ValidateCandidate returns true or false.
 
  virtual unsigned RankCandidate(const TypoCorrection &candidate) {
 
    return (!MatchesTypo(candidate) && ValidateCandidate(candidate))
 
               ? 0
 
               : InvalidDistance;
 
  }
 
 
 
  /// Clone this CorrectionCandidateCallback. CorrectionCandidateCallbacks are
 
  /// initially stack-allocated. However in case where delayed typo-correction
 
  /// is done we need to move the callback to storage with a longer lifetime.
 
  /// Every class deriving from CorrectionCandidateCallback must implement
 
  /// this method.
 
  virtual std::unique_ptr<CorrectionCandidateCallback> clone() = 0;
 
 
 
  void setTypoName(IdentifierInfo *II) { Typo = II; }
 
  void setTypoNNS(NestedNameSpecifier *NNS) { TypoNNS = NNS; }
 
 
 
  // Flags for context-dependent keywords. WantFunctionLikeCasts is only
 
  // used/meaningful when WantCXXNamedCasts is false.
 
  // TODO: Expand these to apply to non-keywords or possibly remove them.
 
  bool WantTypeSpecifiers = true;
 
  bool WantExpressionKeywords = true;
 
  bool WantCXXNamedCasts = true;
 
  bool WantFunctionLikeCasts = true;
 
  bool WantRemainingKeywords = true;
 
  bool WantObjCSuper = false;
 
  // Temporary hack for the one case where a CorrectTypoContext enum is used
 
  // when looking up results.
 
  bool IsObjCIvarLookup = false;
 
  bool IsAddressOfOperand = false;
 
 
 
protected:
 
  bool MatchesTypo(const TypoCorrection &candidate) {
 
    return Typo && candidate.isResolved() && !candidate.requiresImport() &&
 
           candidate.getCorrectionAsIdentifierInfo() == Typo &&
 
           // FIXME: This probably does not return true when both
 
           // NestedNameSpecifiers have the same textual representation.
 
           candidate.getCorrectionSpecifier() == TypoNNS;
 
  }
 
 
 
  IdentifierInfo *Typo;
 
  NestedNameSpecifier *TypoNNS;
 
};
 
 
 
class DefaultFilterCCC final : public CorrectionCandidateCallback {
 
public:
 
  explicit DefaultFilterCCC(IdentifierInfo *Typo = nullptr,
 
                            NestedNameSpecifier *TypoNNS = nullptr)
 
      : CorrectionCandidateCallback(Typo, TypoNNS) {}
 
 
 
  std::unique_ptr<CorrectionCandidateCallback> clone() override {
 
    return std::make_unique<DefaultFilterCCC>(*this);
 
  }
 
};
 
 
 
/// Simple template class for restricting typo correction candidates
 
/// to ones having a single Decl* of the given type.
 
template <class C>
 
class DeclFilterCCC final : public CorrectionCandidateCallback {
 
public:
 
  bool ValidateCandidate(const TypoCorrection &candidate) override {
 
    return candidate.getCorrectionDeclAs<C>();
 
  }
 
  std::unique_ptr<CorrectionCandidateCallback> clone() override {
 
    return std::make_unique<DeclFilterCCC>(*this);
 
  }
 
};
 
 
 
// Callback class to limit the allowed keywords and to only accept typo
 
// corrections that are keywords or whose decls refer to functions (or template
 
// functions) that accept the given number of arguments.
 
class FunctionCallFilterCCC : public CorrectionCandidateCallback {
 
public:
 
  FunctionCallFilterCCC(Sema &SemaRef, unsigned NumArgs,
 
                        bool HasExplicitTemplateArgs,
 
                        MemberExpr *ME = nullptr);
 
 
 
  bool ValidateCandidate(const TypoCorrection &candidate) override;
 
  std::unique_ptr<CorrectionCandidateCallback> clone() override {
 
    return std::make_unique<FunctionCallFilterCCC>(*this);
 
  }
 
 
 
private:
 
  unsigned NumArgs;
 
  bool HasExplicitTemplateArgs;
 
  DeclContext *CurContext;
 
  MemberExpr *MemberFn;
 
};
 
 
 
// Callback class that effectively disabled typo correction
 
class NoTypoCorrectionCCC final : public CorrectionCandidateCallback {
 
public:
 
  NoTypoCorrectionCCC() {
 
    WantTypeSpecifiers = false;
 
    WantExpressionKeywords = false;
 
    WantCXXNamedCasts = false;
 
    WantFunctionLikeCasts = false;
 
    WantRemainingKeywords = false;
 
  }
 
 
 
  bool ValidateCandidate(const TypoCorrection &candidate) override {
 
    return false;
 
  }
 
  std::unique_ptr<CorrectionCandidateCallback> clone() override {
 
    return std::make_unique<NoTypoCorrectionCCC>(*this);
 
  }
 
};
 
 
 
} // namespace clang
 
 
 
#endif // LLVM_CLANG_SEMA_TYPOCORRECTION_H