owenv created this revision.
Herald added subscribers: cfe-commits, arphaman.
Herald added a project: clang.
An educational note is attached to a diagnostic and is essentially just a path
to associated documentation somewhere in a compiler toolchain. The
documentation can then be displayed alongside emitted diagnostics to teach users
about relevant language concepts or describe common problems and solutions.

This commit adds records for serializing educational notes in .dia files and
adds support for retrieving them from deserialized diagnostics via libclang.
It does not add any mechanisms for emitting educational notes alongside Clang
diagnostics. For now, only downstream projects (swift) support diagnostics
with educational notes


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D80126

Files:
  clang/include/clang-c/Index.h
  clang/include/clang/Frontend/SerializedDiagnosticReader.h
  clang/include/clang/Frontend/SerializedDiagnostics.h
  clang/lib/Frontend/SerializedDiagnosticPrinter.cpp
  clang/lib/Frontend/SerializedDiagnosticReader.cpp
  clang/test/Misc/Inputs/serialized-diags-edu-notes.dia
  clang/test/Misc/serialized-diags-edu-notes.c
  clang/tools/c-index-test/c-index-test.c
  clang/tools/libclang/CIndexDiagnostic.cpp
  clang/tools/libclang/CIndexDiagnostic.h
  clang/tools/libclang/CXLoadedDiagnostic.cpp
  clang/tools/libclang/CXLoadedDiagnostic.h
  clang/tools/libclang/CXStoredDiagnostic.cpp
  clang/tools/libclang/libclang.exports

Index: clang/tools/libclang/libclang.exports
===================================================================
--- clang/tools/libclang/libclang.exports
+++ clang/tools/libclang/libclang.exports
@@ -219,6 +219,7 @@
 clang_getDiagnosticCategory
 clang_getDiagnosticCategoryName
 clang_getDiagnosticCategoryText
+clang_getDiagnosticEducationalNotePaths
 clang_getDiagnosticFixIt
 clang_getDiagnosticInSet
 clang_getDiagnosticLocation
Index: clang/tools/libclang/CXStoredDiagnostic.cpp
===================================================================
--- clang/tools/libclang/CXStoredDiagnostic.cpp
+++ clang/tools/libclang/CXStoredDiagnostic.cpp
@@ -109,3 +109,7 @@
   return cxstring::createDup(Hint.CodeToInsert);
 }
 
+CXStringSet *CXStoredDiagnostic::getEducationalNotePaths() const {
+  // Educational notes are not yet supported by native Clang diagnostics.
+  return cxstring::createSet({});
+}
Index: clang/tools/libclang/CXLoadedDiagnostic.h
===================================================================
--- clang/tools/libclang/CXLoadedDiagnostic.h
+++ clang/tools/libclang/CXLoadedDiagnostic.h
@@ -58,6 +58,9 @@
   CXString getFixIt(unsigned FixIt,
                     CXSourceRange *ReplacementRange) const override;
 
+  /// Return the educational note paths associated with the diagnostic.
+  virtual CXStringSet *getEducationalNotePaths() const override;
+
   static bool classof(const CXDiagnosticImpl *D) {
     return D->getKind() == LoadedDiagnosticKind;
   }
@@ -82,6 +85,7 @@
 
   std::vector<CXSourceRange> Ranges;
   std::vector<std::pair<CXSourceRange, const char *> > FixIts;
+  std::vector<std::string> EducationalNotePaths;
   const char *Spelling;
   llvm::StringRef DiagOption;
   llvm::StringRef CategoryText;
Index: clang/tools/libclang/CXLoadedDiagnostic.cpp
===================================================================
--- clang/tools/libclang/CXLoadedDiagnostic.cpp
+++ clang/tools/libclang/CXLoadedDiagnostic.cpp
@@ -41,7 +41,8 @@
   Strings Categories;
   Strings WarningFlags;
   Strings FileNames;
-  
+  Strings EducationalNotePaths;
+
   FileSystemOptions FO;
   FileManager FakeFiles;
   llvm::DenseMap<unsigned, const FileEntry *> Files;
@@ -145,6 +146,10 @@
   return cxstring::createRef(FixIts[FixIt].second);
 }
 
