https://github.com/a-tarasyuk created 
https://github.com/llvm/llvm-project/pull/140629

This patch enhances Clang's diagnosis for unknown attributes by providing typo 
correction suggestions for known attributes.

```cpp
[[gmu::deprected]] // expected-warning {{unknown attribute 'gmu::deprected' 
ignored; did you mean 'gnu::deprecated'?}}
int f1(void) {
  return 0;
}

[[deprected]] // expected-warning {{unknown attribute 'deprected' ignored; did 
you mean 'deprecated'?}}
int f2(void) {
  return 0;
}
```

>From 5b8f0635fdddc87ea326a3b1b47191ab9bf23c0f Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.taras...@outlook.com>
Date: Tue, 20 May 2025 00:38:43 +0300
Subject: [PATCH] [Clang] add typo correction for unknown attribute names

---
 clang/docs/ReleaseNotes.rst                   |   2 +
 .../include/clang/Basic/AttributeCommonInfo.h |   5 +
 clang/include/clang/Basic/Attributes.h        |   4 +
 clang/include/clang/Basic/CMakeLists.txt      |   6 +
 .../clang/Basic/DiagnosticCommonKinds.td      |   2 +
 .../clang/Basic/SimpleTypoCorrection.h        |  35 ++++
 clang/include/clang/Sema/Sema.h               |   2 +
 clang/lib/AST/CommentSema.cpp                 |  97 +++--------
 clang/lib/Basic/Attributes.cpp                | 153 ++++++++++++------
 clang/lib/Basic/CMakeLists.txt                |   1 +
 clang/lib/Basic/SimpleTypoCorrection.cpp      |  52 ++++++
 clang/lib/Sema/SemaDeclAttr.cpp               |  18 ++-
 clang/lib/Sema/SemaType.cpp                   |   4 +-
 clang/test/Parser/cxx0x-attributes.cpp        |   2 +-
 clang/test/Sema/attr-c2x.c                    |   4 +-
 clang/test/Sema/unknown-attributes.c          |  16 +-
 clang/test/SemaCXX/cxx11-gnu-attrs.cpp        |   2 +-
 clang/utils/TableGen/ClangAttrEmitter.cpp     |  27 ++++
 clang/utils/TableGen/TableGen.cpp             |   6 +
 clang/utils/TableGen/TableGenBackends.h       |   2 +
 20 files changed, 307 insertions(+), 133 deletions(-)
 create mode 100644 clang/include/clang/Basic/SimpleTypoCorrection.h
 create mode 100644 clang/lib/Basic/SimpleTypoCorrection.cpp

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index ac9baf229b489..deee00128c1fa 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -561,6 +561,8 @@ Improvements to Clang's diagnostics
 - Fixed a crash when checking a ``__thread``-specified variable declaration
   with a dependent type in C++. (#GH140509)
 
+- Clang now suggests corrections for unknown attribute names.
+
 Improvements to Clang's time-trace
 ----------------------------------
 
diff --git a/clang/include/clang/Basic/AttributeCommonInfo.h 
b/clang/include/clang/Basic/AttributeCommonInfo.h
index 6db7b53317e7d..b4b8345b4ed40 100644
--- a/clang/include/clang/Basic/AttributeCommonInfo.h
+++ b/clang/include/clang/Basic/AttributeCommonInfo.h
@@ -21,6 +21,8 @@ namespace clang {
 
 class ASTRecordWriter;
 class IdentifierInfo;
+class LangOptions;
+class TargetInfo;
 
 class AttributeCommonInfo {
 public:
@@ -196,6 +198,9 @@ class AttributeCommonInfo {
   /// with surrounding underscores removed as appropriate (e.g.
   /// __gnu__::__attr__ will be normalized to gnu::attr).
   std::string getNormalizedFullName() const;
+  std::optional<std::string>
+  getCorrectedFullName(const TargetInfo &Target,
+                       const LangOptions &LangOpts) const;
   SourceRange getNormalizedRange() const;
 
   bool isDeclspecAttribute() const { return SyntaxUsed == AS_Declspec; }
diff --git a/clang/include/clang/Basic/Attributes.h 
b/clang/include/clang/Basic/Attributes.h
index 99bb668fe32d0..9cf6fb3d89019 100644
--- a/clang/include/clang/Basic/Attributes.h
+++ b/clang/include/clang/Basic/Attributes.h
@@ -19,6 +19,10 @@ class TargetInfo;
 
 /// Return the version number associated with the attribute if we
 /// recognize and implement the attribute specified by the given information.
+int hasAttribute(AttributeCommonInfo::Syntax Syntax, llvm::StringRef ScopeName,
+                 llvm::StringRef AttrName, const TargetInfo &Target,
+                 const LangOptions &LangOpts, bool CheckPlugins);
+
 int hasAttribute(AttributeCommonInfo::Syntax Syntax,
                  const IdentifierInfo *Scope, const IdentifierInfo *Attr,
                  const TargetInfo &Target, const LangOptions &LangOpts);
diff --git a/clang/include/clang/Basic/CMakeLists.txt 
b/clang/include/clang/Basic/CMakeLists.txt
index 265ea1fc06494..1873878e2e46b 100644
--- a/clang/include/clang/Basic/CMakeLists.txt
+++ b/clang/include/clang/Basic/CMakeLists.txt
@@ -79,6 +79,12 @@ clang_tablegen(CXX11AttributeInfo.inc 
-gen-cxx11-attribute-info
   TARGET CXX11AttributeInfo
   )
 
+  clang_tablegen(AttributeSpellingList.inc -gen-attribute-spelling-list
+  -I ${CMAKE_CURRENT_SOURCE_DIR}/../../
+  SOURCE Attr.td
+  TARGET AttributeSpellingList
+  )
+
 clang_tablegen(Builtins.inc -gen-clang-builtins
   SOURCE Builtins.td
   TARGET ClangBuiltins)
diff --git a/clang/include/clang/Basic/DiagnosticCommonKinds.td 
b/clang/include/clang/Basic/DiagnosticCommonKinds.td
index e4d94fefbbf3d..0bd8a423c393e 100644
--- a/clang/include/clang/Basic/DiagnosticCommonKinds.td
+++ b/clang/include/clang/Basic/DiagnosticCommonKinds.td
@@ -181,6 +181,8 @@ def err_opencl_unknown_type_specifier : Error<
 
 def warn_unknown_attribute_ignored : Warning<
   "unknown attribute %0 ignored">, InGroup<UnknownAttributes>;
+def warn_unknown_attribute_ignored_suggestion : Warning<
+  "unknown attribute %0 ignored; did you mean '%1'?">, 
InGroup<UnknownAttributes>;
 def warn_attribute_ignored : Warning<"%0 attribute ignored">,
   InGroup<IgnoredAttributes>;
 def err_keyword_not_supported_on_target : Error<
diff --git a/clang/include/clang/Basic/SimpleTypoCorrection.h 
b/clang/include/clang/Basic/SimpleTypoCorrection.h
new file mode 100644
index 0000000000000..4cd104f79aebe
--- /dev/null
+++ b/clang/include/clang/Basic/SimpleTypoCorrection.h
@@ -0,0 +1,35 @@
+#ifndef LLVM_CLANG_BASIC_SIMPLETYPOCORRECTION_H
+#define LLVM_CLANG_BASIC_SIMPLETYPOCORRECTION_H
+
+#include "clang/Basic/LLVM.h"
+#include "llvm/ADT/StringRef.h"
+
+namespace clang {
+
+class IdentifierInfo;
+
+class SimpleTypoCorrection {
+  StringRef BestCandidate;
+  StringRef Typo;
+
+  const unsigned MaxEditDistance;
+  unsigned BestEditDistance;
+  unsigned BestIndex;
+  unsigned NextIndex;
+
+public:
+  explicit SimpleTypoCorrection(StringRef Typo)
+      : BestCandidate(), Typo(Typo), MaxEditDistance((Typo.size() + 2) / 3),
+        BestEditDistance(MaxEditDistance + 1), BestIndex(0), NextIndex(0) {}
+
+  void add(const StringRef Candidate);
+  void add(const char *Candidate);
+  void add(const IdentifierInfo *Candidate);
+
+  std::optional<StringRef> getCorrection() const;
+  bool hasCorrection() const;
+  unsigned getCorrectionIndex() const;
+};
+} // namespace clang
+
+#endif // LLVM_CLANG_BASIC_SIMPLETYPOCORRECTION_H
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 5ec67087aeea4..d7a3a81065d33 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -5033,6 +5033,8 @@ class Sema final : public SemaBase {
   /// which might be lying around on it.
   void checkUnusedDeclAttributes(Declarator &D);
 
+  void DiagnoseUnknownAttribute(const ParsedAttr &AL);
+
   /// DeclClonePragmaWeak - clone existing decl (maybe definition),
   /// \#pragma weak needs a non-definition decl and source may not have one.
   NamedDecl *DeclClonePragmaWeak(NamedDecl *ND, const IdentifierInfo *II,
diff --git a/clang/lib/AST/CommentSema.cpp b/clang/lib/AST/CommentSema.cpp
index bd2206bb8a3bc..fb745fc560d2f 100644
--- a/clang/lib/AST/CommentSema.cpp
+++ b/clang/lib/AST/CommentSema.cpp
@@ -13,6 +13,7 @@
 #include "clang/AST/DeclTemplate.h"
 #include "clang/Basic/DiagnosticComment.h"
 #include "clang/Basic/LLVM.h"
+#include "clang/Basic/SimpleTypoCorrection.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Lex/Preprocessor.h"
 #include "llvm/ADT/StringSwitch.h"
@@ -975,69 +976,22 @@ unsigned Sema::resolveParmVarReference(StringRef Name,
   return ParamCommandComment::InvalidParamIndex;
 }
 
-namespace {
-class SimpleTypoCorrector {
-  const NamedDecl *BestDecl;
-
-  StringRef Typo;
-  const unsigned MaxEditDistance;
-
-  unsigned BestEditDistance;
-  unsigned BestIndex;
-  unsigned NextIndex;
-
-public:
-  explicit SimpleTypoCorrector(StringRef Typo)
-      : BestDecl(nullptr), Typo(Typo), MaxEditDistance((Typo.size() + 2) / 3),
-        BestEditDistance(MaxEditDistance + 1), BestIndex(0), NextIndex(0) {}
-
-  void addDecl(const NamedDecl *ND);
-
-  const NamedDecl *getBestDecl() const {
-    if (BestEditDistance > MaxEditDistance)
-      return nullptr;
-
-    return BestDecl;
-  }
+unsigned
+Sema::correctTypoInParmVarReference(StringRef Typo,
+                                    ArrayRef<const ParmVarDecl *> ParamVars) {
+  SimpleTypoCorrection STC(Typo);
+  for (unsigned i = 0, e = ParamVars.size(); i != e; ++i) {
+    const ParmVarDecl *Param = ParamVars[i];
+    if (!Param)
+      continue;
 
-  unsigned getBestDeclIndex() const {
-    assert(getBestDecl());
-    return BestIndex;
+    STC.add(Param->getIdentifier());
   }
-};
-
-void SimpleTypoCorrector::addDecl(const NamedDecl *ND) {
-  unsigned CurrIndex = NextIndex++;
-
-  const IdentifierInfo *II = ND->getIdentifier();
-  if (!II)
-    return;
 
-  StringRef Name = II->getName();
-  unsigned MinPossibleEditDistance = abs((int)Name.size() - (int)Typo.size());
-  if (MinPossibleEditDistance > 0 &&
-      Typo.size() / MinPossibleEditDistance < 3)
-    return;
+  if (STC.hasCorrection())
+    return STC.getCorrectionIndex();
 
-  unsigned EditDistance = Typo.edit_distance(Name, true, MaxEditDistance);
-  if (EditDistance < BestEditDistance) {
-    BestEditDistance = EditDistance;
-    BestDecl = ND;
-    BestIndex = CurrIndex;
-  }
-}
-} // end anonymous namespace
-
-unsigned Sema::correctTypoInParmVarReference(
-                                    StringRef Typo,
-                                    ArrayRef<const ParmVarDecl *> ParamVars) {
-  SimpleTypoCorrector Corrector(Typo);
-  for (unsigned i = 0, e = ParamVars.size(); i != e; ++i)
-    Corrector.addDecl(ParamVars[i]);
-  if (Corrector.getBestDecl())
-    return Corrector.getBestDeclIndex();
-  else
-    return ParamCommandComment::InvalidParamIndex;
+  return ParamCommandComment::InvalidParamIndex;
 }
 
 namespace {
@@ -1079,16 +1033,18 @@ bool Sema::resolveTParamReference(
 
 namespace {
 void CorrectTypoInTParamReferenceHelper(
-                            const TemplateParameterList *TemplateParameters,
-                            SimpleTypoCorrector &Corrector) {
+    const TemplateParameterList *TemplateParameters,
+    SimpleTypoCorrection &STC) {
   for (unsigned i = 0, e = TemplateParameters->size(); i != e; ++i) {
     const NamedDecl *Param = TemplateParameters->getParam(i);
-    Corrector.addDecl(Param);
+    if (!Param)
+      continue;
+
+    STC.add(Param->getIdentifier());
 
     if (const TemplateTemplateParmDecl *TTP =
             dyn_cast<TemplateTemplateParmDecl>(Param))
-      CorrectTypoInTParamReferenceHelper(TTP->getTemplateParameters(),
-                                         Corrector);
+      CorrectTypoInTParamReferenceHelper(TTP->getTemplateParameters(), STC);
   }
 }
 } // end anonymous namespace
@@ -1096,13 +1052,12 @@ void CorrectTypoInTParamReferenceHelper(
 StringRef Sema::correctTypoInTParamReference(
                             StringRef Typo,
                             const TemplateParameterList *TemplateParameters) {
-  SimpleTypoCorrector Corrector(Typo);
-  CorrectTypoInTParamReferenceHelper(TemplateParameters, Corrector);
-  if (const NamedDecl *ND = Corrector.getBestDecl()) {
-    const IdentifierInfo *II = ND->getIdentifier();
-    assert(II && "SimpleTypoCorrector should not return this decl");
-    return II->getName();
-  }
+  SimpleTypoCorrection STC(Typo);
+  CorrectTypoInTParamReferenceHelper(TemplateParameters, STC);
+
+  if (auto CorrectedTParamReference = STC.getCorrection())
+    return *CorrectedTParamReference;
+
   return StringRef();
 }
 
diff --git a/clang/lib/Basic/Attributes.cpp b/clang/lib/Basic/Attributes.cpp
index 8ff5cc54ccc93..0c0a816c78039 100644
--- a/clang/lib/Basic/Attributes.cpp
+++ b/clang/lib/Basic/Attributes.cpp
@@ -15,6 +15,7 @@
 #include "clang/Basic/IdentifierTable.h"
 #include "clang/Basic/LangOptions.h"
 #include "clang/Basic/ParsedAttrInfo.h"
+#include "clang/Basic/SimpleTypoCorrection.h"
 #include "clang/Basic/TargetInfo.h"
 
 #include "llvm/ADT/StringMap.h"
@@ -22,30 +23,37 @@
 
 using namespace clang;
 
-static int hasAttributeImpl(AttributeCommonInfo::Syntax Syntax, StringRef Name,
-                            StringRef ScopeName, const TargetInfo &Target,
-                            const LangOptions &LangOpts) {
+static StringRef canonicalizeScopeName(StringRef Name) {
+  // Normalize the scope name, but only for gnu and clang attributes.
+  if (Name == "__gnu__")
+    return "gnu";
 
-#include "clang/Basic/AttrHasAttributeImpl.inc"
+  if (Name == "_Clang")
+    return "clang";
 
-  return 0;
+  return Name;
 }
 
-int clang::hasAttribute(AttributeCommonInfo::Syntax Syntax,
-                        const IdentifierInfo *Scope, const IdentifierInfo 
*Attr,
-                        const TargetInfo &Target, const LangOptions &LangOpts,
-                        bool CheckPlugins) {
-  StringRef Name = Attr->getName();
+static StringRef canonicalizeAttrName(StringRef Name) {
   // Normalize the attribute name, __foo__ becomes foo.
   if (Name.size() >= 4 && Name.starts_with("__") && Name.ends_with("__"))
-    Name = Name.substr(2, Name.size() - 4);
+    return Name.substr(2, Name.size() - 4);
 
-  // Normalize the scope name, but only for gnu and clang attributes.
-  StringRef ScopeName = Scope ? Scope->getName() : "";
-  if (ScopeName == "__gnu__")
-    ScopeName = "gnu";
-  else if (ScopeName == "_Clang")
-    ScopeName = "clang";
+  return Name;
+}
+
+static int hasAttributeImpl(AttributeCommonInfo::Syntax Syntax, StringRef Name,
+                            StringRef ScopeName, const TargetInfo &Target,
+                            const LangOptions &LangOpts) {
+#include "clang/Basic/AttrHasAttributeImpl.inc"
+  return 0;
+}
+
+int clang::hasAttribute(AttributeCommonInfo::Syntax Syntax, StringRef 
ScopeName,
+                        StringRef Name, const TargetInfo &Target,
+                        const LangOptions &LangOpts, bool CheckPlugins) {
+  ScopeName = canonicalizeScopeName(ScopeName);
+  Name = canonicalizeAttrName(Name);
 
   // As a special case, look for the omp::sequence and omp::directive
   // attributes. We support those, but not through the typical attribute
@@ -72,6 +80,14 @@ int clang::hasAttribute(AttributeCommonInfo::Syntax Syntax,
   return 0;
 }
 
+int clang::hasAttribute(AttributeCommonInfo::Syntax Syntax,
+                        const IdentifierInfo *Scope, const IdentifierInfo 
*Attr,
+                        const TargetInfo &Target, const LangOptions &LangOpts,
+                        bool CheckPlugins) {
+  return hasAttribute(Syntax, Scope ? Scope->getName() : "", Attr->getName(),
+                      Target, LangOpts, CheckPlugins);
+}
+
 int clang::hasAttribute(AttributeCommonInfo::Syntax Syntax,
                         const IdentifierInfo *Scope, const IdentifierInfo 
*Attr,
                         const TargetInfo &Target, const LangOptions &LangOpts) 
{
@@ -90,25 +106,25 @@ const char 
*attr::getSubjectMatchRuleSpelling(attr::SubjectMatchRule Rule) {
 }
 
 static StringRef
-normalizeAttrScopeName(const IdentifierInfo *Scope,
+normalizeAttrScopeName(StringRef ScopeName,
                        AttributeCommonInfo::Syntax SyntaxUsed) {
-  if (!Scope)
-    return "";
-
-  // Normalize the "__gnu__" scope name to be "gnu" and the "_Clang" scope name
-  // to be "clang".
-  StringRef ScopeName = Scope->getName();
   if (SyntaxUsed == AttributeCommonInfo::AS_CXX11 ||
-      SyntaxUsed == AttributeCommonInfo::AS_C23) {
-    if (ScopeName == "__gnu__")
-      ScopeName = "gnu";
-    else if (ScopeName == "_Clang")
-      ScopeName = "clang";
-  }
+      SyntaxUsed == AttributeCommonInfo::AS_C23)
+    return canonicalizeScopeName(ScopeName);
+
   return ScopeName;
 }
 
-static StringRef normalizeAttrName(const IdentifierInfo *Name,
+static StringRef
+normalizeAttrScopeName(const IdentifierInfo *ScopeName,
+                       AttributeCommonInfo::Syntax SyntaxUsed) {
+  if (ScopeName)
+    return normalizeAttrScopeName(ScopeName->getName(), SyntaxUsed);
+
+  return "";
+}
+
+static StringRef normalizeAttrName(StringRef AttrName,
                                    StringRef NormalizedScopeName,
                                    AttributeCommonInfo::Syntax SyntaxUsed) {
   // Normalize the attribute name, __foo__ becomes foo. This is only allowable
@@ -119,10 +135,9 @@ static StringRef normalizeAttrName(const IdentifierInfo 
*Name,
         SyntaxUsed == AttributeCommonInfo::AS_C23) &&
        (NormalizedScopeName.empty() || NormalizedScopeName == "gnu" ||
         NormalizedScopeName == "clang"));
-  StringRef AttrName = Name->getName();
-  if (ShouldNormalize && AttrName.size() >= 4 && AttrName.starts_with("__") &&
-      AttrName.ends_with("__"))
-    AttrName = AttrName.slice(2, AttrName.size() - 2);
+
+  if (ShouldNormalize)
+    return canonicalizeAttrName(AttrName);
 
   return AttrName;
 }
@@ -137,16 +152,11 @@ bool AttributeCommonInfo::isClangScope() const {
 
 #include "clang/Sema/AttrParsedAttrKinds.inc"
 
-static SmallString<64> normalizeName(const IdentifierInfo *Name,
-                                     const IdentifierInfo *Scope,
+static SmallString<64> normalizeName(StringRef AttrName, StringRef ScopeName,
                                      AttributeCommonInfo::Syntax SyntaxUsed) {
-  StringRef ScopeName = normalizeAttrScopeName(Scope, SyntaxUsed);
-  StringRef AttrName = normalizeAttrName(Name, ScopeName, SyntaxUsed);
-
-  std::string StrAttrName = AttrName.str();
-  if (SyntaxUsed == AttributeCommonInfo::AS_HLSLAnnotation)
-    StrAttrName = AttrName.lower();
-
+  std::string StrAttrName = SyntaxUsed == 
AttributeCommonInfo::AS_HLSLAnnotation
+                                ? AttrName.lower()
+                                : AttrName.str();
   SmallString<64> FullName = ScopeName;
   if (!ScopeName.empty()) {
     assert(SyntaxUsed == AttributeCommonInfo::AS_CXX11 ||
@@ -154,10 +164,18 @@ static SmallString<64> normalizeName(const IdentifierInfo 
*Name,
     FullName += "::";
   }
   FullName += StrAttrName;
-
   return FullName;
 }
 
+static SmallString<64> normalizeName(const IdentifierInfo *Name,
+                                     const IdentifierInfo *Scope,
+                                     AttributeCommonInfo::Syntax SyntaxUsed) {
+  StringRef ScopeName = normalizeAttrScopeName(Scope, SyntaxUsed);
+  StringRef AttrName =
+      normalizeAttrName(Name->getName(), ScopeName, SyntaxUsed);
+  return normalizeName(AttrName, ScopeName, SyntaxUsed);
+}
+
 AttributeCommonInfo::Kind
 AttributeCommonInfo::getParsedKind(const IdentifierInfo *Name,
                                    const IdentifierInfo *ScopeName,
@@ -167,8 +185,8 @@ AttributeCommonInfo::getParsedKind(const IdentifierInfo 
*Name,
 
 AttributeCommonInfo::AttrArgsInfo
 AttributeCommonInfo::getCXX11AttrArgsInfo(const IdentifierInfo *Name) {
-  StringRef AttrName =
-      normalizeAttrName(Name, /*NormalizedScopeName*/ "", Syntax::AS_CXX11);
+  StringRef AttrName = normalizeAttrName(
+      Name->getName(), /*NormalizedScopeName*/ "", Syntax::AS_CXX11);
 #define CXX11_ATTR_ARGS_INFO
   return llvm::StringSwitch<AttributeCommonInfo::AttrArgsInfo>(AttrName)
 #include "clang/Basic/CXX11AttributeInfo.inc"
@@ -203,10 +221,47 @@ unsigned 
AttributeCommonInfo::calculateAttributeSpellingListIndex() const {
   // attribute spell list index matching code.
   auto Syntax = static_cast<AttributeCommonInfo::Syntax>(getSyntax());
   StringRef ScopeName = normalizeAttrScopeName(getScopeName(), Syntax);
-  StringRef Name = normalizeAttrName(getAttrName(), ScopeName, Syntax);
-
+  StringRef Name =
+      normalizeAttrName(getAttrName()->getName(), ScopeName, Syntax);
   AttributeCommonInfo::Scope ComputedScope =
       getScopeFromNormalizedScopeName(ScopeName);
 
 #include "clang/Sema/AttrSpellingListIndex.inc"
 }
+
+#include "clang/Basic/AttributeSpellingList.inc"
+
+std::optional<std::string>
+AttributeCommonInfo::getCorrectedFullName(const TargetInfo &Target,
+                                          const LangOptions &LangOpts) const {
+  StringRef ScopeName = normalizeAttrScopeName(getScopeName(), getSyntax());
+  if (ScopeName.size() > 0 &&
+      llvm::none_of(AttrScopeSpellingList,
+                    [&](const char *S) { return S == ScopeName; })) {
+    SimpleTypoCorrection STC(ScopeName);
+    for (const auto &Scope : AttrScopeSpellingList)
+      STC.add(Scope);
+
+    if (auto CorrectedScopeName = STC.getCorrection())
+      ScopeName = *CorrectedScopeName;
+  }
+
+  StringRef AttrName =
+      normalizeAttrName(getAttrName()->getName(), ScopeName, getSyntax());
+  if (llvm::none_of(AttrSpellingList,
+                    [&](const char *A) { return A == AttrName; })) {
+    SimpleTypoCorrection STC(AttrName);
+    for (const auto &Attr : AttrSpellingList)
+      STC.add(Attr);
+
+    if (auto CorrectedAttrName = STC.getCorrection())
+      AttrName = *CorrectedAttrName;
+  }
+
+  if (hasAttribute(getSyntax(), ScopeName, AttrName, Target, LangOpts,
+                   /*CheckPlugins=*/true))
+    return static_cast<std::string>(
+        normalizeName(AttrName, ScopeName, getSyntax()));
+
+  return std::nullopt;
+}
diff --git a/clang/lib/Basic/CMakeLists.txt b/clang/lib/Basic/CMakeLists.txt
index 0eacf79f5d478..f8a31c890ac4d 100644
--- a/clang/lib/Basic/CMakeLists.txt
+++ b/clang/lib/Basic/CMakeLists.txt
@@ -86,6 +86,7 @@ add_clang_library(clangBasic
   SanitizerSpecialCaseList.cpp
   Sanitizers.cpp
   Sarif.cpp
+  SimpleTypoCorrection.cpp
   SourceLocation.cpp
   SourceManager.cpp
   SourceMgrAdapter.cpp
diff --git a/clang/lib/Basic/SimpleTypoCorrection.cpp 
b/clang/lib/Basic/SimpleTypoCorrection.cpp
new file mode 100644
index 0000000000000..c98b89d9f080d
--- /dev/null
+++ b/clang/lib/Basic/SimpleTypoCorrection.cpp
@@ -0,0 +1,52 @@
+#include "clang/Basic/SimpleTypoCorrection.h"
+#include "clang/Basic/IdentifierTable.h"
+#include "clang/Basic/LLVM.h"
+#include "llvm/ADT/StringRef.h"
+
+using namespace clang;
+
+void SimpleTypoCorrection::add(const StringRef Candidate) {
+  if (Candidate.empty())
+    return;
+
+  unsigned MinPossibleEditDistance =
+      abs(static_cast<int>(Candidate.size()) - static_cast<int>(Typo.size()));
+
+  if (MinPossibleEditDistance > 0 && Typo.size() / MinPossibleEditDistance < 3)
+    return;
+
+  unsigned EditDistance = Typo.edit_distance(
+      Candidate, /*AllowReplacements*/ true, MaxEditDistance);
+
+  if (EditDistance < BestEditDistance) {
+    BestCandidate = Candidate;
+    BestEditDistance = EditDistance;
+    BestIndex = NextIndex;
+  }
+
+  ++NextIndex;
+}
+
+void SimpleTypoCorrection::add(const char *Candidate) {
+  if (Candidate)
+    add(StringRef(Candidate));
+}
+
+void SimpleTypoCorrection::add(const IdentifierInfo *Candidate) {
+  if (Candidate)
+    add(Candidate->getName());
+}
+
+unsigned SimpleTypoCorrection::getCorrectionIndex() const {
+  return BestIndex;
+}
+
+std::optional<StringRef> SimpleTypoCorrection::getCorrection() const {
+  if (hasCorrection())
+    return BestCandidate;
+  return std::nullopt;
+}
+
+bool SimpleTypoCorrection::hasCorrection() const {
+  return BestEditDistance <= MaxEditDistance;
+}
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 4d7f0455444f1..c7840fb199ff6 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -6867,9 +6867,7 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, 
const ParsedAttr &AL,
                               : diag::warn_unhandled_ms_attribute_ignored)
           << AL.getAttrName() << AL.getRange();
     } else {
-      S.Diag(AL.getNormalizedRange().getBegin(),
-             diag::warn_unknown_attribute_ignored)
-          << "'" + AL.getNormalizedFullName() + "'" << AL.getNormalizedRange();
+      S.DiagnoseUnknownAttribute(AL);
     }
     return;
   }
@@ -7865,6 +7863,20 @@ void Sema::checkUnusedDeclAttributes(Declarator &D) {
     ::checkUnusedDeclAttributes(*this, D.getTypeObject(i).getAttrs());
 }
 
+void Sema::DiagnoseUnknownAttribute(const ParsedAttr &AL) {
+  std::string NormalizedFullName = '\'' + AL.getNormalizedFullName() + '\'';
+  if (auto CorrectedFullName =
+          AL.getCorrectedFullName(Context.getTargetInfo(), getLangOpts())) {
+    Diag(AL.getNormalizedRange().getBegin(),
+         diag::warn_unknown_attribute_ignored_suggestion)
+        << NormalizedFullName << *CorrectedFullName << AL.getNormalizedRange();
+  } else {
+    Diag(AL.getNormalizedRange().getBegin(),
+         diag::warn_unknown_attribute_ignored)
+        << NormalizedFullName << AL.getNormalizedRange();
+  }
+}
+
 NamedDecl *Sema::DeclClonePragmaWeak(NamedDecl *ND, const IdentifierInfo *II,
                                      SourceLocation Loc) {
   assert(isa<FunctionDecl>(ND) || isa<VarDecl>(ND));
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 9ed2326f151a3..550c7a36205b1 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8788,9 +8788,7 @@ static void processTypeAttrs(TypeProcessingState &state, 
QualType &type,
 
     case ParsedAttr::UnknownAttribute:
       if (attr.isStandardAttributeSyntax()) {
-        state.getSema().Diag(attr.getLoc(),
-                             diag::warn_unknown_attribute_ignored)
-            << attr << attr.getRange();
+        state.getSema().DiagnoseUnknownAttribute(attr);
         // Mark the attribute as invalid so we don't emit the same diagnostic
         // multiple times.
         attr.setInvalid();
diff --git a/clang/test/Parser/cxx0x-attributes.cpp 
b/clang/test/Parser/cxx0x-attributes.cpp
index 6e0904e94fecc..372a373a49ec5 100644
--- a/clang/test/Parser/cxx0x-attributes.cpp
+++ b/clang/test/Parser/cxx0x-attributes.cpp
@@ -344,7 +344,7 @@ enum class [[]] EvenMoreSecrets {};
 
 namespace arguments {
   void f[[gnu::format(printf, 1, 2)]](const char*, ...);
-  void g() [[unknown::foo(ignore arguments for unknown attributes, even with 
symbols!)]]; // expected-warning {{unknown attribute 'foo' ignored}}
+  void g() [[unknown::foo(ignore arguments for unknown attributes, even with 
symbols!)]]; // expected-warning {{unknown attribute 'unknown::foo' ignored}}
   [[deprecated("with argument")]] int i;
   // expected-warning@-1 {{use of the 'deprecated' attribute is a C++14 
extension}}
 }
diff --git a/clang/test/Sema/attr-c2x.c b/clang/test/Sema/attr-c2x.c
index c774cc68eb890..eff90d639c0c0 100644
--- a/clang/test/Sema/attr-c2x.c
+++ b/clang/test/Sema/attr-c2x.c
@@ -39,5 +39,5 @@ void bar(void) {
 [[__gnu__::__hot__]] void hot_func4(void);
 
 // Note how not all GCC attributes are supported in C.
-[[gnu::abi_tag("")]] void abi_func(void); // expected-warning {{unknown 
attribute 'abi_tag' ignored}}
-struct S s [[gnu::init_priority(1)]]; // expected-warning {{unknown attribute 
'init_priority' ignored}}
+[[gnu::abi_tag("")]] void abi_func(void); // expected-warning {{unknown 
attribute 'gnu::abi_tag' ignored}}
+struct S s [[gnu::init_priority(1)]]; // expected-warning {{unknown attribute 
'gnu::init_priority' ignored}}
diff --git a/clang/test/Sema/unknown-attributes.c 
b/clang/test/Sema/unknown-attributes.c
index 1f6708fc7219f..a701650c9e056 100644
--- a/clang/test/Sema/unknown-attributes.c
+++ b/clang/test/Sema/unknown-attributes.c
@@ -1,12 +1,22 @@
 // RUN: %clang_cc1 -Wunknown-attributes -fsyntax-only -verify %s
-// RUN: %clang_cc1 -x c++ -fsyntax-only -Wunknown-attributes -verify %s
+// RUN: %clang_cc1 -x c++ -Wunknown-attributes -fsyntax-only -verify %s
 
-[[foo::a]] // expected-warning {{unknown attribute 'foo::a' ignored}}
+[[gmu::deprected]] // expected-warning {{unknown attribute 'gmu::deprected' 
ignored; did you mean 'gnu::deprecated'?}}
 int f1(void) {
   return 0;
 }
 
-[[clan::deprecated]] // expected-warning {{unknown attribute 
'clan::deprecated' ignored}}
+[[gmu::deprecated]] // expected-warning {{unknown attribute 'gmu::deprecated' 
ignored; did you mean 'gnu::deprecated'?}}
 int f2(void) {
   return 0;
 }
+
+[[gnu::deprected]] // expected-warning {{unknown attribute 'gnu::deprected' 
ignored; did you mean 'gnu::deprecated'?}}
+int f3(void) {
+  return 0;
+}
+
+[[deprected]] // expected-warning {{unknown attribute 'deprected' ignored; did 
you mean 'deprecated'?}}
+int f4(void) {
+  return 0;
+}
diff --git a/clang/test/SemaCXX/cxx11-gnu-attrs.cpp 
b/clang/test/SemaCXX/cxx11-gnu-attrs.cpp
index a91cb278b4d24..b8d914a6791a9 100644
--- a/clang/test/SemaCXX/cxx11-gnu-attrs.cpp
+++ b/clang/test/SemaCXX/cxx11-gnu-attrs.cpp
@@ -3,7 +3,7 @@
 // Error cases.
 
 [[gnu::this_attribute_does_not_exist]] int unknown_attr;
-// expected-warning@-1 {{unknown attribute 'this_attribute_does_not_exist' 
ignored}}
+// expected-warning@-1 {{unknown attribute 
'gnu::this_attribute_does_not_exist' ignored}}
 int [[gnu::unused]] attr_on_type;
 // expected-error@-1 {{'unused' attribute cannot be applied to types}}
 int *[[gnu::unused]] attr_on_ptr;
diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp 
b/clang/utils/TableGen/ClangAttrEmitter.cpp
index 9684ec9520e5a..1487a473814d2 100644
--- a/clang/utils/TableGen/ClangAttrEmitter.cpp
+++ b/clang/utils/TableGen/ClangAttrEmitter.cpp
@@ -4105,6 +4105,33 @@ void EmitClangAttrParsedAttrList(const RecordKeeper 
&Records, raw_ostream &OS) {
   }
 }
 
+void EmitAttributeSpellingList(const RecordKeeper &Records, raw_ostream &OS) {
+  emitSourceFileHeader("List of attribute names", OS, Records);
+
+  std::set<StringRef> AttrSpellingList;
+  std::set<StringRef> AttrScopeSpellingList;
+
+  for (const auto *A : Records.getAllDerivedDefinitions("Attr")) {
+    for (const auto &S : GetFlattenedSpellings(*A)) {
+      AttrSpellingList.insert(S.name());
+      if (S.nameSpace().size())
+        AttrScopeSpellingList.insert(S.nameSpace());
+    }
+  }
+
+  OS << "static constexpr const char *AttrSpellingList[] = {\n";
+  for (const auto &AttrName : AttrSpellingList) {
+    OS << "  " << "\"" << AttrName << "\"" << "," << "\n";
+  }
+  OS << "};" << "\n";
+
+  OS << "static constexpr const char *AttrScopeSpellingList[] = {\n";
+  for (const auto &AttrScopeName : AttrScopeSpellingList) {
+    OS << "  " << "\"" << AttrScopeName << "\"" << "," << "\n";
+  }
+  OS << "};" << "\n";
+}
+
 static bool isArgVariadic(const Record &R, StringRef AttrName) {
   return createArgument(R, AttrName)->isVariadic();
 }
diff --git a/clang/utils/TableGen/TableGen.cpp 
b/clang/utils/TableGen/TableGen.cpp
index a2c6f002f7359..9ab6e7d1e61d7 100644
--- a/clang/utils/TableGen/TableGen.cpp
+++ b/clang/utils/TableGen/TableGen.cpp
@@ -73,6 +73,7 @@ enum ActionType {
   GenClangOpenCLBuiltinHeader,
   GenClangOpenCLBuiltinTests,
   GenCXX11AttributeInfo,
+  GenAttributeSpellingList,
   GenArmNeon,
   GenArmFP16,
   GenArmBF16,
@@ -240,6 +241,8 @@ cl::opt<ActionType> Action(
                    "Generate OpenCL builtin declaration tests"),
         clEnumValN(GenCXX11AttributeInfo, "gen-cxx11-attribute-info",
                    "Generate CXX11 attributes info"),
+        clEnumValN(GenAttributeSpellingList, "gen-attribute-spelling-list",
+                   "Generate attribute spelling list"),
         clEnumValN(GenArmNeon, "gen-arm-neon", "Generate arm_neon.h for 
clang"),
         clEnumValN(GenArmFP16, "gen-arm-fp16", "Generate arm_fp16.h for 
clang"),
         clEnumValN(GenArmBF16, "gen-arm-bf16", "Generate arm_bf16.h for 
clang"),
@@ -351,6 +354,9 @@ bool ClangTableGenMain(raw_ostream &OS, const RecordKeeper 
&Records) {
   case GenCXX11AttributeInfo:
     EmitCXX11AttributeInfo(Records, OS);
     break;
+  case GenAttributeSpellingList:
+    EmitAttributeSpellingList(Records, OS);
+    break;
   case GenClangAttrImpl:
     EmitClangAttrImpl(Records, OS);
     break;
diff --git a/clang/utils/TableGen/TableGenBackends.h 
b/clang/utils/TableGen/TableGenBackends.h
index 54031147d38e1..79b1f66d0e49e 100644
--- a/clang/utils/TableGen/TableGenBackends.h
+++ b/clang/utils/TableGen/TableGenBackends.h
@@ -51,6 +51,8 @@ void EmitClangAttrSubjectMatchRulesParserStringSwitches(
     const llvm::RecordKeeper &Records, llvm::raw_ostream &OS);
 void EmitCXX11AttributeInfo(const llvm::RecordKeeper &Records,
                             llvm::raw_ostream &OS);
+void EmitAttributeSpellingList(const llvm::RecordKeeper &Records,
+                               llvm::raw_ostream &OS);
 void EmitClangAttrClass(const llvm::RecordKeeper &Records,
                         llvm::raw_ostream &OS);
 void EmitClangAttrImpl(const llvm::RecordKeeper &Records,

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to