+CXStringSet *CXLoadedDiagnostic::getEducationalNotePaths() const {
+  return cxstring::createSet(EducationalNotePaths);
+}
+
 void CXLoadedDiagnostic::decodeLocation(CXSourceLocation location,
                                         CXFile *file,
                                         unsigned int *line,
@@ -233,6 +238,10 @@
   visitSourceRangeRecord(const serialized_diags::Location &Start,
                          const serialized_diags::Location &End) override;
 
+  std::error_code visitEducationalNotePathRecord(unsigned ID,
+                                                 StringRef Path) override;
+  std::error_code visitEducationalNoteRecord(unsigned ID) override;
+
 public:
   DiagLoader(enum CXLoadDiag_Error *e, CXString *es)
       : SerializedDiagnosticReader(), error(e), errorString(es) {
@@ -347,6 +356,15 @@
   return std::error_code();
 }
 
+std::error_code DiagLoader::visitEducationalNotePathRecord(unsigned ID,
+                                                           StringRef Path) {
+  // FIXME: Why do we care about long strings?
+  if (Path.size() > 65536)
+    return reportInvalidFile("Out-of-bounds string in educational note path");
+  TopDiags->EducationalNotePaths[ID] = TopDiags->copyString(Path);
+  return std::error_code();
+}
+
 std::error_code
 DiagLoader::visitSourceRangeRecord(const serialized_diags::Location &Start,
                                    const serialized_diags::Location &End) {
@@ -372,6 +390,12 @@
   return std::error_code();
 }
 
+std::error_code DiagLoader::visitEducationalNoteRecord(unsigned ID) {
+  CurrentDiags.back()->EducationalNotePaths.push_back(
+      TopDiags->EducationalNotePaths[ID]);
+  return std::error_code();
+}
+
 std::error_code DiagLoader::visitDiagnosticRecord(
     unsigned Severity, const serialized_diags::Location &Location,
     unsigned Category, unsigned Flag, StringRef Message) {
Index: clang/tools/libclang/CIndexDiagnostic.h
===================================================================
--- clang/tools/libclang/CIndexDiagnostic.h
+++ clang/tools/libclang/CIndexDiagnostic.h
@@ -89,6 +89,9 @@
   virtual CXString getFixIt(unsigned FixIt,
                             CXSourceRange *ReplacementRange) const = 0;
 
+  /// Return the educational note paths associated with the diagnostic.
+  virtual CXStringSet *getEducationalNotePaths() const = 0;
+
   Kind getKind() const { return K; }
   
   CXDiagnosticSetImpl &getChildDiagnostics() {
@@ -150,6 +153,9 @@
   CXString getFixIt(unsigned FixIt,
                     CXSourceRange *ReplacementRange) const override;
 
+  /// Return the educational note paths associated with the diagnostic.
+  CXStringSet *getEducationalNotePaths() const override;
+
   static bool classof(const CXDiagnosticImpl *D) {
     return D->getKind() == StoredDiagnosticKind;
   }
Index: clang/tools/libclang/CIndexDiagnostic.cpp
===================================================================
--- clang/tools/libclang/CIndexDiagnostic.cpp
+++ clang/tools/libclang/CIndexDiagnostic.cpp
@@ -77,6 +77,9 @@
       *ReplacementRange = clang_getNullRange();
     return cxstring::createEmpty();
   }
+  CXStringSet *getEducationalNotePaths() const override {
+    return cxstring::createSet({});
+  }
 };    
     
 class CXDiagnosticRenderer : public DiagnosticNoteRenderer {
@@ -438,6 +441,12 @@
   return D->getFixIt(FixIt, ReplacementRange);
 }
 
+CXStringSet *clang_getDiagnosticEducationalNotePaths(CXDiagnostic Diagnostic) {
+  if (CXDiagnosticImpl *D = static_cast<CXDiagnosticImpl *>(Diagnostic))
+    return D->getEducationalNotePaths();
+  return cxstring::createSet({});
+}
+
 void clang_disposeDiagnosticSet(CXDiagnosticSet Diags) {
   if (CXDiagnosticSetImpl *D = static_cast<CXDiagnosticSetImpl *>(Diags)) {
     if (D->isExternallyManaged())
Index: clang/tools/c-index-test/c-index-test.c
===================================================================
--- clang/tools/c-index-test/c-index-test.c
+++ clang/tools/c-index-test/c-index-test.c
@@ -1189,10 +1189,11 @@
   FILE *out = stderr;
   CXFile file;
   CXString Msg;
+  CXStringSet *EducationalNotePaths = NULL;
   unsigned display_opts = CXDiagnostic_DisplaySourceLocation
     | CXDiagnostic_DisplayColumn | CXDiagnostic_DisplaySourceRanges
     | CXDiagnostic_DisplayOption;
-  unsigned i, num_fixits;
+  unsigned i, count;
 
   if (clang_getDiagnosticSeverity(Diagnostic) == CXDiagnostic_Ignored)
     return;
@@ -1206,9 +1207,9 @@
   if (!file)
     return;
 
-  num_fixits = clang_getDiagnosticNumFixIts(Diagnostic);
-  fprintf(stderr, "Number FIX-ITs = %d\n", num_fixits);
-  for (i = 0; i != num_fixits; ++i) {
+  count = clang_getDiagnosticNumFixIts(Diagnostic);
+  fprintf(stderr, "Number FIX-ITs = %d\n", count);
+  for (i = 0; i != count; ++i) {
     CXSourceRange range;
     CXString insertion_text = clang_getDiagnosticFixIt(Diagnostic, i, &range);
     CXSourceLocation start = clang_getRangeStart(range);
@@ -1240,6 +1241,14 @@
     }
     clang_disposeString(insertion_text);
   }
+
+  EducationalNotePaths = clang_getDiagnosticEducationalNotePaths(Diagnostic);
+  if (EducationalNotePaths) {
+    for (i = 0, count = EducationalNotePaths->Count; i < count; ++i)
+      printf("[Educational Note: %s]\n",
+             clang_getCString(EducationalNotePaths->Strings[i]));
+    clang_disposeStringSet(EducationalNotePaths);
+  }
 }
 
 void PrintDiagnosticSet(CXDiagnosticSet Set) {
@@ -4669,6 +4678,18 @@
   }  
 }
 
+static void printEducationalNotePaths(CXDiagnostic D, unsigned indent) {
+  unsigned i, count;
+  CXStringSet *EducationalNotePaths =
+      clang_getDiagnosticEducationalNotePaths(D);
+  for (i = 0, count = EducationalNotePaths->Count; i < count; ++i) {
+    printIndent(indent);
+    printf("[Educational Note: %s]\n",
+           clang_getCString(EducationalNotePaths->Strings[i]));
+  }
+  clang_disposeStringSet(EducationalNotePaths);
+}
+
 static void printDiagnosticSet(CXDiagnosticSet Diags, unsigned indent) {
   unsigned i, n;
 
@@ -4716,7 +4737,8 @@
     
     printRanges(D, indent);
     printFixIts(D, indent);
-    
+    printEducationalNotePaths(D, indent);
+
     /* Print subdiagnostics. */
     printDiagnosticSet(clang_getChildDiagnostics(D), indent+2);
 
Index: clang/test/Misc/serialized-diags-edu-notes.c
===================================================================
--- /dev/null
+++ clang/test/Misc/serialized-diags-edu-notes.c
@@ -0,0 +1,4 @@
+// RUN: c-index-test -read-diagnostics %S/Inputs/serialized-diags-edu-notes.dia 2>&1 | FileCheck %s
+
+// CHECK: hello.swift:1:1: error: non-nominal type '(Int, Int)' cannot be extended [] []
+// CHECK: [Educational Note: /Users/owenvoorhees/Documents/Development/swift-source/build/Ninja-ReleaseAssert/swift-macosx-x86_64/share/doc/swift/diagnostics/nominal-types.md]
Index: clang/lib/Frontend/SerializedDiagnosticReader.cpp
===================================================================
--- clang/lib/Frontend/SerializedDiagnosticReader.cpp
+++ clang/lib/Frontend/SerializedDiagnosticReader.cpp
@@ -318,6 +318,20 @@
       if ((EC = visitVersionRecord(Record[0])))
         return EC;
       continue;
+    case RECORD_EDUCATIONAL_NOTE_PATH:
+      // An educational note path has an ID and path size.
+      if (Record.size() != 2)
+        return SDError::MalformedDiagnosticRecord;
+      if ((EC = visitEducationalNotePathRecord(Record[0], Blob)))
+        return EC;
+      continue;
+    case RECORD_EDUCATIONAL_NOTE:
+      // An educational note just has an ID.
+      if (Record.size() != 1)
+        return SDError::MalformedDiagnosticRecord;
+      if ((EC = visitEducationalNoteRecord(Record[0])))
+        return EC;
+      continue;
     }
   }
 }
Index: clang/lib/Frontend/SerializedDiagnosticPrinter.cpp
===================================================================
--- clang/lib/Frontend/SerializedDiagnosticPrinter.cpp
+++ clang/lib/Frontend/SerializedDiagnosticPrinter.cpp
@@ -93,6 +93,7 @@
   AbbrevLookup FileLookup;
   AbbrevLookup CategoryLookup;
   AbbrevLookup DiagFlagLookup;
+  AbbrevLookup EduNoteLookup;
 
 public:
   SDiagsMerger(SDiagsWriter &Writer)
@@ -120,6 +121,10 @@
   visitSourceRangeRecord(const serialized_diags::Location &Start,
                          const serialized_diags::Location &End) override;
 
+  std::error_code visitEducationalNotePathRecord(unsigned ID,
+                                                 StringRef Path) override;
+  std::error_code visitEducationalNoteRecord(unsigned ID) override;
+
 private:
   std::error_code adjustSourceLocFilename(RecordData &Record,
                                           unsigned int offset);
@@ -197,6 +202,8 @@
                        ArrayRef<FixItHint> Hints,
                        const SourceManager &SM);
 
+  void EmitEducationalNotes(ArrayRef<StringRef> Notes);
+
   /// Emit a record for a CharSourceRange.
   void EmitCharSourceRange(CharSourceRange R, const SourceManager &SM);
 
@@ -212,6 +219,9 @@
   /// Emit (lazily) the file string and retrieved the file identifier.
   unsigned getEmitFile(const char *Filename);
 
+  /// Lazily emit an educational note path.
+  unsigned getEmitEducationalNotePath(StringRef Path);
+
   /// Add SourceLocation information the specified record.
   void AddLocToRecord(FullSourceLoc Loc, PresumedLoc PLoc,
                       RecordDataImpl &Record, unsigned TokSize = 0);
@@ -273,6 +283,9 @@
     /// The collection of files used.
     llvm::DenseMap<const char *, unsigned> Files;
 
+    /// The collection of educational note paths used.
+    llvm::DenseMap<const char *, unsigned> EducationalNotePaths;
+
     typedef llvm::DenseMap<const void *, std::pair<unsigned, StringRef> >
     DiagFlagsTy;
 
@@ -452,6 +465,8 @@
   EmitRecordID(RECORD_DIAG_FLAG, "DiagFlag", Stream, Record);
   EmitRecordID(RECORD_FILENAME, "FileName", Stream, Record);
   EmitRecordID(RECORD_FIXIT, "FixIt", Stream, Record);
+  EmitRecordID(RECORD_EDUCATIONAL_NOTE_PATH, "EduNotePath", Stream, Record);
+  EmitRecordID(RECORD_EDUCATIONAL_NOTE, "EduNote", Stream, Record);
 
   // Emit abbreviation for RECORD_DIAG.
   Abbrev = std::make_shared<BitCodeAbbrev>();
@@ -508,6 +523,24 @@
   Abbrevs.set(RECORD_FIXIT, Stream.EmitBlockInfoAbbrev(BLOCK_DIAG,
                                                        Abbrev));
 
+  // Emit the abbreviation for RECORD_EDUCATIONAL_NOTE_PATH.
+  Abbrev = std::make_shared<BitCodeAbbrev>();
+  Abbrev->Add(BitCodeAbbrevOp(RECORD_EDUCATIONAL_NOTE_PATH));
+  Abbrev->Add(
+      BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 10)); // Mapped edu note ID.
+  Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 16)); // Path size.
+  Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob));      // Path text.
+  Abbrevs.set(RECORD_EDUCATIONAL_NOTE_PATH,
+              Stream.EmitBlockInfoAbbrev(BLOCK_DIAG, Abbrev));
+
+  // Emit the abbreviation for RECORD_EDUCATIONAL_NOTE.
+  Abbrev = std::make_shared<BitCodeAbbrev>();
+  Abbrev->Add(BitCodeAbbrevOp(RECORD_EDUCATIONAL_NOTE));
+  Abbrev->Add(
+      BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 10)); // Mapped edu note ID.
+  Abbrevs.set(RECORD_EDUCATIONAL_NOTE,
+              Stream.EmitBlockInfoAbbrev(BLOCK_DIAG, Abbrev));
+
   Stream.ExitBlock();
 }
 
@@ -566,6 +599,24 @@
   return entry.first;
 }
 
+unsigned SDiagsWriter::getEmitEducationalNotePath(StringRef Path) {
+  if (Path.empty())
+    return 0;
+
+  unsigned &entry = State->EducationalNotePaths[Path.data()];
+  if (entry)
+    return entry;
+
+  // Lazily generate the record for the path.
+  entry = State->EducationalNotePaths.size();
+  RecordData::value_type Record[] = {RECORD_EDUCATIONAL_NOTE_PATH, entry,
+                                     Path.size()};
+  State->Stream.EmitRecordWithBlob(
+      State->Abbrevs.get(RECORD_EDUCATIONAL_NOTE_PATH), Record, Path);
+
+  return entry;
+}
+
 void SDiagsWriter::HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
                                     const Diagnostic &Info) {
   // Enter the block for a non-note diagnostic immediately, rather than waiting
@@ -718,6 +769,21 @@
   Writer.EmitCodeContext(Ranges, Hints, Loc.getManager());
 }
 
+void SDiagsWriter::EmitEducationalNotes(ArrayRef<StringRef> Notes) {
+  llvm::BitstreamWriter &Stream = State->Stream;
+  RecordData &Record = State->Record;
+  AbbreviationMap &Abbrevs = State->Abbrevs;
+
+  for (ArrayRef<StringRef>::iterator I = Notes.begin(), E = Notes.end(); I != E;
+       ++I) {
+    unsigned ID = getEmitEducationalNotePath(*I);
+    Record.clear();
+    Record.push_back(ID);
+    Stream.EmitRecord(RECORD_EDUCATIONAL_NOTE, Record,
+                      Abbrevs.get(RECORD_EDUCATIONAL_NOTE));
+  }
+}
+
 void SDiagsRenderer::emitNote(FullSourceLoc Loc, StringRef Message) {
   Writer.EnterDiagBlock();
   PresumedLoc PLoc = Loc.hasManager() ? Loc.getPresumedLoc() : PresumedLoc();
@@ -858,3 +924,17 @@
   DiagFlagLookup[ID] = Writer.getEmitDiagnosticFlag(Name);
   return std::error_code();
 }
+
+std::error_code SDiagsMerger::visitEducationalNotePathRecord(unsigned ID,
+                                                             StringRef Path) {
+  EduNoteLookup[ID] = Writer.getEmitEducationalNotePath(Path);
+  return std::error_code();
+}
+
+std::error_code SDiagsMerger::visitEducationalNoteRecord(unsigned ID) {
+  RecordData::value_type Record[] = {EduNoteLookup[ID]};
+  Writer.State->Stream.EmitRecord(
+      RECORD_EDUCATIONAL_NOTE, Record,
+      Writer.State->Abbrevs.get(RECORD_EDUCATIONAL_NOTE));
+  return std::error_code();
+}
Index: clang/include/clang/Frontend/SerializedDiagnostics.h
===================================================================
--- clang/include/clang/Frontend/SerializedDiagnostics.h
+++ clang/include/clang/Frontend/SerializedDiagnostics.h
@@ -32,8 +32,10 @@
   RECORD_CATEGORY,
   RECORD_FILENAME,
   RECORD_FIXIT,
+  RECORD_EDUCATIONAL_NOTE_PATH,
+  RECORD_EDUCATIONAL_NOTE,
   RECORD_FIRST = RECORD_VERSION,
-  RECORD_LAST = RECORD_FIXIT
+  RECORD_LAST = RECORD_EDUCATIONAL_NOTE
 };
 
 /// A stable version of DiagnosticIDs::Level.
Index: clang/include/clang/Frontend/SerializedDiagnosticReader.h
===================================================================
--- clang/include/clang/Frontend/SerializedDiagnosticReader.h
+++ clang/include/clang/Frontend/SerializedDiagnosticReader.h
@@ -121,6 +121,14 @@
     return {};
   }
 
+  /// Visit an educational note path.
+  virtual std::error_code visitEducationalNotePathRecord(unsigned ID,
+                                                         StringRef Path) {
+    return {};
+  }
+
+  virtual std::error_code visitEducationalNoteRecord(unsigned ID) { return {}; }
+
   /// Visit the version of the set of diagnostics.
   virtual std::error_code visitVersionRecord(unsigned Version) { return {}; }
 };
Index: clang/include/clang-c/Index.h
===================================================================
--- clang/include/clang-c/Index.h
+++ clang/include/clang-c/Index.h
@@ -1103,6 +1103,21 @@
 CINDEX_LINKAGE CXString clang_getDiagnosticFixIt(
     CXDiagnostic Diagnostic, unsigned FixIt, CXSourceRange *ReplacementRange);
 
+/**
+ * Retrieve the educational note paths of a given diagnostic.
+ *
+ * An educational note path is a path to external documentation associated with
+ * a diagnostic. The documentation is intended to be displayed alongside emitted
+ * diagnostics to teach users about relevant language concepts or describe
+ * common problems and solutions.
+ *
+ * \param Diagnostic The diagnostic whose educational notes are being queried.
+ *
+ * \returns A set of strings which represent paths to educational notes.
+ */
+CINDEX_LINKAGE CXStringSet *
+clang_getDiagnosticEducationalNotePaths(CXDiagnostic Diagnostic);
+
 /**
  * @}
  */
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to